diff --git a/server/handlers/wireguard/analyzer/structure.go b/server/handlers/wireguard/analyzer/structure.go index 58a9c9e..b399312 100644 --- a/server/handlers/wireguard/analyzer/structure.go +++ b/server/handlers/wireguard/analyzer/structure.go @@ -55,7 +55,7 @@ func analyzeStructureIsValid(ctx *analyzerContext) { if property.Value == nil || property.Value.Value == "" { ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ Message: "This property is missing a value", - Range: property.Value.ToLSPRange(), + Range: property.ToLSPRange(), Severity: &common.SeverityError, }) } diff --git a/server/handlers/wireguard/ast/wireguard_fields.go b/server/handlers/wireguard/ast/wireguard_fields.go index 68d9694..053283d 100644 --- a/server/handlers/wireguard/ast/wireguard_fields.go +++ b/server/handlers/wireguard/ast/wireguard_fields.go @@ -1,6 +1,7 @@ package ast import ( + "config-lsp/utils" "slices" ) @@ -47,3 +48,23 @@ func (s *WGSection) FindFirstPropertyByName(name string) *WGProperty { return nil } + +func (s *WGSection) FindPropertyByName(name string) *WGProperty { + for _, property := range s.Properties { + if property.Key.Name == name { + return property + } + } + + return nil +} + +func (s *WGSection) GetLastProperty() *WGProperty { + if len(s.Properties) == 0 { + return nil + } + + lastLine := utils.FindBiggestKey(s.Properties) + + return s.Properties[lastLine] +} diff --git a/server/handlers/wireguard/handlers/code-actions.go b/server/handlers/wireguard/handlers/code-actions.go index 32dbf57..bbdad88 100644 --- a/server/handlers/wireguard/handlers/code-actions.go +++ b/server/handlers/wireguard/handlers/code-actions.go @@ -1,9 +1,10 @@ package handlers -/* import ( + "config-lsp/handlers/wireguard" "config-lsp/handlers/wireguard/ast" wgcommands "config-lsp/handlers/wireguard/commands" + protocol "github.com/tliron/glsp/protocol_3_16" ) @@ -12,7 +13,6 @@ type CodeActionName string const ( CodeActionGeneratePrivateKey CodeActionName = "generatePrivateKey" CodeActionGeneratePresharedKey CodeActionName = "generatePresharedKey" - CodeActionAddKeepalive CodeActionName = "addKeepalive" ) type CodeAction interface { @@ -33,14 +33,15 @@ func CodeActionGeneratePrivateKeyArgsFromArguments(arguments map[string]any) Cod } } -func (args CodeActionGeneratePrivateKeyArgs) RunCommand(p *ast.WGConfig) (*protocol.ApplyWorkspaceEditParams, error) { +func (args CodeActionGeneratePrivateKeyArgs) RunCommand(d *wireguard.WGDocument) (*protocol.ApplyWorkspaceEditParams, error) { privateKey, err := wgcommands.CreateNewPrivateKey() if err != nil { return &protocol.ApplyWorkspaceEditParams{}, err } - section, property := p.GetPropertyByLine(args.Line) + section := d.Config.FindSectionByLine(args.Line) + property := d.Config.FindPropertyByLine(args.Line) if section == nil || property == nil { return nil, nil @@ -54,7 +55,16 @@ func (args CodeActionGeneratePrivateKeyArgs) RunCommand(p *ast.WGConfig) (*proto args.URI: { { NewText: " " + privateKey, - Range: property.GetInsertRange(args.Line), + Range: protocol.Range{ + Start: protocol.Position{ + Line: property.End.Line, + Character: property.End.Character, + }, + End: protocol.Position{ + Line: property.End.Line, + Character: property.End.Character, + }, + }, }, }, }, @@ -74,14 +84,15 @@ func CodeActionGeneratePresharedKeyArgsFromArguments(arguments map[string]any) C } } -func (args CodeActionGeneratePresharedKeyArgs) RunCommand(p *ast.WGConfig) (*protocol.ApplyWorkspaceEditParams, error) { +func (args CodeActionGeneratePresharedKeyArgs) RunCommand(d *wireguard.WGDocument) (*protocol.ApplyWorkspaceEditParams, error) { presharedKey, err := wgcommands.CreatePresharedKey() if err != nil { return &protocol.ApplyWorkspaceEditParams{}, err } - section, property := p.GetPropertyByLine(args.Line) + section := d.Config.FindSectionByLine(args.Line) + property := d.Config.FindPropertyByLine(args.Line) if section == nil || property == nil { return nil, nil @@ -95,45 +106,14 @@ func (args CodeActionGeneratePresharedKeyArgs) RunCommand(p *ast.WGConfig) (*pro args.URI: { { NewText: " " + presharedKey, - Range: property.GetInsertRange(args.Line), - }, - }, - }, - }, - }, nil -} - -type CodeActionAddKeepaliveArgs struct { - URI protocol.DocumentUri - SectionIndex uint32 -} - -func CodeActionAddKeepaliveArgsFromArguments(arguments map[string]any) CodeActionAddKeepaliveArgs { - return CodeActionAddKeepaliveArgs{ - URI: arguments["URI"].(protocol.DocumentUri), - SectionIndex: uint32(arguments["SectionIndex"].(float64)), - } -} - -func (args CodeActionAddKeepaliveArgs) RunCommand(p *ast.WGConfig) (*protocol.ApplyWorkspaceEditParams, error) { - section := p.Sections[args.SectionIndex] - - label := "Add PersistentKeepalive" - return &protocol.ApplyWorkspaceEditParams{ - Label: &label, - Edit: protocol.WorkspaceEdit{ - Changes: map[protocol.DocumentUri][]protocol.TextEdit{ - args.URI: { - { - NewText: "PersistentKeepalive = 25\n", Range: protocol.Range{ Start: protocol.Position{ - Line: section.EndLine + 1, - Character: 0, + Line: property.End.Line, + Character: property.End.Character, }, End: protocol.Position{ - Line: section.EndLine + 1, - Character: 0, + Line: property.End.Line, + Character: property.End.Character, }, }, }, @@ -142,4 +122,4 @@ func (args CodeActionAddKeepaliveArgs) RunCommand(p *ast.WGConfig) (*protocol.Ap }, }, nil } -*/ + diff --git a/server/handlers/wireguard/handlers/completions_body.go b/server/handlers/wireguard/handlers/completions_body.go index e553dfc..b844734 100644 --- a/server/handlers/wireguard/handlers/completions_body.go +++ b/server/handlers/wireguard/handlers/completions_body.go @@ -86,13 +86,20 @@ func getPropertyCompletions( Address = 10.| */ - if property == nil || property.Separator == nil { + currentLine := params.Position.Line + position := common.LSPCharacterAsCursorPosition(params.Position.Character) + + // Special case, key defined but separator missing + if property != nil && property.Separator == nil && !property.Key.ContainsPosition(position) { + return getKeyCompletions(section, true, currentLine), nil + } + + if property == nil || property.Separator == nil || property.Key.ContainsPosition(position) { // First scenario - return getKeyCompletions(section), nil + return getKeyCompletions(section, false, currentLine), nil } // Check if the cursor it outside the value - position := common.LSPCharacterAsCursorPosition(params.Position.Character) if property.Value != nil && property.Value.IsPositionAfterEnd(position) { // Then we don't show anything return nil, nil @@ -104,45 +111,55 @@ func getPropertyCompletions( func getKeyCompletions( section ast.WGSection, + onlySeparator bool, + currentLine uint32, ) []protocol.CompletionItem { options := make(map[string]docvalues.DocumentationValue) + allowedDuplicatedFields := make(map[string]struct{}) switch section.Header.Name { case "Interface": maps.Copy(options, fields.InterfaceOptions) - - // Remove existing, non-duplicate options - for _, property := range section.Properties { - if _, found := fields.InterfaceAllowedDuplicateFields[property.Key.Name]; found { - continue - } - - delete(options, property.Key.Name) - } + allowedDuplicatedFields = fields.InterfaceAllowedDuplicateFields case "Peer": maps.Copy(options, fields.PeerOptions) + allowedDuplicatedFields = fields.PeerAllowedDuplicateFields + } - // Remove existing, non-duplicate options - for _, property := range section.Properties { - if _, found := fields.PeerAllowedDuplicateFields[property.Key.Name]; found { - continue - } - - delete(options, property.Key.Name) + // Remove existing, non-duplicate options + for _, property := range section.Properties { + if _, found := allowedDuplicatedFields[property.Key.Name]; found { + continue } + + if property.Key.Start.Line == currentLine { + // The user is currently typing the key, thus we should suggest it + continue + } + + delete(options, property.Key.Name) } kind := protocol.CompletionItemKindField return utils.MapMapToSlice( options, - func(optionName string, value docvalues.DocumentationValue) protocol.CompletionItem { - insertText := optionName + " = " + func(rawOptionName string, value docvalues.DocumentationValue) protocol.CompletionItem { + var label string + var insertText string + + if onlySeparator { + label = rawOptionName + " = " + insertText = "= " + } else { + label = rawOptionName + insertText = rawOptionName + " = " + } return protocol.CompletionItem{ Kind: &kind, Documentation: value.Documentation, - Label: optionName, + Label: label, InsertText: &insertText, } }, diff --git a/server/handlers/wireguard/handlers/fetch-code-actions.go b/server/handlers/wireguard/handlers/fetch-code-actions.go index 7a5a86b..389ceba 100644 --- a/server/handlers/wireguard/handlers/fetch-code-actions.go +++ b/server/handlers/wireguard/handlers/fetch-code-actions.go @@ -1,37 +1,52 @@ package handlers -/* import ( - "config-lsp/handlers/wireguard/ast" + "config-lsp/handlers/wireguard" "config-lsp/handlers/wireguard/commands" + protocol "github.com/tliron/glsp/protocol_3_16" ) func GetKeepaliveCodeActions( - p *ast.WGConfig, + d *wireguard.WGDocument, params *protocol.CodeActionParams, ) []protocol.CodeAction { line := params.Range.Start.Line - for index, section := range p.Sections { - if section.StartLine >= line && line <= section.EndLine && section.Header != nil && *section.Header == "Peer" { - if section.ExistsProperty("Endpoint") && !section.ExistsProperty("PersistentKeepalive") { - commandID := "wireguard." + CodeActionAddKeepalive - command := protocol.Command{ - Title: "Add PersistentKeepalive", - Command: string(commandID), - Arguments: []any{ - CodeActionAddKeepaliveArgs{ - URI: params.TextDocument.URI, - SectionIndex: uint32(index), - }, - }, + for _, section := range d.Indexes.SectionsByName["Peer"] { + if section.Start.Line >= line && line <= section.End.Line { + if section.FindPropertyByName("Endpoint") != nil && section.FindFirstPropertyByName("PersistentKeepalive") == nil { + var insertionLine uint32 + lastProperty := section.GetLastProperty() + + if lastProperty == nil { + insertionLine = section.End.Line + } else { + insertionLine = lastProperty.End.Line + 1 } return []protocol.CodeAction{ { Title: "Add PersistentKeepalive", - Command: &command, + Edit: &protocol.WorkspaceEdit{ + Changes: map[protocol.DocumentUri][]protocol.TextEdit{ + params.TextDocument.URI: { + { + Range: protocol.Range{ + Start: protocol.Position{ + Line: insertionLine, + Character: 0, + }, + End: protocol.Position{ + Line: insertionLine, + Character: 0, + }, + }, + NewText: "PersistentKeepalive = 25\n", + }, + }, + }, + }, }, } } @@ -42,11 +57,17 @@ func GetKeepaliveCodeActions( } func GetKeyGenerationCodeActions( - p *ast.WGConfig, + d *wireguard.WGDocument, params *protocol.CodeActionParams, ) []protocol.CodeAction { + if !wgcommands.AreWireguardToolsAvailable() { + return nil + } + line := params.Range.Start.Line - section, property := p.GetPropertyByLine(line) + + section := d.Config.FindSectionByLine(line) + property := d.Config.FindPropertyByLine(line) if section == nil || property == nil || property.Separator == nil { return nil @@ -54,10 +75,6 @@ func GetKeyGenerationCodeActions( switch property.Key.Name { case "PrivateKey": - if !wgcommands.AreWireguardToolsAvailable() { - return nil - } - commandID := "wireguard." + CodeActionGeneratePrivateKey command := protocol.Command{ Title: "Generate Private Key", @@ -103,4 +120,3 @@ func GetKeyGenerationCodeActions( return nil } -*/ diff --git a/server/handlers/wireguard/lsp/text-document-code-action.go b/server/handlers/wireguard/lsp/text-document-code-action.go index fe006e8..98bda8b 100644 --- a/server/handlers/wireguard/lsp/text-document-code-action.go +++ b/server/handlers/wireguard/lsp/text-document-code-action.go @@ -1,18 +1,20 @@ package lsp import ( + "config-lsp/handlers/wireguard" "config-lsp/handlers/wireguard/handlers" + "github.com/tliron/glsp" protocol "github.com/tliron/glsp/protocol_3_16" ) func TextDocumentCodeAction(context *glsp.Context, params *protocol.CodeActionParams) ([]protocol.CodeAction, error) { - p := documentParserMap[params.TextDocument.URI] + d := wireguard.DocumentParserMap[params.TextDocument.URI] actions := make([]protocol.CodeAction, 0, 2) - actions = append(actions, handlers.GetKeyGenerationCodeActions(p, params)...) - actions = append(actions, handlers.GetKeepaliveCodeActions(p, params)...) + actions = append(actions, handlers.GetKeyGenerationCodeActions(d, params)...) + actions = append(actions, handlers.GetKeepaliveCodeActions(d, params)...) if len(actions) > 0 { return actions, nil diff --git a/server/handlers/wireguard/lsp/text-document-hover.go b/server/handlers/wireguard/lsp/text-document-hover.go index d75dab3..bc54cec 100644 --- a/server/handlers/wireguard/lsp/text-document-hover.go +++ b/server/handlers/wireguard/lsp/text-document-hover.go @@ -1,10 +1,6 @@ package lsp import ( - "config-lsp/handlers/wireguard/handlers" - "config-lsp/handlers/wireguard/parser" - "strings" - "github.com/tliron/glsp" protocol "github.com/tliron/glsp/protocol_3_16" ) @@ -13,6 +9,7 @@ func TextDocumentHover( context *glsp.Context, params *protocol.HoverParams, ) (*protocol.Hover, error) { + /* p := documentParserMap[params.TextDocument.URI] switch p.GetTypeByLine(params.Position.Line) { @@ -37,6 +34,7 @@ func TextDocumentHover( } return &hover, nil } + */ return nil, nil } diff --git a/server/handlers/wireguard/lsp/workspace-execute-command.go b/server/handlers/wireguard/lsp/workspace-execute-command.go index cbae011..19cd6fc 100644 --- a/server/handlers/wireguard/lsp/workspace-execute-command.go +++ b/server/handlers/wireguard/lsp/workspace-execute-command.go @@ -1,6 +1,7 @@ package lsp import ( + "config-lsp/handlers/wireguard" "config-lsp/handlers/wireguard/handlers" "strings" @@ -15,21 +16,15 @@ func WorkspaceExecuteCommand(context *glsp.Context, params *protocol.ExecuteComm case string(handlers.CodeActionGeneratePrivateKey): args := handlers.CodeActionGeneratePrivateKeyArgsFromArguments(params.Arguments[0].(map[string]any)) - p := documentParserMap[args.URI] + d := wireguard.DocumentParserMap[args.URI] - return args.RunCommand(p) + return args.RunCommand(d) case string(handlers.CodeActionGeneratePresharedKey): args := handlers.CodeActionGeneratePresharedKeyArgsFromArguments(params.Arguments[0].(map[string]any)) - parser := documentParserMap[args.URI] + d := wireguard.DocumentParserMap[args.URI] - return args.RunCommand(parser) - case string(handlers.CodeActionAddKeepalive): - args := handlers.CodeActionAddKeepaliveArgsFromArguments(params.Arguments[0].(map[string]any)) - - p := documentParserMap[args.URI] - - return args.RunCommand(p) + return args.RunCommand(d) } return nil, nil