package openssh import ( "config-lsp/common" "errors" "unicode/utf8" "github.com/tliron/glsp" protocol "github.com/tliron/glsp/protocol_3_16" "golang.org/x/exp/maps" _ "github.com/tliron/commonlog/simple" ) func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionParams) (interface{}, error) { optionName, line, err := Parser.FindByLineNumber(uint32(params.Position.Line)) if err == nil { if line.IsCursorAtRootOption(int(params.Position.Character)) { return getRootCompletions(), nil } else { rawLine, cursor := common.OffsetLineAtLeft( uint32(utf8.RuneCountInString(optionName + " ")), line.Value, params.Position.Character, ) return getOptionCompletions(optionName, rawLine, cursor), nil } } else if errors.Is(err, common.LineNotFoundError{}) { return getRootCompletions(), nil } return nil, err } func getRootCompletions() []protocol.CompletionItem { completions := make([]protocol.CompletionItem, len(Options)) optionsKey := maps.Keys(Options) for index := 0; index < len(maps.Keys(Options)); index++ { label := optionsKey[index] option := Options[label] insertText := label + " " + "${1:value}" format := protocol.InsertTextFormatSnippet kind := protocol.CompletionItemKindField completions[index] = protocol.CompletionItem{ Label: label, Documentation: common.GetDocumentation(&option), InsertText: &insertText, InsertTextFormat: &format, Kind: &kind, } } return completions } func getCompletionsFromValue(requiredValue common.Value, line string, cursor uint32) []protocol.CompletionItem { switch requiredValue.(type) { case common.EnumValue: enumValue := requiredValue.(common.EnumValue) completions := make([]protocol.CompletionItem, len(requiredValue.(common.EnumValue).Values)) for index, value := range enumValue.Values { textFormat := protocol.InsertTextFormatPlainText kind := protocol.CompletionItemKindEnum completions[index] = protocol.CompletionItem{ Label: value, InsertTextFormat: &textFormat, Kind: &kind, } } return completions case common.CustomValue: customValue := requiredValue.(common.CustomValue) val := customValue.FetchValue() return getCompletionsFromValue(val, line, cursor) case common.ArrayValue: arrayValue := requiredValue.(common.ArrayValue) relativePosition, found := common.FindPreviousCharacter(line, arrayValue.Separator) if found { line, cursor = common.OffsetLineAtLeft(relativePosition, line, cursor) } return getCompletionsFromValue(arrayValue.SubValue, line, cursor) case common.OrValue: orValue := requiredValue.(common.OrValue) completions := make([]protocol.CompletionItem, 0) for _, subValue := range orValue.Values { completions = append(completions, getCompletionsFromValue(subValue, line, cursor)...) } return completions case common.PrefixWithMeaningValue: prefixWithMeaningValue := requiredValue.(common.PrefixWithMeaningValue) return getCompletionsFromValue(prefixWithMeaningValue.SubValue, line, cursor) case common.KeyValueAssignmentValue: keyValueAssignmentValue := requiredValue.(common.KeyValueAssignmentValue) relativePosition, found := common.FindPreviousCharacter(line, keyValueAssignmentValue.Separator) if found { line, cursor = common.OffsetLineAtLeft(relativePosition, line, cursor) return getCompletionsFromValue(keyValueAssignmentValue.Value, line, cursor) } else { return getCompletionsFromValue(keyValueAssignmentValue.Key, line, cursor) } } return []protocol.CompletionItem{} } func getOptionCompletions(optionName string, line string, cursor uint32) []protocol.CompletionItem { option := Options[optionName] completions := getCompletionsFromValue(option.Value, line, cursor) return completions }