feat: Adding fstab; Adding root handler; Improvements

This commit is contained in:
Myzel394 2024-08-04 14:59:12 +02:00
parent a6a4c04af3
commit 49a12c9080
No known key found for this signature in database
GPG Key ID: DEC4AAB876F73185
19 changed files with 766 additions and 70 deletions

9
common/PARSER.md Normal file
View 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

View File

@ -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
} }

View File

@ -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 {

View File

@ -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)
} }

View File

@ -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)
} }

View 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
View 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
View File

@ -0,0 +1,7 @@
package fstab
import (
protocol "github.com/tliron/glsp/protocol_3_16"
)
var documentParserMap = map[protocol.DocumentUri]*FstabParser{}

View 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
}

View 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
}

View File

@ -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,

View File

@ -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
View File

@ -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
View 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
View 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
View 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)
}

View 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)
}

View 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
View 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
}