mirror of
https://github.com/Myzel394/config-lsp.git
synced 2025-06-19 07:25:27 +02:00
feat: Adding fstab; Adding root handler; Improvements
This commit is contained in:
parent
a6a4c04af3
commit
49a12c9080
9
common/PARSER.md
Normal file
9
common/PARSER.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Parser
|
||||||
|
|
||||||
|
The parser used in `config-lsp` work on the following principles:
|
||||||
|
|
||||||
|
1. Read the configuration file and divide each line into sections
|
||||||
|
2. On changes, run the "analyzer", which will then actually parse the sections and check for errors
|
||||||
|
3. On completion requests, check which section is being requested and return completions for it
|
||||||
|
4. On hover requests, check which section is being requested and return the hover information for it
|
||||||
|
|
@ -2,10 +2,42 @@ package common
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
docvalues "config-lsp/doc-values"
|
docvalues "config-lsp/doc-values"
|
||||||
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Parser interface {
|
||||||
|
ParseFromContent(content string) []ParseError
|
||||||
|
AnalyzeValues() []protocol.Diagnostic
|
||||||
|
}
|
||||||
|
|
||||||
|
type ParseError struct {
|
||||||
|
Line uint32
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ParseError) Error() string {
|
||||||
|
return "Parse error"
|
||||||
|
}
|
||||||
|
func (e ParseError) ToDiagnostic() protocol.Diagnostic {
|
||||||
|
severity := protocol.DiagnosticSeverityError
|
||||||
|
return protocol.Diagnostic{
|
||||||
|
Severity: &severity,
|
||||||
|
Message: e.Err.Error(),
|
||||||
|
Range: protocol.Range{
|
||||||
|
Start: protocol.Position{
|
||||||
|
Line: e.Line,
|
||||||
|
Character: 0,
|
||||||
|
},
|
||||||
|
End: protocol.Position{
|
||||||
|
Line: e.Line,
|
||||||
|
Character: 999999,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type SimpleConfigPosition struct {
|
type SimpleConfigPosition struct {
|
||||||
Line uint32
|
Line uint32
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ func fetchPasswdInfo() ([]passwdInfo, error) {
|
|||||||
// if `separatorForMultiple` is not empty, it will return an ArrayValue
|
// if `separatorForMultiple` is not empty, it will return an ArrayValue
|
||||||
func UserValue(separatorForMultiple string, enforceValues bool) Value {
|
func UserValue(separatorForMultiple string, enforceValues bool) Value {
|
||||||
return CustomValue{
|
return CustomValue{
|
||||||
FetchValue: func() Value {
|
FetchValue: func(context CustomValueContext) Value {
|
||||||
infos, err := fetchPasswdInfo()
|
infos, err := fetchPasswdInfo()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -125,7 +125,7 @@ func fetchGroupInfo() ([]groupInfo, error) {
|
|||||||
|
|
||||||
func GroupValue(separatorForMultiple string, enforceValues bool) Value {
|
func GroupValue(separatorForMultiple string, enforceValues bool) Value {
|
||||||
return CustomValue{
|
return CustomValue{
|
||||||
FetchValue: func() Value {
|
FetchValue: func(context CustomValueContext) Value {
|
||||||
infos, err := fetchGroupInfo()
|
infos, err := fetchGroupInfo()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -4,8 +4,20 @@ import (
|
|||||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type CustomValueContext interface {
|
||||||
|
GetIsContext() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type EmptyValueContext struct{}
|
||||||
|
|
||||||
|
func (EmptyValueContext) GetIsContext() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var EmptyValueContextInstance = EmptyValueContext{}
|
||||||
|
|
||||||
type CustomValue struct {
|
type CustomValue struct {
|
||||||
FetchValue func() Value
|
FetchValue func(context CustomValueContext) Value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v CustomValue) GetTypeDescription() []string {
|
func (v CustomValue) GetTypeDescription() []string {
|
||||||
@ -13,9 +25,9 @@ func (v CustomValue) GetTypeDescription() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v CustomValue) CheckIsValid(value string) error {
|
func (v CustomValue) CheckIsValid(value string) error {
|
||||||
return v.FetchValue().CheckIsValid(value)
|
return v.FetchValue(EmptyValueContextInstance).CheckIsValid(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v CustomValue) FetchCompletions(line string, cursor uint32) []protocol.CompletionItem {
|
func (v CustomValue) FetchCompletions(line string, cursor uint32) []protocol.CompletionItem {
|
||||||
return v.FetchValue().FetchCompletions(line, cursor)
|
return v.FetchValue(EmptyValueContextInstance).FetchCompletions(line, cursor)
|
||||||
}
|
}
|
||||||
|
@ -14,8 +14,17 @@ func (e KeyValueAssignmentError) Error() string {
|
|||||||
return "This is not valid key-value assignment"
|
return "This is not valid key-value assignment"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type KeyValueAssignmentContext struct {
|
||||||
|
SelectedKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (KeyValueAssignmentContext) GetIsContext() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
type KeyValueAssignmentValue struct {
|
type KeyValueAssignmentValue struct {
|
||||||
Key Value
|
Key Value
|
||||||
|
// If this is a `CustomValue`, it will receive a `KeyValueAssignmentContext`
|
||||||
Value Value
|
Value Value
|
||||||
ValueIsOptional bool
|
ValueIsOptional bool
|
||||||
Separator string
|
Separator string
|
||||||
@ -38,6 +47,24 @@ func (v KeyValueAssignmentValue) GetTypeDescription() []string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v KeyValueAssignmentValue) getValue(selectedKey string) Value {
|
||||||
|
switch v.Value.(type) {
|
||||||
|
case CustomValue:
|
||||||
|
{
|
||||||
|
customValue := v.Value.(CustomValue)
|
||||||
|
context := KeyValueAssignmentContext{
|
||||||
|
SelectedKey: selectedKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
return customValue.FetchValue(context)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
return v.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (v KeyValueAssignmentValue) CheckIsValid(value string) error {
|
func (v KeyValueAssignmentValue) CheckIsValid(value string) error {
|
||||||
parts := strings.Split(value, v.Separator)
|
parts := strings.Split(value, v.Separator)
|
||||||
|
|
||||||
@ -60,7 +87,7 @@ func (v KeyValueAssignmentValue) CheckIsValid(value string) error {
|
|||||||
return KeyValueAssignmentError{}
|
return KeyValueAssignmentError{}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = v.Value.CheckIsValid(parts[1])
|
err = v.getValue(parts[0]).CheckIsValid(parts[1])
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -77,10 +104,11 @@ func (v KeyValueAssignmentValue) FetchCompletions(line string, cursor uint32) []
|
|||||||
relativePosition, found := utils.FindPreviousCharacter(line, v.Separator, int(cursor-1))
|
relativePosition, found := utils.FindPreviousCharacter(line, v.Separator, int(cursor-1))
|
||||||
|
|
||||||
if found {
|
if found {
|
||||||
|
selectedKey := line[:uint32(relativePosition)]
|
||||||
line = line[uint32(relativePosition+len(v.Separator)):]
|
line = line[uint32(relativePosition+len(v.Separator)):]
|
||||||
cursor -= uint32(relativePosition)
|
cursor -= uint32(relativePosition)
|
||||||
|
|
||||||
return v.Value.FetchCompletions(line, cursor)
|
return v.getValue(selectedKey).FetchCompletions(line, cursor)
|
||||||
} else {
|
} else {
|
||||||
return v.Key.FetchCompletions(line, cursor)
|
return v.Key.FetchCompletions(line, cursor)
|
||||||
}
|
}
|
||||||
|
50
handlers/fstab/documentation.go
Normal file
50
handlers/fstab/documentation.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package fstab
|
||||||
|
|
||||||
|
import (
|
||||||
|
docvalues "config-lsp/doc-values"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var uuidField = docvalues.RegexValue{
|
||||||
|
Regex: *regexp.MustCompile(`[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}`),
|
||||||
|
}
|
||||||
|
var labelField = docvalues.RegexValue{
|
||||||
|
Regex: *regexp.MustCompile(`\S+`),
|
||||||
|
}
|
||||||
|
|
||||||
|
var specField = docvalues.OrValue{
|
||||||
|
Values: []docvalues.Value{
|
||||||
|
// docvalues.PathValue{
|
||||||
|
// RequiredType: docvalues.PathTypeFile & docvalues.PathTypeExistenceOptional,
|
||||||
|
// },
|
||||||
|
docvalues.KeyValueAssignmentValue{
|
||||||
|
Separator: "=",
|
||||||
|
ValueIsOptional: false,
|
||||||
|
Key: docvalues.EnumValue{
|
||||||
|
EnforceValues: true,
|
||||||
|
Values: []docvalues.EnumString{
|
||||||
|
docvalues.CreateEnumString("UUID"),
|
||||||
|
docvalues.CreateEnumString("LABEL"),
|
||||||
|
docvalues.CreateEnumString("PARTUUID"),
|
||||||
|
docvalues.CreateEnumString("PARTLABEL"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Value: docvalues.CustomValue{
|
||||||
|
FetchValue: func(rawContext docvalues.CustomValueContext) docvalues.Value {
|
||||||
|
context := rawContext.(docvalues.KeyValueAssignmentContext)
|
||||||
|
|
||||||
|
switch context.SelectedKey {
|
||||||
|
case "UUID":
|
||||||
|
case "PARTUUID":
|
||||||
|
return uuidField
|
||||||
|
case "LABEL":
|
||||||
|
case "PARTLABEL":
|
||||||
|
return labelField
|
||||||
|
}
|
||||||
|
|
||||||
|
return docvalues.StringValue{}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
240
handlers/fstab/parser.go
Normal file
240
handlers/fstab/parser.go
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
package fstab
|
||||||
|
|
||||||
|
import (
|
||||||
|
"config-lsp/common"
|
||||||
|
"regexp"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ignoreLinePattern = regexp.MustCompile(`^\s*(#|$)`)
|
||||||
|
var whitespacePattern = regexp.MustCompile(`\s+`)
|
||||||
|
|
||||||
|
type MalformedLineError struct{}
|
||||||
|
|
||||||
|
func (e MalformedLineError) Error() string {
|
||||||
|
return "Malformed line"
|
||||||
|
}
|
||||||
|
|
||||||
|
type Field struct {
|
||||||
|
Value string
|
||||||
|
Start uint32
|
||||||
|
End uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Field) CreateRange(fieldLine uint32) protocol.Range {
|
||||||
|
return protocol.Range{
|
||||||
|
Start: protocol.Position{
|
||||||
|
Line: fieldLine,
|
||||||
|
Character: f.Start,
|
||||||
|
},
|
||||||
|
End: protocol.Position{
|
||||||
|
Line: fieldLine,
|
||||||
|
Character: f.End,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type FstabFields struct {
|
||||||
|
Spec *Field
|
||||||
|
MountPoint *Field
|
||||||
|
FilesystemType *Field
|
||||||
|
Options *Field
|
||||||
|
Freq *Field
|
||||||
|
Pass *Field
|
||||||
|
}
|
||||||
|
|
||||||
|
type FstabEntry struct {
|
||||||
|
Line uint32
|
||||||
|
Fields FstabFields
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *FstabEntry) CheckIsValid() []protocol.Diagnostic {
|
||||||
|
if e.Fields.Spec == nil || e.Fields.MountPoint == nil || e.Fields.FilesystemType == nil || e.Fields.Options == nil || e.Fields.Freq == nil || e.Fields.Pass == nil {
|
||||||
|
severity := protocol.DiagnosticSeverityHint
|
||||||
|
|
||||||
|
return []protocol.Diagnostic{
|
||||||
|
{
|
||||||
|
Message: "This line is not fully filled",
|
||||||
|
Severity: &severity,
|
||||||
|
Range: protocol.Range{
|
||||||
|
Start: protocol.Position{
|
||||||
|
Line: e.Line,
|
||||||
|
Character: 0,
|
||||||
|
},
|
||||||
|
End: protocol.Position{
|
||||||
|
Line: e.Line,
|
||||||
|
Character: 9999,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diagnostics := make([]protocol.Diagnostic, 0)
|
||||||
|
severity := protocol.DiagnosticSeverityError
|
||||||
|
|
||||||
|
err := specField.CheckIsValid(e.Fields.Spec.Value)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
diagnostics = append(diagnostics, protocol.Diagnostic{
|
||||||
|
Range: e.Fields.Spec.CreateRange(e.Line),
|
||||||
|
Message: err.Error(),
|
||||||
|
Severity: &severity,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return diagnostics
|
||||||
|
}
|
||||||
|
|
||||||
|
type FstabParser struct {
|
||||||
|
entries []FstabEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *FstabParser) AddLine(line string, lineNumber int) error {
|
||||||
|
fields := whitespacePattern.Split(line, -1)
|
||||||
|
|
||||||
|
if len(fields) == 0 {
|
||||||
|
return MalformedLineError{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var spec Field
|
||||||
|
var mountPoint Field
|
||||||
|
var filesystemType Field
|
||||||
|
var options Field
|
||||||
|
var freq Field
|
||||||
|
var pass Field
|
||||||
|
|
||||||
|
switch len(fields) {
|
||||||
|
case 6:
|
||||||
|
value := fields[5]
|
||||||
|
start := uint32(strings.Index(line, value))
|
||||||
|
pass = Field{
|
||||||
|
Value: fields[5],
|
||||||
|
Start: start,
|
||||||
|
End: start + uint32(len(value)),
|
||||||
|
}
|
||||||
|
case 5:
|
||||||
|
value := fields[4]
|
||||||
|
start := uint32(strings.Index(line, value))
|
||||||
|
|
||||||
|
freq = Field{
|
||||||
|
Value: value,
|
||||||
|
Start: start,
|
||||||
|
End: start + uint32(len(value)),
|
||||||
|
}
|
||||||
|
case 4:
|
||||||
|
value := fields[3]
|
||||||
|
start := uint32(strings.Index(line, value))
|
||||||
|
|
||||||
|
options = Field{
|
||||||
|
Value: value,
|
||||||
|
Start: start,
|
||||||
|
End: start + uint32(len(value)),
|
||||||
|
}
|
||||||
|
case 3:
|
||||||
|
value := fields[2]
|
||||||
|
start := uint32(strings.Index(line, value))
|
||||||
|
|
||||||
|
filesystemType = Field{
|
||||||
|
Value: value,
|
||||||
|
Start: start,
|
||||||
|
End: start + uint32(len(value)),
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
value := fields[1]
|
||||||
|
start := uint32(strings.Index(line, value))
|
||||||
|
|
||||||
|
mountPoint = Field{
|
||||||
|
Value: value,
|
||||||
|
Start: start,
|
||||||
|
End: start + uint32(len(value)),
|
||||||
|
}
|
||||||
|
case 1:
|
||||||
|
value := fields[0]
|
||||||
|
start := uint32(strings.Index(line, value))
|
||||||
|
|
||||||
|
spec = Field{
|
||||||
|
Value: value,
|
||||||
|
Start: start,
|
||||||
|
End: start + uint32(len(value)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entry := FstabEntry{
|
||||||
|
Line: uint32(lineNumber),
|
||||||
|
Fields: FstabFields{
|
||||||
|
Spec: &spec,
|
||||||
|
MountPoint: &mountPoint,
|
||||||
|
FilesystemType: &filesystemType,
|
||||||
|
Options: &options,
|
||||||
|
Freq: &freq,
|
||||||
|
Pass: &pass,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
p.entries = append(p.entries, entry)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *FstabParser) ParseFromContent(content string) []common.ParseError {
|
||||||
|
errors := []common.ParseError{}
|
||||||
|
lines := strings.Split(content, "\n")
|
||||||
|
|
||||||
|
for index, line := range lines {
|
||||||
|
if ignoreLinePattern.MatchString(line) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err := p.AddLine(line, index)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
errors = append(errors, common.ParseError{
|
||||||
|
Line: uint32(index),
|
||||||
|
Err: err,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *FstabParser) GetEntry(line uint32) (FstabEntry, bool) {
|
||||||
|
index, found := slices.BinarySearchFunc(p.entries, line, func(entry FstabEntry, line uint32) int {
|
||||||
|
if entry.Line < line {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry.Line > line {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return FstabEntry{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.entries[index], true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *FstabParser) Clear() {
|
||||||
|
p.entries = []FstabEntry{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *FstabParser) AnalyzeValues() []protocol.Diagnostic {
|
||||||
|
diagnostics := []protocol.Diagnostic{}
|
||||||
|
|
||||||
|
for _, entry := range p.entries {
|
||||||
|
newDiagnostics := entry.CheckIsValid()
|
||||||
|
|
||||||
|
if len(newDiagnostics) > 0 {
|
||||||
|
diagnostics = append(diagnostics, newDiagnostics...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return diagnostics
|
||||||
|
}
|
7
handlers/fstab/shared.go
Normal file
7
handlers/fstab/shared.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package fstab
|
||||||
|
|
||||||
|
import (
|
||||||
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
|
)
|
||||||
|
|
||||||
|
var documentParserMap = map[protocol.DocumentUri]*FstabParser{}
|
40
handlers/fstab/text-document-did-change.go
Normal file
40
handlers/fstab/text-document-did-change.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package fstab
|
||||||
|
|
||||||
|
import (
|
||||||
|
"config-lsp/common"
|
||||||
|
"config-lsp/utils"
|
||||||
|
|
||||||
|
"github.com/tliron/glsp"
|
||||||
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TextDocumentDidChange(
|
||||||
|
context *glsp.Context,
|
||||||
|
params *protocol.DidChangeTextDocumentParams,
|
||||||
|
) error {
|
||||||
|
content := params.ContentChanges[0].(protocol.TextDocumentContentChangeEventWhole).Text
|
||||||
|
common.ClearDiagnostics(context, params.TextDocument.URI)
|
||||||
|
|
||||||
|
parser := documentParserMap[params.TextDocument.URI]
|
||||||
|
parser.Clear()
|
||||||
|
|
||||||
|
diagnostics := make([]protocol.Diagnostic, 0)
|
||||||
|
errors := parser.ParseFromContent(content)
|
||||||
|
|
||||||
|
if len(errors) > 0 {
|
||||||
|
diagnostics = append(diagnostics, utils.Map(
|
||||||
|
errors,
|
||||||
|
func(err common.ParseError) protocol.Diagnostic {
|
||||||
|
return err.ToDiagnostic()
|
||||||
|
},
|
||||||
|
)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
diagnostics = append(diagnostics, parser.AnalyzeValues()...)
|
||||||
|
|
||||||
|
if len(diagnostics) > 0 {
|
||||||
|
common.SendDiagnostics(context, params.TextDocument.URI, diagnostics)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
32
handlers/fstab/text-document-did-open.go
Normal file
32
handlers/fstab/text-document-did-open.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package fstab
|
||||||
|
|
||||||
|
import (
|
||||||
|
"config-lsp/common"
|
||||||
|
"config-lsp/utils"
|
||||||
|
|
||||||
|
"github.com/tliron/glsp"
|
||||||
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TextDocumentDidOpen(
|
||||||
|
context *glsp.Context,
|
||||||
|
params *protocol.DidOpenTextDocumentParams,
|
||||||
|
) error {
|
||||||
|
common.ClearDiagnostics(context, params.TextDocument.URI)
|
||||||
|
|
||||||
|
parser := FstabParser{}
|
||||||
|
|
||||||
|
documentParserMap[params.TextDocument.URI] = &parser
|
||||||
|
|
||||||
|
errors := parser.ParseFromContent(params.TextDocument.Text)
|
||||||
|
diagnostics := utils.Map(
|
||||||
|
errors,
|
||||||
|
func(err common.ParseError) protocol.Diagnostic {
|
||||||
|
return err.ToDiagnostic()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
common.SendDiagnostics(context, params.TextDocument.URI, diagnostics)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -826,7 +826,7 @@ See PATTERNS in ssh_config(5) for more information on patterns. This keyword may
|
|||||||
Alternately the name internal-sftp implements an in- process SFTP server. This may simplify configurations using ChrootDirectory to force a different filesystem root on clients. It accepts the same command line arguments as sftp-server and even though it is in- process, settings such as LogLevel or SyslogFacility do not apply to it and must be set explicitly via command line arguments.
|
Alternately the name internal-sftp implements an in- process SFTP server. This may simplify configurations using ChrootDirectory to force a different filesystem root on clients. It accepts the same command line arguments as sftp-server and even though it is in- process, settings such as LogLevel or SyslogFacility do not apply to it and must be set explicitly via command line arguments.
|
||||||
By default no subsystems are defined.`,
|
By default no subsystems are defined.`,
|
||||||
docvalues.StringValue{},
|
docvalues.StringValue{},
|
||||||
),
|
),
|
||||||
"SyslogFacility": common.NewOption(`Gives the facility code that is used when logging messages from sshd(8). The possible values are: DAEMON, USER, AUTH, LOCAL0, LOCAL1, LOCAL2, LOCAL3, LOCAL4, LOCAL5, LOCAL6, LOCAL7. The default is AUTH.`,
|
"SyslogFacility": common.NewOption(`Gives the facility code that is used when logging messages from sshd(8). The possible values are: DAEMON, USER, AUTH, LOCAL0, LOCAL1, LOCAL2, LOCAL3, LOCAL4, LOCAL5, LOCAL6, LOCAL7. The default is AUTH.`,
|
||||||
docvalues.EnumValue{
|
docvalues.EnumValue{
|
||||||
EnforceValues: true,
|
EnforceValues: true,
|
||||||
|
@ -11,6 +11,8 @@ import (
|
|||||||
func TextDocumentDidOpen(context *glsp.Context, params *protocol.DidOpenTextDocumentParams) error {
|
func TextDocumentDidOpen(context *glsp.Context, params *protocol.DidOpenTextDocumentParams) error {
|
||||||
readBytes, err := os.ReadFile(params.TextDocument.URI[len("file://"):])
|
readBytes, err := os.ReadFile(params.TextDocument.URI[len("file://"):])
|
||||||
|
|
||||||
|
println("opened i la language eta", params.TextDocument.LanguageID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
53
main.go
53
main.go
@ -1,67 +1,18 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
openssh "config-lsp/handlers/openssh"
|
roothandler "config-lsp/root-handler"
|
||||||
|
|
||||||
"github.com/tliron/commonlog"
|
"github.com/tliron/commonlog"
|
||||||
"github.com/tliron/glsp"
|
|
||||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
|
||||||
"github.com/tliron/glsp/server"
|
|
||||||
|
|
||||||
// Must include a backend implementation
|
// Must include a backend implementation
|
||||||
// See CommonLog for other options: https://github.com/tliron/commonlog
|
// See CommonLog for other options: https://github.com/tliron/commonlog
|
||||||
_ "github.com/tliron/commonlog/simple"
|
_ "github.com/tliron/commonlog/simple"
|
||||||
)
|
)
|
||||||
|
|
||||||
const lsName = "config-lsp"
|
|
||||||
|
|
||||||
var (
|
|
||||||
version string = "0.0.1"
|
|
||||||
handler protocol.Handler
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// This increases logging verbosity (optional)
|
// This increases logging verbosity (optional)
|
||||||
commonlog.Configure(1, nil)
|
commonlog.Configure(1, nil)
|
||||||
|
|
||||||
handler = protocol.Handler{
|
roothandler.SetUpRootHandler()
|
||||||
Initialize: initialize,
|
|
||||||
Initialized: initialized,
|
|
||||||
Shutdown: shutdown,
|
|
||||||
SetTrace: setTrace,
|
|
||||||
TextDocumentDidOpen: openssh.TextDocumentDidOpen,
|
|
||||||
TextDocumentDidChange: openssh.TextDocumentDidChange,
|
|
||||||
TextDocumentCompletion: openssh.TextDocumentCompletion,
|
|
||||||
}
|
|
||||||
|
|
||||||
server := server.NewServer(&handler, lsName, false)
|
|
||||||
|
|
||||||
server.RunStdio()
|
|
||||||
}
|
|
||||||
|
|
||||||
func initialize(context *glsp.Context, params *protocol.InitializeParams) (any, error) {
|
|
||||||
capabilities := handler.CreateServerCapabilities()
|
|
||||||
capabilities.TextDocumentSync = protocol.TextDocumentSyncKindFull
|
|
||||||
|
|
||||||
return protocol.InitializeResult{
|
|
||||||
Capabilities: capabilities,
|
|
||||||
ServerInfo: &protocol.InitializeResultServerInfo{
|
|
||||||
Name: lsName,
|
|
||||||
Version: &version,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func initialized(context *glsp.Context, params *protocol.InitializedParams) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func shutdown(context *glsp.Context) error {
|
|
||||||
protocol.SetTraceValue(protocol.TraceValueOff)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setTrace(context *glsp.Context, params *protocol.SetTraceParams) error {
|
|
||||||
protocol.SetTraceValue(params.Value)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
58
root-handler/handler.go
Normal file
58
root-handler/handler.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package roothandler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tliron/glsp"
|
||||||
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
|
|
||||||
|
"github.com/tliron/glsp/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
const lsName = "config-lsp"
|
||||||
|
|
||||||
|
var version string = "0.0.1"
|
||||||
|
|
||||||
|
var lspHandler protocol.Handler
|
||||||
|
|
||||||
|
// The root handler which handles all the LSP requests and then forwards them to the appropriate handler
|
||||||
|
func SetUpRootHandler() {
|
||||||
|
rootHandler = NewRootHandler()
|
||||||
|
lspHandler = protocol.Handler{
|
||||||
|
Initialize: initialize,
|
||||||
|
Initialized: initialized,
|
||||||
|
Shutdown: shutdown,
|
||||||
|
SetTrace: setTrace,
|
||||||
|
TextDocumentDidOpen: TextDocumentDidOpen,
|
||||||
|
TextDocumentDidChange: TextDocumentDidChange,
|
||||||
|
}
|
||||||
|
|
||||||
|
server := server.NewServer(&lspHandler, lsName, false)
|
||||||
|
|
||||||
|
server.RunStdio()
|
||||||
|
}
|
||||||
|
|
||||||
|
func initialize(context *glsp.Context, params *protocol.InitializeParams) (any, error) {
|
||||||
|
capabilities := lspHandler.CreateServerCapabilities()
|
||||||
|
capabilities.TextDocumentSync = protocol.TextDocumentSyncKindFull
|
||||||
|
|
||||||
|
return protocol.InitializeResult{
|
||||||
|
Capabilities: capabilities,
|
||||||
|
ServerInfo: &protocol.InitializeResultServerInfo{
|
||||||
|
Name: lsName,
|
||||||
|
Version: &version,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func initialized(context *glsp.Context, params *protocol.InitializedParams) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func shutdown(context *glsp.Context) error {
|
||||||
|
protocol.SetTraceValue(protocol.TraceValueOff)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setTrace(context *glsp.Context, params *protocol.SetTraceParams) error {
|
||||||
|
protocol.SetTraceValue(params.Value)
|
||||||
|
return nil
|
||||||
|
}
|
101
root-handler/lsp-utils.go
Normal file
101
root-handler/lsp-utils.go
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package roothandler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"config-lsp/common"
|
||||||
|
"config-lsp/utils"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SupportedLanguage string
|
||||||
|
|
||||||
|
const (
|
||||||
|
LanguageSSHDConfig SupportedLanguage = "sshd_config"
|
||||||
|
LanguageFstab SupportedLanguage = "fstab"
|
||||||
|
)
|
||||||
|
|
||||||
|
var AllSupportedLanguages = []string{
|
||||||
|
string(LanguageSSHDConfig),
|
||||||
|
string(LanguageFstab),
|
||||||
|
}
|
||||||
|
|
||||||
|
type FatalFileNotReadableError struct {
|
||||||
|
FileURI protocol.DocumentUri
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e FatalFileNotReadableError) Error() string {
|
||||||
|
return fmt.Sprint("Fatal error! config-lsp was unable to read the file (%s); error: %s", e.FileURI, e.Err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
type UnsupportedLanguageError struct {
|
||||||
|
SuggestedLanguage string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e UnsupportedLanguageError) Error() string {
|
||||||
|
return fmt.Sprint("Language '%s' is not supported. Choose one of: %s", e.SuggestedLanguage, strings.Join(AllSupportedLanguages, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
type LanguageUndetectableError struct{}
|
||||||
|
|
||||||
|
func (e LanguageUndetectableError) Error() string {
|
||||||
|
return "Please add: '#?lsp.language=<language>' to the top of the file. config-lsp was unable to detect the appropriate language for this file."
|
||||||
|
}
|
||||||
|
|
||||||
|
var valueToLanguageMap = map[string]SupportedLanguage{
|
||||||
|
"sshd_config": LanguageSSHDConfig,
|
||||||
|
"sshdconfig": LanguageSSHDConfig,
|
||||||
|
"ssh_config": LanguageSSHDConfig,
|
||||||
|
"sshconfig": LanguageSSHDConfig,
|
||||||
|
|
||||||
|
"fstab": LanguageFstab,
|
||||||
|
"etc/fstab": LanguageFstab,
|
||||||
|
}
|
||||||
|
|
||||||
|
var typeOverwriteRegex = regexp.MustCompile(`^#\?\s*lsp\.language\s*=\s*(\w+)\s*$`)
|
||||||
|
|
||||||
|
func DetectLanguage(
|
||||||
|
content string,
|
||||||
|
advertisedLanguage string,
|
||||||
|
uri protocol.DocumentUri,
|
||||||
|
) (SupportedLanguage, error) {
|
||||||
|
if match := typeOverwriteRegex.FindStringSubmatch(content); match != nil {
|
||||||
|
suggestedLanguage := strings.ToLower(match[1])
|
||||||
|
|
||||||
|
foundLanguage, ok := valueToLanguageMap[suggestedLanguage]
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
return foundLanguage, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
matchIndex := strings.Index(content, match[0])
|
||||||
|
contentUntilMatch := content[:matchIndex]
|
||||||
|
|
||||||
|
return "", common.ParseError{
|
||||||
|
Line: uint32(utils.CountCharacterOccurrences(contentUntilMatch, '\n')),
|
||||||
|
Err: UnsupportedLanguageError{
|
||||||
|
SuggestedLanguage: suggestedLanguage,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if language, ok := valueToLanguageMap[advertisedLanguage]; ok {
|
||||||
|
return language, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch uri {
|
||||||
|
case "file:///etc/ssh/sshd_config":
|
||||||
|
case "file:///etc/ssh/ssh_config":
|
||||||
|
return LanguageSSHDConfig, nil
|
||||||
|
case "file:///etc/fstab":
|
||||||
|
return LanguageFstab, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", common.ParseError{
|
||||||
|
Line: 0,
|
||||||
|
Err: LanguageUndetectableError{},
|
||||||
|
}
|
||||||
|
}
|
29
root-handler/singleton.go
Normal file
29
root-handler/singleton.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package roothandler
|
||||||
|
|
||||||
|
import (
|
||||||
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
|
)
|
||||||
|
|
||||||
|
var rootHandler RootHandler
|
||||||
|
|
||||||
|
type RootHandler struct {
|
||||||
|
languageMap map[protocol.DocumentUri]SupportedLanguage
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRootHandler() RootHandler {
|
||||||
|
return RootHandler{
|
||||||
|
languageMap: make(map[protocol.DocumentUri]SupportedLanguage),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *RootHandler) AddDocument(uri protocol.DocumentUri, language SupportedLanguage) {
|
||||||
|
h.languageMap[uri] = language
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *RootHandler) GetLanguageForDocument(uri protocol.DocumentUri) SupportedLanguage {
|
||||||
|
return h.languageMap[uri]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *RootHandler) RemoveDocument(uri protocol.DocumentUri) {
|
||||||
|
delete(h.languageMap, uri)
|
||||||
|
}
|
21
root-handler/text-document-did-change.go
Normal file
21
root-handler/text-document-did-change.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package roothandler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"config-lsp/handlers/fstab"
|
||||||
|
|
||||||
|
"github.com/tliron/glsp"
|
||||||
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TextDocumentDidChange(context *glsp.Context, params *protocol.DidChangeTextDocumentParams) error {
|
||||||
|
language := rootHandler.GetLanguageForDocument(params.TextDocument.URI)
|
||||||
|
|
||||||
|
switch language {
|
||||||
|
case LanguageFstab:
|
||||||
|
return fstab.TextDocumentDidChange(context, params)
|
||||||
|
case LanguageSSHDConfig:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
panic("root-handler/TextDocumentDidChange: unexpected language" + language)
|
||||||
|
}
|
71
root-handler/text-document-did-open.go
Normal file
71
root-handler/text-document-did-open.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package roothandler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"config-lsp/common"
|
||||||
|
fstab "config-lsp/handlers/fstab"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/tliron/glsp"
|
||||||
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TextDocumentDidOpen(context *glsp.Context, params *protocol.DidOpenTextDocumentParams) error {
|
||||||
|
common.ClearDiagnostics(context, params.TextDocument.URI)
|
||||||
|
|
||||||
|
// Find the file type
|
||||||
|
content := params.TextDocument.Text
|
||||||
|
language, err := DetectLanguage(content, params.TextDocument.LanguageID, params.TextDocument.URI)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
parseError := err.(common.ParseError)
|
||||||
|
showParseError(
|
||||||
|
context,
|
||||||
|
params.TextDocument.URI,
|
||||||
|
parseError,
|
||||||
|
)
|
||||||
|
|
||||||
|
return parseError.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Everything okay, now we can handle the file
|
||||||
|
rootHandler.AddDocument(params.TextDocument.URI, language)
|
||||||
|
|
||||||
|
switch language {
|
||||||
|
case LanguageFstab:
|
||||||
|
return fstab.TextDocumentDidOpen(context, params)
|
||||||
|
case LanguageSSHDConfig:
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unexpected roothandler.SupportedLanguage: %#v", language))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func showParseError(
|
||||||
|
context *glsp.Context,
|
||||||
|
uri protocol.DocumentUri,
|
||||||
|
err common.ParseError,
|
||||||
|
) {
|
||||||
|
severity := protocol.DiagnosticSeverityError
|
||||||
|
|
||||||
|
common.SendDiagnostics(
|
||||||
|
context,
|
||||||
|
uri,
|
||||||
|
[]protocol.Diagnostic{
|
||||||
|
{
|
||||||
|
Severity: &severity,
|
||||||
|
Message: err.Err.Error(),
|
||||||
|
Range: protocol.Range{
|
||||||
|
Start: protocol.Position{
|
||||||
|
Line: err.Line,
|
||||||
|
Character: 0,
|
||||||
|
},
|
||||||
|
End: protocol.Position{
|
||||||
|
Line: err.Line,
|
||||||
|
Character: 99999,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
13
utils/text.go
Normal file
13
utils/text.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
func CountCharacterOccurrences(line string, character rune) int {
|
||||||
|
count := 0
|
||||||
|
|
||||||
|
for _, c := range line {
|
||||||
|
if c == character {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user