mirror of
https://github.com/Myzel394/config-lsp.git
synced 2025-06-18 23:15:26 +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 (
|
||||
docvalues "config-lsp/doc-values"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
"regexp"
|
||||
"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 {
|
||||
Line uint32
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ func fetchPasswdInfo() ([]passwdInfo, error) {
|
||||
// if `separatorForMultiple` is not empty, it will return an ArrayValue
|
||||
func UserValue(separatorForMultiple string, enforceValues bool) Value {
|
||||
return CustomValue{
|
||||
FetchValue: func() Value {
|
||||
FetchValue: func(context CustomValueContext) Value {
|
||||
infos, err := fetchPasswdInfo()
|
||||
|
||||
if err != nil {
|
||||
@ -125,7 +125,7 @@ func fetchGroupInfo() ([]groupInfo, error) {
|
||||
|
||||
func GroupValue(separatorForMultiple string, enforceValues bool) Value {
|
||||
return CustomValue{
|
||||
FetchValue: func() Value {
|
||||
FetchValue: func(context CustomValueContext) Value {
|
||||
infos, err := fetchGroupInfo()
|
||||
|
||||
if err != nil {
|
||||
|
@ -4,8 +4,20 @@ import (
|
||||
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 {
|
||||
FetchValue func() Value
|
||||
FetchValue func(context CustomValueContext) Value
|
||||
}
|
||||
|
||||
func (v CustomValue) GetTypeDescription() []string {
|
||||
@ -13,9 +25,9 @@ func (v CustomValue) GetTypeDescription() []string {
|
||||
}
|
||||
|
||||
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 {
|
||||
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"
|
||||
}
|
||||
|
||||
type KeyValueAssignmentContext struct {
|
||||
SelectedKey string
|
||||
}
|
||||
|
||||
func (KeyValueAssignmentContext) GetIsContext() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type KeyValueAssignmentValue struct {
|
||||
Key Value
|
||||
// If this is a `CustomValue`, it will receive a `KeyValueAssignmentContext`
|
||||
Value Value
|
||||
ValueIsOptional bool
|
||||
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 {
|
||||
parts := strings.Split(value, v.Separator)
|
||||
|
||||
@ -60,7 +87,7 @@ func (v KeyValueAssignmentValue) CheckIsValid(value string) error {
|
||||
return KeyValueAssignmentError{}
|
||||
}
|
||||
|
||||
err = v.Value.CheckIsValid(parts[1])
|
||||
err = v.getValue(parts[0]).CheckIsValid(parts[1])
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
@ -77,10 +104,11 @@ func (v KeyValueAssignmentValue) FetchCompletions(line string, cursor uint32) []
|
||||
relativePosition, found := utils.FindPreviousCharacter(line, v.Separator, int(cursor-1))
|
||||
|
||||
if found {
|
||||
selectedKey := line[:uint32(relativePosition)]
|
||||
line = line[uint32(relativePosition+len(v.Separator)):]
|
||||
cursor -= uint32(relativePosition)
|
||||
|
||||
return v.Value.FetchCompletions(line, cursor)
|
||||
return v.getValue(selectedKey).FetchCompletions(line, cursor)
|
||||
} else {
|
||||
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
|
||||
}
|
@ -11,6 +11,8 @@ import (
|
||||
func TextDocumentDidOpen(context *glsp.Context, params *protocol.DidOpenTextDocumentParams) error {
|
||||
readBytes, err := os.ReadFile(params.TextDocument.URI[len("file://"):])
|
||||
|
||||
println("opened i la language eta", params.TextDocument.LanguageID)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
53
main.go
53
main.go
@ -1,67 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
openssh "config-lsp/handlers/openssh"
|
||||
roothandler "config-lsp/root-handler"
|
||||
|
||||
"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
|
||||
// See CommonLog for other options: https://github.com/tliron/commonlog
|
||||
_ "github.com/tliron/commonlog/simple"
|
||||
)
|
||||
|
||||
const lsName = "config-lsp"
|
||||
|
||||
var (
|
||||
version string = "0.0.1"
|
||||
handler protocol.Handler
|
||||
)
|
||||
|
||||
func main() {
|
||||
// This increases logging verbosity (optional)
|
||||
commonlog.Configure(1, nil)
|
||||
|
||||
handler = protocol.Handler{
|
||||
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
|
||||
roothandler.SetUpRootHandler()
|
||||
}
|
||||
|
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