diff --git a/server/common/options.go b/server/common/options.go index 8dc762d..3ac4edb 100644 --- a/server/common/options.go +++ b/server/common/options.go @@ -27,6 +27,9 @@ type ServerOptionsType struct { var ServerOptions = new(ServerOptionsType) func InitServerOptions() { + ServerOptions.NoUndetectableErrors = false + ServerOptions.NoTypoSuggestions = false + if slices.Contains(os.Args, "--no-undetectable-errors") { Log.Info("config-lsp will not return errors for undetectable files") ServerOptions.NoUndetectableErrors = true diff --git a/server/go.mod b/server/go.mod index 886d90f..45a560f 100644 --- a/server/go.mod +++ b/server/go.mod @@ -15,6 +15,7 @@ require ( require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/gorilla/websocket v1.5.3 // indirect + github.com/hbollon/go-edlib v1.6.0 // indirect github.com/iancoleman/strcase v0.3.0 // indirect github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect diff --git a/server/go.sum b/server/go.sum index a99cb8e..b8344b8 100644 --- a/server/go.sum +++ b/server/go.sum @@ -9,6 +9,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hbollon/go-edlib v1.6.0 h1:ga7AwwVIvP8mHm9GsPueC0d71cfRU/52hmPJ7Tprv4E= +github.com/hbollon/go-edlib v1.6.0/go.mod h1:wnt6o6EIVEzUfgbUZY7BerzQ2uvzp354qmS2xaLkrhM= github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM= diff --git a/server/handlers/ssh_config/analyzer/values.go b/server/handlers/ssh_config/analyzer/values.go index 49b7e1e..a339487 100644 --- a/server/handlers/ssh_config/analyzer/values.go +++ b/server/handlers/ssh_config/analyzer/values.go @@ -29,8 +29,6 @@ func analyzeValuesAreValid( ), ) ctx.document.Indexes.UnknownOptions[info.Option.Start.Line] = info - - continue } } } diff --git a/server/handlers/ssh_config/handlers/code-action-typos.go b/server/handlers/ssh_config/handlers/code-action-typos.go new file mode 100644 index 0000000..cbf4c3f --- /dev/null +++ b/server/handlers/ssh_config/handlers/code-action-typos.go @@ -0,0 +1,103 @@ +package handlers + +import ( + "config-lsp/common" + sshconfig "config-lsp/handlers/ssh_config" + "config-lsp/handlers/ssh_config/diagnostics" + "config-lsp/handlers/ssh_config/fields" + "fmt" + + "github.com/hbollon/go-edlib" + protocol "github.com/tliron/glsp/protocol_3_16" +) + +func getKeywordTypoFixes( + d *sshconfig.SSHDocument, + params *protocol.CodeActionParams, +) []protocol.CodeAction { + if common.ServerOptions.NoTypoSuggestions { + return nil + } + + line := params.Range.Start.Line + + if typoOption, found := d.Indexes.UnknownOptions[line]; found { + name := typoOption.Option.Key.Value.Value + + suggestedOptions := findSimilarOptions(name) + + actions := make([]protocol.CodeAction, 0, len(suggestedOptions)) + + kind := protocol.CodeActionKindQuickFix + for index, normalizedOptionName := range suggestedOptions { + isPreferred := index == 0 + optionName := fields.FieldsNameFormattedMap[normalizedOptionName] + + actions = append(actions, protocol.CodeAction{ + Title: fmt.Sprintf("Typo Fix: %s", optionName), + IsPreferred: &isPreferred, + Kind: &kind, + Diagnostics: []protocol.Diagnostic{ + diagnostics.GenerateUnknownOption( + typoOption.Option.Key.ToLSPRange(), + typoOption.Option.Key.Value.Value, + ), + }, + Edit: &protocol.WorkspaceEdit{ + Changes: map[protocol.DocumentUri][]protocol.TextEdit{ + params.TextDocument.URI: { + { + Range: typoOption.Option.Key.ToLSPRange(), + NewText: optionName, + }, + }, + }, + }, + }) + } + + return actions + } + + return nil +} + +// Find options that are similar to the given option name. +// This is used to find typos & suggest the correct option name. +// Once an option is found that has a Damerau-Levenshtein distance of 1, it is immediately returned. +// If not, then the next 2 options of similarity 2, or 3 options of similarity 3 are returned. +// If no options with similarity <= 3 are found, then an empty slice is returned. +func findSimilarOptions( + optionName string, +) []fields.NormalizedOptionName { + normalizedOptionName := string(fields.CreateNormalizedName(optionName)) + + optionsPerSimilarity := map[uint8][]fields.NormalizedOptionName{ + 2: make([]fields.NormalizedOptionName, 0, 2), + 3: make([]fields.NormalizedOptionName, 0, 3), + } + + for name := range fields.Options { + normalizedName := string(name) + similarity := edlib.DamerauLevenshteinDistance(normalizedName, normalizedOptionName) + + switch similarity { + case 1: + return []fields.NormalizedOptionName{name} + case 2: + optionsPerSimilarity[2] = append(optionsPerSimilarity[2], name) + + if len(optionsPerSimilarity[2]) >= 2 { + return optionsPerSimilarity[2] + } + case 3: + optionsPerSimilarity[3] = append(optionsPerSimilarity[3], name) + + if len(optionsPerSimilarity[3]) >= 3 { + return optionsPerSimilarity[3] + } + } + } + + return append(optionsPerSimilarity[2], optionsPerSimilarity[3]...) +} diff --git a/server/handlers/ssh_config/handlers/fetch-code-actions.go b/server/handlers/ssh_config/handlers/fetch-code-actions.go index 1adb69d..c27acf6 100644 --- a/server/handlers/ssh_config/handlers/fetch-code-actions.go +++ b/server/handlers/ssh_config/handlers/fetch-code-actions.go @@ -12,12 +12,22 @@ func FetchCodeActions( d *sshconfig.SSHDocument, params *protocol.CodeActionParams, ) []protocol.CodeAction { - line := params.Range.Start.Line - if d.Indexes == nil { return nil } + actions := getAddToUnknownCodeAction(d, params) + actions = append(actions, getKeywordTypoFixes(d, params)...) + + return actions +} + +func getAddToUnknownCodeAction( + d *sshconfig.SSHDocument, + params *protocol.CodeActionParams, +) []protocol.CodeAction { + line := params.Range.Start.Line + if unknownOption, found := d.Indexes.UnknownOptions[line]; found { var blockLine *uint32 @@ -39,7 +49,7 @@ func FetchCodeActions( }, } kind := protocol.CodeActionKindQuickFix - codeAction := &protocol.CodeAction{ + codeAction := protocol.CodeAction{ Title: fmt.Sprintf("Add %s to unknown options", unknownOption.Option.Key.Key), Command: &command, Kind: &kind, @@ -52,7 +62,7 @@ func FetchCodeActions( } return []protocol.CodeAction{ - *codeAction, + codeAction, } } diff --git a/server/root-handler/lsp/text-document-did-open.go b/server/root-handler/lsp/text-document-did-open.go index e1ddd28..842d136 100644 --- a/server/root-handler/lsp/text-document-did-open.go +++ b/server/root-handler/lsp/text-document-did-open.go @@ -61,9 +61,6 @@ func initFile( uri protocol.DocumentUri, advertisedLanguage string, ) (*shared.SupportedLanguage, error) { - println("Initializing the file") - println(advertisedLanguage) - println(uri) language, err := utils.DetectLanguage(content, advertisedLanguage, uri) if err != nil {