diff --git a/config-lsp b/config-lsp index 69fc8f6..9b34894 100755 Binary files a/config-lsp and b/config-lsp differ diff --git a/handlers/wireguard/code-actions.go b/handlers/wireguard/code-actions.go deleted file mode 100644 index f888ef8..0000000 --- a/handlers/wireguard/code-actions.go +++ /dev/null @@ -1,157 +0,0 @@ -package wireguard - -import ( - protocol "github.com/tliron/glsp/protocol_3_16" -) - -type codeActionName string - -const ( - codeActionGeneratePrivateKey codeActionName = "generatePrivateKey" - codeActionGeneratePresharedKey codeActionName = "generatePresharedKey" - codeActionAddKeepalive codeActionName = "addKeepalive" -) - -type codeActionGeneratePrivateKeyArgs struct { - URI protocol.DocumentUri - Line uint32 -} - -func codeActionGeneratePrivateKeyArgsFromArguments(arguments map[string]any) codeActionGeneratePrivateKeyArgs { - return codeActionGeneratePrivateKeyArgs{ - URI: arguments["URI"].(protocol.DocumentUri), - Line: uint32(arguments["Line"].(float64)), - } -} - -type codeActionGeneratePresharedKeyArgs struct { - URI protocol.DocumentUri - Line uint32 -} - -func codeActionGeneratePresharedKeyArgsFromArguments(arguments map[string]any) codeActionGeneratePresharedKeyArgs { - return codeActionGeneratePresharedKeyArgs{ - URI: arguments["URI"].(protocol.DocumentUri), - Line: uint32(arguments["Line"].(float64)), - } -} - -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 (p wireguardProperty) getInsertRange(line uint32) protocol.Range { - var insertPosition uint32 = p.Separator.Location.End - var length uint32 = 0 - - if p.Value != nil { - insertPosition = p.Value.Location.Start - 1 - // Length of the value; +1 because of the starting space - length = (p.Value.Location.End - p.Value.Location.Start) + 1 - } - - return protocol.Range{ - Start: protocol.Position{ - Line: line, - Character: insertPosition, - }, - End: protocol.Position{ - Line: line, - Character: insertPosition + length, - }, - } -} - -func (p *wireguardParser) runGeneratePrivateKey(args codeActionGeneratePrivateKeyArgs) (*protocol.ApplyWorkspaceEditParams, error) { - privateKey, err := createNewPrivateKey() - - if err != nil { - return &protocol.ApplyWorkspaceEditParams{}, err - } - - section, property := p.getPropertyByLine(args.Line) - - if section == nil || property == nil { - return nil, nil - } - - label := "Generate Private Key" - return &protocol.ApplyWorkspaceEditParams{ - Label: &label, - Edit: protocol.WorkspaceEdit{ - Changes: map[protocol.DocumentUri][]protocol.TextEdit{ - args.URI: { - { - NewText: " " + privateKey, - Range: property.getInsertRange(args.Line), - }, - }, - }, - }, - }, nil -} - -func (p *wireguardParser) runGeneratePresharedKey(args codeActionGeneratePresharedKeyArgs) (*protocol.ApplyWorkspaceEditParams, error) { - presharedKey, err := createPresharedKey() - - if err != nil { - return &protocol.ApplyWorkspaceEditParams{}, err - } - - section, property := p.getPropertyByLine(args.Line) - - if section == nil || property == nil { - return nil, nil - } - - label := "Generate Preshared Key" - return &protocol.ApplyWorkspaceEditParams{ - Label: &label, - Edit: protocol.WorkspaceEdit{ - Changes: map[protocol.DocumentUri][]protocol.TextEdit{ - args.URI: { - { - NewText: " " + presharedKey, - Range: property.getInsertRange(args.Line), - }, - }, - }, - }, - }, nil -} - -func (p *wireguardParser) runAddKeepalive(args codeActionAddKeepaliveArgs) (*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, - }, - End: protocol.Position{ - Line: section.EndLine + 1, - Character: 0, - }, - }, - }, - }, - }, - }, - }, nil -} diff --git a/handlers/wireguard/wg-commands.go b/handlers/wireguard/commands/wg-commands.go similarity index 77% rename from handlers/wireguard/wg-commands.go rename to handlers/wireguard/commands/wg-commands.go index 37660dc..147d74b 100644 --- a/handlers/wireguard/wg-commands.go +++ b/handlers/wireguard/commands/wg-commands.go @@ -1,4 +1,4 @@ -package wireguard +package wgcommands import ( "os/exec" @@ -8,13 +8,13 @@ import ( var whitespacePattern = regexp.MustCompile(`[\s\n]+`) -func areWireguardToolsAvailable() bool { +func AreWireguardToolsAvailable() bool { _, err := exec.LookPath("wg") return err == nil } -func createNewPrivateKey() (string, error) { +func CreateNewPrivateKey() (string, error) { cmd := exec.Command("wg", "genkey") bytes, err := cmd.Output() @@ -26,7 +26,7 @@ func createNewPrivateKey() (string, error) { return string(whitespacePattern.ReplaceAll(bytes, []byte(""))), nil } -func createPublicKey(privateKey string) (string, error) { +func CreatePublicKey(privateKey string) (string, error) { cmd := exec.Command("wg", "pubkey") cmd.Stdin = strings.NewReader(privateKey) @@ -39,7 +39,7 @@ func createPublicKey(privateKey string) (string, error) { return string(whitespacePattern.ReplaceAll(bytes, []byte(""))), nil } -func createPresharedKey() (string, error) { +func CreatePresharedKey() (string, error) { cmd := exec.Command("wg", "genpsk") bytes, err := cmd.Output() diff --git a/handlers/wireguard/wg_commands_test.go b/handlers/wireguard/commands/wg-commands_test.go similarity index 78% rename from handlers/wireguard/wg_commands_test.go rename to handlers/wireguard/commands/wg-commands_test.go index 6cb2de6..350cf91 100644 --- a/handlers/wireguard/wg_commands_test.go +++ b/handlers/wireguard/commands/wg-commands_test.go @@ -1,11 +1,11 @@ -package wireguard +package wgcommands import "testing" func TestWireguardAvailable( t *testing.T, ) { - if !areWireguardToolsAvailable() { + if !AreWireguardToolsAvailable() { t.Skip("Wireguard tools not available") } } @@ -13,7 +13,7 @@ func TestWireguardAvailable( func TestWireguardPrivateKey( t *testing.T, ) { - privateKey, err := createNewPrivateKey() + privateKey, err := CreateNewPrivateKey() if err != nil { t.Fatal(err) @@ -26,7 +26,7 @@ func TestWireguardPublicKey( t *testing.T, ) { privateKey := "UPBKR0kLF2C/+Ei5fwN5KHsAcon9xfBX+RWhebYFGWg=" - publicKey, err := createPublicKey(privateKey) + publicKey, err := CreatePublicKey(privateKey) if err != nil { t.Fatal(err) diff --git a/handlers/wireguard/documentation-fields.go b/handlers/wireguard/fields/documentation-fields.go similarity index 96% rename from handlers/wireguard/documentation-fields.go rename to handlers/wireguard/fields/documentation-fields.go index 9ca858d..f7031d7 100644 --- a/handlers/wireguard/documentation-fields.go +++ b/handlers/wireguard/fields/documentation-fields.go @@ -1,15 +1,15 @@ -// Documentation taken from https://github.com/pirate/wireguard-docs -package wireguard +package fields import ( docvalues "config-lsp/doc-values" ) -var headerInterfaceEnum = docvalues.CreateEnumStringWithDoc( +// Documentation taken from https://github.com/pirate/wireguard-docs +var HeaderInterfaceEnum = docvalues.CreateEnumStringWithDoc( "[Interface]", "Defines the VPN settings for the local node.", ) -var headerPeerEnum = docvalues.CreateEnumStringWithDoc( +var HeaderPeerEnum = docvalues.CreateEnumStringWithDoc( "[Peer]", `Defines the VPN settings for a remote peer capable of routing traffic for one or more addresses (itself and/or other peers). Peers can be either a public bounce server that relays traffic to other peers, or a directly accessible client via LAN/internet that is not behind a NAT and only routes traffic for itself. @@ -25,7 +25,7 @@ var maxPortValue = 65535 var minMTUValue = 68 var maxMTUValue = 1500 -var interfaceOptions = map[string]docvalues.DocumentationValue{ +var InterfaceOptions = map[string]docvalues.DocumentationValue{ "Address": { Documentation: `Defines what address range the local node should route traffic for. Depending on whether the node is a simple client joining the VPN subnet, or a bounce server that's relaying traffic between multiple clients, this can be set to a single IP of the node itself (specified with CIDR notation), e.g. 192.0.2.3/32), or a range of IPv4/IPv6 subnets that the node can route traffic for. @@ -221,14 +221,14 @@ Remove the iptables rule that forwards packets on the WireGuard interface }, } -var interfaceAllowedDuplicateFields = map[string]struct{}{ +var InterfaceAllowedDuplicateFields = map[string]struct{}{ "PreUp": {}, "PostUp": {}, "PreDown": {}, "PostDown": {}, } -var peerOptions = map[string]docvalues.DocumentationValue{ +var PeerOptions = map[string]docvalues.DocumentationValue{ "Endpoint": { Documentation: `Defines the publicly accessible address for a remote peer. This should be left out for peers behind a NAT or peers that don't have a stable publicly accessible IP:PORT pair. Typically, this only needs to be defined on the main bounce server, but it can also be defined on other public nodes with stable IPs like public-server2 in the example config below. @@ -316,11 +316,11 @@ Oocal NAT-ed node to remote public node }, } -var peerAllowedDuplicateFields = map[string]struct{}{ +var PeerAllowedDuplicateFields = map[string]struct{}{ "AllowedIPs": {}, } -var optionsHeaderMap = map[string](map[string]docvalues.DocumentationValue){ - "Interface": interfaceOptions, - "Peer": peerOptions, +var OptionsHeaderMap = map[string](map[string]docvalues.DocumentationValue){ + "Interface": InterfaceOptions, + "Peer": PeerOptions, } diff --git a/handlers/wireguard/analyzer.go b/handlers/wireguard/handlers/analyzer.go similarity index 65% rename from handlers/wireguard/analyzer.go rename to handlers/wireguard/handlers/analyzer.go index efe7288..d710309 100644 --- a/handlers/wireguard/analyzer.go +++ b/handlers/wireguard/handlers/analyzer.go @@ -1,7 +1,9 @@ -package wireguard +package handlers import ( docvalues "config-lsp/doc-values" + "config-lsp/handlers/wireguard/fields" + "config-lsp/handlers/wireguard/parser" "config-lsp/utils" "fmt" "slices" @@ -10,33 +12,38 @@ import ( protocol "github.com/tliron/glsp/protocol_3_16" ) -func (p wireguardParser) analyze() []protocol.Diagnostic { - sectionsErrors := p.analyzeSections() +func Analyze( + p parser.WireguardParser, +) []protocol.Diagnostic { + sectionsErrors := analyzeSections(p.Sections) + sectionsErrors = append(analyzeOnlyOneInterfaceSectionSpecified(p)) if len(sectionsErrors) > 0 { return sectionsErrors } - validCheckErrors := p.checkIfValuesAreValid() + validCheckErrors := checkIfValuesAreValid(p.Sections) if len(validCheckErrors) > 0 { return validCheckErrors } - diagnostics := []protocol.Diagnostic{} - diagnostics = append(diagnostics, p.checkForDuplicateProperties()...) - diagnostics = append(diagnostics, p.analyzeDNSContainsFallback()...) - diagnostics = append(diagnostics, p.analyzeKeepAliveIsSet()...) - diagnostics = append(diagnostics, p.analyzeSymmetricPropertiesExist()...) + diagnostics := make([]protocol.Diagnostic, 0) + diagnostics = append(diagnostics, analyzeParserForDuplicateProperties(p)...) + diagnostics = append(diagnostics, analyzeDNSContainsFallback(p)...) + diagnostics = append(diagnostics, analyzeKeepAliveIsSet(p)...) + diagnostics = append(diagnostics, analyzeSymmetricPropertiesExist(p)...) return diagnostics } -func (p wireguardParser) analyzeSections() []protocol.Diagnostic { - diagnostics := []protocol.Diagnostic{} +func analyzeSections( + sections []*parser.WireguardSection, +) []protocol.Diagnostic { + var diagnostics []protocol.Diagnostic - for _, section := range p.Sections { - sectionDiagnostics := section.analyzeSection() + for _, section := range sections { + sectionDiagnostics := analyzeSection(*section) if len(sectionDiagnostics) > 0 { diagnostics = append(diagnostics, sectionDiagnostics...) @@ -47,20 +54,22 @@ func (p wireguardParser) analyzeSections() []protocol.Diagnostic { return diagnostics } - return p.analyzeOnlyOneInterfaceSectionSpecified() + return diagnostics } -func (p wireguardParser) analyzeOnlyOneInterfaceSectionSpecified() []protocol.Diagnostic { - diagnostics := []protocol.Diagnostic{} +func analyzeOnlyOneInterfaceSectionSpecified( + p parser.WireguardParser, +) []protocol.Diagnostic { + var diagnostics []protocol.Diagnostic alreadyFound := false - for _, section := range p.getSectionsByName("Interface") { + for _, section := range p.GetSectionsByName("Interface") { if alreadyFound { severity := protocol.DiagnosticSeverityError diagnostics = append(diagnostics, protocol.Diagnostic{ Message: "Only one [Interface] section is allowed", Severity: &severity, - Range: section.getHeaderLineRange(), + Range: section.GetHeaderLineRange(), }) } @@ -70,8 +79,10 @@ func (p wireguardParser) analyzeOnlyOneInterfaceSectionSpecified() []protocol.Di return diagnostics } -func (p wireguardParser) analyzeDNSContainsFallback() []protocol.Diagnostic { - lineNumber, property := p.fetchPropertyByName("DNS") +func analyzeDNSContainsFallback( + p parser.WireguardParser, +) []protocol.Diagnostic { + lineNumber, property := p.FindFirstPropertyByName("DNS") if property == nil { return []protocol.Diagnostic{} @@ -103,17 +114,19 @@ func (p wireguardParser) analyzeDNSContainsFallback() []protocol.Diagnostic { return []protocol.Diagnostic{} } -func (p wireguardParser) analyzeKeepAliveIsSet() []protocol.Diagnostic { - diagnostics := make([]protocol.Diagnostic, 0) +func analyzeKeepAliveIsSet( + p parser.WireguardParser, +) []protocol.Diagnostic { + var diagnostics []protocol.Diagnostic - for _, section := range p.getSectionsByName("Peer") { + for _, section := range p.GetSectionsByName("Peer") { // If an endpoint is set, then we should only check for the keepalive property - if section.existsProperty("Endpoint") && !section.existsProperty("PersistentKeepalive") { + if section.ExistsProperty("Endpoint") && !section.ExistsProperty("PersistentKeepalive") { severity := protocol.DiagnosticSeverityHint diagnostics = append(diagnostics, protocol.Diagnostic{ Message: "PersistentKeepalive is not set. It is recommended to set this property, as it helps to maintain the connection when users are behind NAT", Severity: &severity, - Range: section.getRange(), + Range: section.GetRange(), }) } } @@ -123,14 +136,16 @@ func (p wireguardParser) analyzeKeepAliveIsSet() []protocol.Diagnostic { // Check if the values are valid. // Assumes that sections have been analyzed already. -func (p wireguardParser) checkIfValuesAreValid() []protocol.Diagnostic { - diagnostics := []protocol.Diagnostic{} +func checkIfValuesAreValid( + sections []*parser.WireguardSection, +) []protocol.Diagnostic { + var diagnostics []protocol.Diagnostic - for _, section := range p.Sections { + for _, section := range sections { for lineNumber, property := range section.Properties { diagnostics = append( diagnostics, - property.analyzeProperty(section, lineNumber)..., + analyzeProperty(property, section, lineNumber)..., ) } } @@ -138,8 +153,10 @@ func (p wireguardParser) checkIfValuesAreValid() []protocol.Diagnostic { return diagnostics } -func (s wireguardSection) analyzeSection() []protocol.Diagnostic { - diagnostics := []protocol.Diagnostic{} +func analyzeSection( + s parser.WireguardSection, +) []protocol.Diagnostic { + var diagnostics []protocol.Diagnostic if s.Name == nil { // No section name @@ -147,18 +164,18 @@ func (s wireguardSection) analyzeSection() []protocol.Diagnostic { diagnostics = append(diagnostics, protocol.Diagnostic{ Message: "This section is missing a name", Severity: &severity, - Range: s.getRange(), + Range: s.GetRange(), }) return diagnostics } - if _, found := optionsHeaderMap[*s.Name]; !found { + if _, found := fields.OptionsHeaderMap[*s.Name]; !found { // Unknown section severity := protocol.DiagnosticSeverityError diagnostics = append(diagnostics, protocol.Diagnostic{ Message: fmt.Sprintf("Unknown section '%s'. It must be one of: [Interface], [Peer]", *s.Name), Severity: &severity, - Range: s.getHeaderLineRange(), + Range: s.GetHeaderLineRange(), }) return diagnostics @@ -171,11 +188,12 @@ func (s wireguardSection) analyzeSection() []protocol.Diagnostic { // Returns a list of diagnostics. // `belongingSection` is the section to which the property belongs. This value is // expected to be non-nil and expected to be a valid Wireguard section. -func (p wireguardProperty) analyzeProperty( - belongingSection *wireguardSection, +func analyzeProperty( + p parser.WireguardProperty, + belongingSection *parser.WireguardSection, propertyLine uint32, ) []protocol.Diagnostic { - sectionOptions := optionsHeaderMap[*belongingSection.Name] + sectionOptions := fields.OptionsHeaderMap[*belongingSection.Name] option, found := sectionOptions[p.Key.Name] if !found { @@ -206,7 +224,7 @@ func (p wireguardProperty) analyzeProperty( { Message: "Property is missing a value", Severity: &severity, - Range: p.getLineRange(propertyLine), + Range: p.GetLineRange(propertyLine), }, } } @@ -232,36 +250,40 @@ func (p wireguardProperty) analyzeProperty( }) } -func (p wireguardParser) checkForDuplicateProperties() []protocol.Diagnostic { +func analyzeParserForDuplicateProperties( + p parser.WireguardParser, +) []protocol.Diagnostic { diagnostics := make([]protocol.Diagnostic, 0) for _, section := range p.Sections { - diagnostics = append(diagnostics, section.analyzeDuplicateProperties()...) + diagnostics = append(diagnostics, analyzeDuplicateProperties(*section)...) } return diagnostics } -func (p wireguardSection) analyzeDuplicateProperties() []protocol.Diagnostic { - diagnostics := make([]protocol.Diagnostic, 0) +func analyzeDuplicateProperties( + s parser.WireguardSection, +) []protocol.Diagnostic { + var diagnostics []protocol.Diagnostic existingProperties := make(map[string]uint32) - lines := utils.KeysOfMap(p.Properties) + lines := utils.KeysOfMap(s.Properties) slices.Sort(lines) for _, currentLineNumber := range lines { - property := p.Properties[currentLineNumber] + property := s.Properties[currentLineNumber] var skipCheck = false - if p.Name != nil { - switch *p.Name { + if s.Name != nil { + switch *s.Name { case "Interface": - if _, found := interfaceAllowedDuplicateFields[property.Key.Name]; found { + if _, found := fields.InterfaceAllowedDuplicateFields[property.Key.Name]; found { skipCheck = true } case "Peer": - if _, found := peerAllowedDuplicateFields[property.Key.Name]; found { + if _, found := fields.PeerAllowedDuplicateFields[property.Key.Name]; found { skipCheck = true } } @@ -295,33 +317,40 @@ func (p wireguardSection) analyzeDuplicateProperties() []protocol.Diagnostic { return diagnostics } +// Strategy +// Simply compare the host bits of the IP addresses. +// Use a binary tree to store the host bits. +/* func (p wireguardParser) analyzeAllowedIPIsInRange() []protocol.Diagnostic { diagnostics := make([]protocol.Diagnostic, 0) return diagnostics } +*/ -func (p wireguardParser) analyzeSymmetricPropertiesExist() []protocol.Diagnostic { +func analyzeSymmetricPropertiesExist( + p parser.WireguardParser, +) []protocol.Diagnostic { diagnostics := make([]protocol.Diagnostic, 0, 4) severity := protocol.DiagnosticSeverityHint - for _, section := range p.getSectionsByName("Interface") { - preUpLine, preUpProperty := section.fetchFirstProperty("PreUp") - preDownLine, preDownProperty := section.fetchFirstProperty("PreDown") + for _, section := range p.GetSectionsByName("Interface") { + preUpLine, preUpProperty := section.FetchFirstProperty("PreUp") + preDownLine, preDownProperty := section.FetchFirstProperty("PreDown") - postUpLine, postUpProperty := section.fetchFirstProperty("PostUp") - postDownLine, postDownProperty := section.fetchFirstProperty("PostDown") + postUpLine, postUpProperty := section.FetchFirstProperty("PostUp") + postDownLine, postDownProperty := section.FetchFirstProperty("PostDown") if preUpProperty != nil && preDownProperty == nil { diagnostics = append(diagnostics, protocol.Diagnostic{ Message: "PreUp is set, but PreDown is not. It is recommended to set both properties symmetrically", - Range: preUpProperty.getLineRange(*preUpLine), + Range: preUpProperty.GetLineRange(*preUpLine), Severity: &severity, }) } else if preUpProperty == nil && preDownProperty != nil { diagnostics = append(diagnostics, protocol.Diagnostic{ Message: "PreDown is set, but PreUp is not. It is recommended to set both properties symmetrically", - Range: preDownProperty.getLineRange(*preDownLine), + Range: preDownProperty.GetLineRange(*preDownLine), Severity: &severity, }) } @@ -329,13 +358,13 @@ func (p wireguardParser) analyzeSymmetricPropertiesExist() []protocol.Diagnostic if postUpProperty != nil && postDownProperty == nil { diagnostics = append(diagnostics, protocol.Diagnostic{ Message: "PostUp is set, but PostDown is not. It is recommended to set both properties symmetrically", - Range: postUpProperty.getLineRange(*postUpLine), + Range: postUpProperty.GetLineRange(*postUpLine), Severity: &severity, }) } else if postUpProperty == nil && postDownProperty != nil { diagnostics = append(diagnostics, protocol.Diagnostic{ Message: "PostDown is set, but PostUp is not. It is recommended to set both properties symmetrically", - Range: postDownProperty.getLineRange(*postDownLine), + Range: postDownProperty.GetLineRange(*postDownLine), Severity: &severity, }) } diff --git a/handlers/wireguard/analyzer_test.go b/handlers/wireguard/handlers/analyzer_test.go similarity index 56% rename from handlers/wireguard/analyzer_test.go rename to handlers/wireguard/handlers/analyzer_test.go index cd4c892..345e3ad 100644 --- a/handlers/wireguard/analyzer_test.go +++ b/handlers/wireguard/handlers/analyzer_test.go @@ -1,19 +1,23 @@ -package wireguard +package handlers -import "testing" +import ( + "config-lsp/handlers/wireguard/parser" + "config-lsp/utils" + "testing" +) func TestMultipleIntefaces(t *testing.T) { - content := dedent(` + content := utils.Dedent(` [Interface] PrivateKey = abc [Interface] PrivateKey = def `) - parser := createWireguardParser() - parser.parseFromString(content) + p := parser.CreateWireguardParser() + p.ParseFromString(content) - diagnostics := parser.analyze() + diagnostics := Analyze(p) if len(diagnostics) == 0 { t.Errorf("Expected diagnostic errors, got %d", len(diagnostics)) @@ -21,14 +25,14 @@ PrivateKey = def } func TestInvalidValue(t *testing.T) { - content := dedent(` + content := utils.Dedent(` [Interface] DNS = nope `) - parser := createWireguardParser() - parser.parseFromString(content) + p := parser.CreateWireguardParser() + p.ParseFromString(content) - diagnostics := parser.analyze() + diagnostics := Analyze(p) if len(diagnostics) == 0 { t.Errorf("Expected diagnostic errors, got %d", len(diagnostics)) @@ -36,16 +40,16 @@ DNS = nope } func TestDuplicateProperties(t *testing.T) { - content := dedent(` + content := utils.Dedent(` [Interface] PrivateKey = abc DNS = 1.1.1.1 PrivateKey = def `) - parser := createWireguardParser() - parser.parseFromString(content) + p := parser.CreateWireguardParser() + p.ParseFromString(content) - diagnostics := parser.analyze() + diagnostics := Analyze(p) if len(diagnostics) == 0 { t.Errorf("Expected diagnostic errors, got %d", len(diagnostics)) diff --git a/handlers/wireguard/handlers/code-actions.go b/handlers/wireguard/handlers/code-actions.go new file mode 100644 index 0000000..ecb37cd --- /dev/null +++ b/handlers/wireguard/handlers/code-actions.go @@ -0,0 +1,143 @@ +package handlers + +import ( + wgcommands "config-lsp/handlers/wireguard/commands" + "config-lsp/handlers/wireguard/parser" + protocol "github.com/tliron/glsp/protocol_3_16" +) + +type CodeActionName string + +const ( + CodeActionGeneratePrivateKey CodeActionName = "generatePrivateKey" + CodeActionGeneratePresharedKey CodeActionName = "generatePresharedKey" + CodeActionAddKeepalive CodeActionName = "addKeepalive" +) + +type CodeAction interface { + RunCommand(*parser.WireguardParser) (*protocol.ApplyWorkspaceEditParams, error) +} + +type CodeActionArgs interface{} + +type CodeActionGeneratePrivateKeyArgs struct { + URI protocol.DocumentUri + Line uint32 +} + +func CodeActionGeneratePrivateKeyArgsFromArguments(arguments map[string]any) CodeActionGeneratePrivateKeyArgs { + return CodeActionGeneratePrivateKeyArgs{ + URI: arguments["URI"].(protocol.DocumentUri), + Line: uint32(arguments["Line"].(float64)), + } +} + +func (args CodeActionGeneratePrivateKeyArgs) RunCommand(p *parser.WireguardParser) (*protocol.ApplyWorkspaceEditParams, error) { + privateKey, err := wgcommands.CreateNewPrivateKey() + + if err != nil { + return &protocol.ApplyWorkspaceEditParams{}, err + } + + section, property := p.GetPropertyByLine(args.Line) + + if section == nil || property == nil { + return nil, nil + } + + label := "Generate Private Key" + return &protocol.ApplyWorkspaceEditParams{ + Label: &label, + Edit: protocol.WorkspaceEdit{ + Changes: map[protocol.DocumentUri][]protocol.TextEdit{ + args.URI: { + { + NewText: " " + privateKey, + Range: property.GetInsertRange(args.Line), + }, + }, + }, + }, + }, nil +} + +type CodeActionGeneratePresharedKeyArgs struct { + URI protocol.DocumentUri + Line uint32 +} + +func CodeActionGeneratePresharedKeyArgsFromArguments(arguments map[string]any) CodeActionGeneratePresharedKeyArgs { + return CodeActionGeneratePresharedKeyArgs{ + URI: arguments["URI"].(protocol.DocumentUri), + Line: uint32(arguments["Line"].(float64)), + } +} + +func (args CodeActionGeneratePresharedKeyArgs) RunCommand(p *parser.WireguardParser) (*protocol.ApplyWorkspaceEditParams, error) { + presharedKey, err := wgcommands.CreatePresharedKey() + + if err != nil { + return &protocol.ApplyWorkspaceEditParams{}, err + } + + section, property := p.GetPropertyByLine(args.Line) + + if section == nil || property == nil { + return nil, nil + } + + label := "Generate Preshared Key" + return &protocol.ApplyWorkspaceEditParams{ + Label: &label, + Edit: protocol.WorkspaceEdit{ + Changes: map[protocol.DocumentUri][]protocol.TextEdit{ + 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 *parser.WireguardParser) (*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, + }, + End: protocol.Position{ + Line: section.EndLine + 1, + Character: 0, + }, + }, + }, + }, + }, + }, + }, nil +} diff --git a/handlers/wireguard/handlers/completions.go b/handlers/wireguard/handlers/completions.go new file mode 100644 index 0000000..a52c3bd --- /dev/null +++ b/handlers/wireguard/handlers/completions.go @@ -0,0 +1,160 @@ +package handlers + +import ( + docvalues "config-lsp/doc-values" + "config-lsp/handlers/wireguard/fields" + "config-lsp/handlers/wireguard/parser" + "config-lsp/utils" + protocol "github.com/tliron/glsp/protocol_3_16" + "maps" +) + +func getHeaderCompletion(name string, documentation string) protocol.CompletionItem { + textFormat := protocol.InsertTextFormatPlainText + kind := protocol.CompletionItemKindEnum + + insertText := "[" + name + "]\n" + + return protocol.CompletionItem{ + Label: "[" + name + "]", + InsertTextFormat: &textFormat, + InsertText: &insertText, + Kind: &kind, + Documentation: &documentation, + } +} + +func GetRootCompletionsForEmptyLine( + p parser.WireguardParser, +) ([]protocol.CompletionItem, error) { + completions := make([]protocol.CompletionItem, 0) + + if _, found := p.GetInterfaceSection(); !found { + completions = append(completions, getHeaderCompletion("Interface", fields.HeaderInterfaceEnum.Documentation)) + } + + completions = append(completions, getHeaderCompletion("Peer", fields.HeaderPeerEnum.Documentation)) + + return completions, nil +} + +func GetCompletionsForSectionEmptyLine( + s parser.WireguardSection, +) ([]protocol.CompletionItem, error) { + if s.Name == nil { + return nil, nil + } + + options := make(map[string]docvalues.DocumentationValue) + + switch *s.Name { + case "Interface": + maps.Copy(options, fields.InterfaceOptions) + + // Remove existing options + for _, property := range s.Properties { + if _, found := fields.InterfaceAllowedDuplicateFields[property.Key.Name]; found { + continue + } + + delete(options, property.Key.Name) + } + case "Peer": + maps.Copy(options, fields.PeerOptions) + + // Remove existing options + for _, property := range s.Properties { + if _, found := fields.PeerAllowedDuplicateFields[property.Key.Name]; found { + continue + } + + delete(options, property.Key.Name) + } + } + + kind := protocol.CompletionItemKindProperty + + return utils.MapMapToSlice( + options, + func(optionName string, value docvalues.DocumentationValue) protocol.CompletionItem { + insertText := optionName + " = " + + return protocol.CompletionItem{ + Kind: &kind, + Documentation: value.Documentation, + Label: optionName, + InsertText: &insertText, + } + }, + ), nil +} + +func GetSeparatorCompletion(property parser.WireguardProperty, character uint32) ([]protocol.CompletionItem, error) { + var insertText string + + if character == property.Key.Location.End { + insertText = property.Key.Name + " = " + } else { + insertText = "= " + } + + kind := protocol.CompletionItemKindValue + + return []protocol.CompletionItem{ + { + Label: insertText, + InsertText: &insertText, + Kind: &kind, + }, + }, parser.PropertyNotFullyTypedError{} +} + +func GetCompletionsForSectionPropertyLine( + s parser.WireguardSection, + lineNumber uint32, + character uint32, +) ([]protocol.CompletionItem, error) { + property, err := s.GetPropertyByLine(lineNumber) + + if err != nil { + return nil, err + } + + if s.Name == nil { + return nil, parser.PropertyNotFoundError{} + } + + options, found := fields.OptionsHeaderMap[*s.Name] + + if !found { + return nil, parser.PropertyNotFoundError{} + } + + if property.Separator == nil { + if _, found := options[property.Key.Name]; found { + return GetSeparatorCompletion(*property, character) + } + // Get empty line completions + return nil, parser.PropertyNotFullyTypedError{} + } + + option, found := options[property.Key.Name] + + if !found { + if character < property.Separator.Location.Start { + return nil, parser.PropertyNotFullyTypedError{} + } else { + return nil, parser.PropertyNotFoundError{} + } + } + + if property.Value == nil { + if character >= property.Separator.Location.End { + return option.FetchCompletions("", 0), nil + } + } + + relativeCursor := character - property.Value.Location.Start + + return option.FetchCompletions(property.Value.Value, relativeCursor), nil +} diff --git a/handlers/wireguard/parser_completions_test.go b/handlers/wireguard/handlers/completions_test.go similarity index 59% rename from handlers/wireguard/parser_completions_test.go rename to handlers/wireguard/handlers/completions_test.go index 3dfc48b..5db4edc 100644 --- a/handlers/wireguard/parser_completions_test.go +++ b/handlers/wireguard/handlers/completions_test.go @@ -1,46 +1,51 @@ -package wireguard +package handlers -import "testing" +import ( + "config-lsp/handlers/wireguard/fields" + "config-lsp/handlers/wireguard/parser" + "config-lsp/utils" + "testing" +) func TestSimplePropertyInInterface( t *testing.T, ) { - sample := dedent(` + sample := utils.Dedent(` [Interface] `) - parser := createWireguardParser() - parser.parseFromString(sample) + p := parser.CreateWireguardParser() + p.ParseFromString(sample) - completions, err := parser.Sections[0].getCompletionsForEmptyLine() + completions, err := GetCompletionsForSectionEmptyLine(*p.Sections[0]) if err != nil { t.Fatalf("getCompletionsForEmptyLine failed with error: %v", err) } - if len(completions) != len(interfaceOptions) { - t.Fatalf("getCompletionsForEmptyLine: Expected %v completions, but got %v", len(interfaceOptions), len(completions)) + if len(completions) != len(fields.InterfaceOptions) { + t.Fatalf("getCompletionsForEmptyLine: Expected %v completions, but got %v", len(fields.InterfaceOptions), len(completions)) } } func TestSimpleOneExistingPropertyInInterface( t *testing.T, ) { - sample := dedent(` + sample := utils.Dedent(` [Interface] PrivateKey = 1234567890 `) - parser := createWireguardParser() - parser.parseFromString(sample) + p := parser.CreateWireguardParser() + p.ParseFromString(sample) - completions, err := parser.Sections[0].getCompletionsForEmptyLine() + completions, err := GetCompletionsForSectionEmptyLine(*p.Sections[0]) if err != nil { t.Fatalf("getCompletionsForEmptyLine failed with error: %v", err) } - expected := len(interfaceOptions) - 1 + expected := len(fields.InterfaceOptions) - 1 if len(completions) != expected { t.Fatalf("getCompletionsForEmptyLine: Expected %v completions, but got %v", expected, len(completions)) } @@ -49,13 +54,13 @@ PrivateKey = 1234567890 func TestEmptyRootCompletionsWork( t *testing.T, ) { - sample := dedent(` + sample := utils.Dedent(` `) - parser := createWireguardParser() - parser.parseFromString(sample) + p := parser.CreateWireguardParser() + p.ParseFromString(sample) - completions := parser.getRootCompletionsForEmptyLine() + completions, _ := GetRootCompletionsForEmptyLine(p) if len(completions) != 2 { t.Fatalf("getRootCompletionsForEmptyLine: Expected 2 completions, but got %v", len(completions)) @@ -65,14 +70,14 @@ func TestEmptyRootCompletionsWork( func TestInterfaceSectionRootCompletionsBeforeWork( t *testing.T, ) { - sample := dedent(` + sample := utils.Dedent(` [Interface] `) - parser := createWireguardParser() - parser.parseFromString(sample) + p := parser.CreateWireguardParser() + p.ParseFromString(sample) - completions := parser.getRootCompletionsForEmptyLine() + completions, _ := GetRootCompletionsForEmptyLine(p) if len(completions) != 1 { t.Fatalf("getRootCompletionsForEmptyLine: Expected 1 completions, but got %v", len(completions)) @@ -82,15 +87,15 @@ func TestInterfaceSectionRootCompletionsBeforeWork( func TestInterfaceAndPeerSectionRootCompletionsWork( t *testing.T, ) { - sample := dedent(` + sample := utils.Dedent(` [Interface] [Peer] `) - parser := createWireguardParser() - parser.parseFromString(sample) + p := parser.CreateWireguardParser() + p.ParseFromString(sample) - completions := parser.getRootCompletionsForEmptyLine() + completions, _ := GetRootCompletionsForEmptyLine(p) if len(completions) != 1 { t.Fatalf("getRootCompletionsForEmptyLine: Expected 1 completions, but got %v", len(completions)) @@ -100,14 +105,14 @@ func TestInterfaceAndPeerSectionRootCompletionsWork( func TestPropertyNoSepatorShouldCompleteSeparator( t *testing.T, ) { - sample := dedent(` + sample := utils.Dedent(` [Interface] DNS `) - parser := createWireguardParser() - parser.parseFromString(sample) + p := parser.CreateWireguardParser() + p.ParseFromString(sample) - completions, err := parser.Sections[0].getCompletionsForPropertyLine(1, 3) + completions, err := GetCompletionsForSectionPropertyLine(*p.Sections[0], 1, 3) if err == nil { t.Fatalf("getCompletionsForPropertyLine err is nil but should not be") @@ -125,14 +130,14 @@ DNS func TestPropertyNoSeparatorWithSpaceShouldCompleteSeparator( t *testing.T, ) { - sample := dedent(` + sample := utils.Dedent(` [Interface] DNS `) - parser := createWireguardParser() - parser.parseFromString(sample) + p := parser.CreateWireguardParser() + p.ParseFromString(sample) - completions, err := parser.Sections[0].getCompletionsForPropertyLine(1, 4) + completions, err := GetCompletionsForSectionPropertyLine(*p.Sections[0], 1, 4) if err == nil { t.Fatalf("getCompletionsForPropertyLine err is nil but should not be") @@ -150,20 +155,20 @@ DNS func TestHeaderButNoProperty( t *testing.T, ) { - sample := dedent(` + sample := utils.Dedent(` [Interface] `) - parser := createWireguardParser() - parser.parseFromString(sample) + p := parser.CreateWireguardParser() + p.ParseFromString(sample) - completions, err := parser.Sections[0].getCompletionsForEmptyLine() + completions, err := GetCompletionsForSectionEmptyLine(*p.Sections[0]) if err != nil { t.Fatalf("getCompletionsForEmptyLine failed with error: %v", err) } - if len(completions) != len(interfaceOptions) { - t.Fatalf("getCompletionsForEmptyLine: Expected %v completions, but got %v", len(interfaceOptions), len(completions)) + if len(completions) != len(fields.InterfaceOptions) { + t.Fatalf("getCompletionsForEmptyLine: Expected %v completions, but got %v", len(fields.InterfaceOptions), len(completions)) } } diff --git a/handlers/wireguard/fetch-code-actions.go b/handlers/wireguard/handlers/fetch-code-actions.go similarity index 65% rename from handlers/wireguard/fetch-code-actions.go rename to handlers/wireguard/handlers/fetch-code-actions.go index 0d4b5a0..84dfead 100644 --- a/handlers/wireguard/fetch-code-actions.go +++ b/handlers/wireguard/handlers/fetch-code-actions.go @@ -1,22 +1,26 @@ -package wireguard +package handlers -import protocol "github.com/tliron/glsp/protocol_3_16" +import ( + "config-lsp/handlers/wireguard/commands" + "config-lsp/handlers/wireguard/parser" + protocol "github.com/tliron/glsp/protocol_3_16" +) -func getKeepaliveCodeActions( +func GetKeepaliveCodeActions( + p *parser.WireguardParser, params *protocol.CodeActionParams, - parser *wireguardParser, ) []protocol.CodeAction { line := params.Range.Start.Line - for index, section := range parser.Sections { + for index, section := range p.Sections { if section.StartLine >= line && line <= section.EndLine && section.Name != nil && *section.Name == "Peer" { - if section.existsProperty("Endpoint") && !section.existsProperty("PersistentKeepalive") { - commandID := "wireguard." + codeActionAddKeepalive + if section.ExistsProperty("Endpoint") && !section.ExistsProperty("PersistentKeepalive") { + commandID := "wireguard." + CodeActionAddKeepalive command := protocol.Command{ Title: "Add PersistentKeepalive", Command: string(commandID), Arguments: []any{ - codeActionAddKeepaliveArgs{ + CodeActionAddKeepaliveArgs{ URI: params.TextDocument.URI, SectionIndex: uint32(index), }, @@ -36,12 +40,12 @@ func getKeepaliveCodeActions( return nil } -func getKeyGenerationCodeActions( +func GetKeyGenerationCodeActions( + p *parser.WireguardParser, params *protocol.CodeActionParams, - parser *wireguardParser, ) []protocol.CodeAction { line := params.Range.Start.Line - section, property := parser.getPropertyByLine(line) + section, property := p.GetPropertyByLine(line) if section == nil || property == nil || property.Separator == nil { return nil @@ -49,16 +53,16 @@ func getKeyGenerationCodeActions( switch property.Key.Name { case "PrivateKey": - if !areWireguardToolsAvailable() { + if !wgcommands.AreWireguardToolsAvailable() { return nil } - commandID := "wireguard." + codeActionGeneratePrivateKey + commandID := "wireguard." + CodeActionGeneratePrivateKey command := protocol.Command{ Title: "Generate Private Key", Command: string(commandID), Arguments: []any{ - codeActionGeneratePrivateKeyArgs{ + CodeActionGeneratePrivateKeyArgs{ URI: params.TextDocument.URI, Line: line, }, @@ -72,16 +76,16 @@ func getKeyGenerationCodeActions( }, } case "PresharedKey": - if !areWireguardToolsAvailable() { + if !wgcommands.AreWireguardToolsAvailable() { return nil } - commandID := "wireguard." + codeActionGeneratePresharedKey + commandID := "wireguard." + CodeActionGeneratePresharedKey command := protocol.Command{ Title: "Generate PresharedKey", Command: string(commandID), Arguments: []any{ - codeActionGeneratePresharedKeyArgs{ + CodeActionGeneratePresharedKeyArgs{ URI: params.TextDocument.URI, Line: line, }, diff --git a/handlers/wireguard/hover.go b/handlers/wireguard/handlers/hover.go similarity index 55% rename from handlers/wireguard/hover.go rename to handlers/wireguard/handlers/hover.go index 5e3d3da..7ac6660 100644 --- a/handlers/wireguard/hover.go +++ b/handlers/wireguard/handlers/hover.go @@ -1,14 +1,20 @@ -package wireguard +package handlers import ( docvalues "config-lsp/doc-values" + "config-lsp/handlers/wireguard/fields" + "config-lsp/handlers/wireguard/parser" "fmt" "strings" ) -func (p wireguardProperty) getHoverInfo(cursor uint32, section *wireguardSection) []string { +func getPropertyInfo( + p parser.WireguardProperty, + cursor uint32, + section parser.WireguardSection, +) []string { if cursor <= p.Key.Location.End { - options, found := optionsHeaderMap[*section.Name] + options, found := fields.OptionsHeaderMap[*section.Name] if !found { return []string{} @@ -23,7 +29,7 @@ func (p wireguardProperty) getHoverInfo(cursor uint32, section *wireguardSection return strings.Split(option.Documentation, "\n") } - options, found := optionsHeaderMap[*section.Name] + options, found := fields.OptionsHeaderMap[*section.Name] if !found { return []string{} @@ -36,22 +42,54 @@ func (p wireguardProperty) getHoverInfo(cursor uint32, section *wireguardSection return []string{} } -func (p wireguardParser) getHeaderInfo(line uint32, cursor uint32) []string { - section := p.getSectionByLine(line) +func getSectionInfo(s parser.WireguardSection) []string { + if s.Name == nil { + return []string{} + } + + contents := []string{ + fmt.Sprintf("## [%s]", *s.Name), + "", + } + + var option *docvalues.EnumString = nil + + switch *s.Name { + case "Interface": + option = &fields.HeaderInterfaceEnum + case "Peer": + option = &fields.HeaderPeerEnum + } + + if option == nil { + return contents + } + + contents = append(contents, strings.Split(option.Documentation, "\n")...) + + return contents +} + +func GetHoverContent( + p parser.WireguardParser, + line uint32, + cursor uint32, +) []string { + section := p.GetSectionByLine(line) if section == nil { return []string{} } - sectionInfo := section.getHeaderInfo() + sectionInfo := getSectionInfo(*section) - property, _ := section.findProperty(line) + property, _ := section.GetPropertyByLine(line) if property == nil { return sectionInfo } - propertyInfo := property.getHoverInfo(cursor, section) + propertyInfo := getPropertyInfo(*property, cursor, *section) if len(propertyInfo) == 0 { return sectionInfo @@ -65,31 +103,3 @@ func (p wireguardParser) getHeaderInfo(line uint32, cursor uint32) []string { return contents } - -func (p wireguardSection) getHeaderInfo() []string { - if p.Name == nil { - return []string{} - } - - contents := []string{ - fmt.Sprintf("## [%s]", *p.Name), - "", - } - - var option *docvalues.EnumString = nil - - switch *p.Name { - case "Interface": - option = &headerInterfaceEnum - case "Peer": - option = &headerPeerEnum - } - - if option == nil { - return contents - } - - contents = append(contents, strings.Split(option.Documentation, "\n")...) - - return contents -} diff --git a/handlers/wireguard/lsp/shared.go b/handlers/wireguard/lsp/shared.go new file mode 100644 index 0000000..377f850 --- /dev/null +++ b/handlers/wireguard/lsp/shared.go @@ -0,0 +1,8 @@ +package lsp + +import ( + "config-lsp/handlers/wireguard/parser" + protocol "github.com/tliron/glsp/protocol_3_16" +) + +var documentParserMap = map[protocol.DocumentUri]*parser.WireguardParser{} diff --git a/handlers/wireguard/text-document-code-action.go b/handlers/wireguard/lsp/text-document-code-action.go similarity index 55% rename from handlers/wireguard/text-document-code-action.go rename to handlers/wireguard/lsp/text-document-code-action.go index 6d63d52..fe006e8 100644 --- a/handlers/wireguard/text-document-code-action.go +++ b/handlers/wireguard/lsp/text-document-code-action.go @@ -1,17 +1,18 @@ -package wireguard +package lsp import ( + "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) { - parser := documentParserMap[params.TextDocument.URI] + p := documentParserMap[params.TextDocument.URI] actions := make([]protocol.CodeAction, 0, 2) - actions = append(actions, getKeyGenerationCodeActions(params, parser)...) - actions = append(actions, getKeepaliveCodeActions(params, parser)...) + actions = append(actions, handlers.GetKeyGenerationCodeActions(p, params)...) + actions = append(actions, handlers.GetKeepaliveCodeActions(p, params)...) if len(actions) > 0 { return actions, nil diff --git a/handlers/wireguard/lsp/text-document-completion.go b/handlers/wireguard/lsp/text-document-completion.go new file mode 100644 index 0000000..4429343 --- /dev/null +++ b/handlers/wireguard/lsp/text-document-completion.go @@ -0,0 +1,47 @@ +package lsp + +import ( + "config-lsp/handlers/wireguard/handlers" + "config-lsp/handlers/wireguard/parser" + "github.com/tliron/glsp" + protocol "github.com/tliron/glsp/protocol_3_16" +) + +func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionParams) (any, error) { + p := documentParserMap[params.TextDocument.URI] + + lineNumber := params.Position.Line + + section := p.GetBelongingSectionByLine(lineNumber) + lineType := p.GetTypeByLine(lineNumber) + + switch lineType { + case parser.LineTypeComment: + return nil, nil + case parser.LineTypeHeader: + return handlers.GetRootCompletionsForEmptyLine(*p) + case parser.LineTypeEmpty: + if section.Name == nil { + // Root completions + return handlers.GetRootCompletionsForEmptyLine(*p) + } + + return handlers.GetCompletionsForSectionEmptyLine(*section) + case parser.LineTypeProperty: + completions, err := handlers.GetCompletionsForSectionPropertyLine(*section, lineNumber, params.Position.Character) + + if completions == nil && err != nil { + switch err.(type) { + // Ignore + case parser.PropertyNotFullyTypedError: + return handlers.GetCompletionsForSectionEmptyLine(*section) + default: + return nil, err + } + } + + return completions, nil + } + + panic("TextDocumentCompletion: unexpected line type") +} diff --git a/handlers/wireguard/text-document-did-change.go b/handlers/wireguard/lsp/text-document-did-change.go similarity index 77% rename from handlers/wireguard/text-document-did-change.go rename to handlers/wireguard/lsp/text-document-did-change.go index aa4d3d6..103755c 100644 --- a/handlers/wireguard/text-document-did-change.go +++ b/handlers/wireguard/lsp/text-document-did-change.go @@ -1,7 +1,8 @@ -package wireguard +package lsp import ( "config-lsp/common" + "config-lsp/handlers/wireguard/handlers" "config-lsp/utils" "github.com/tliron/glsp" @@ -15,11 +16,11 @@ func TextDocumentDidChange( content := params.ContentChanges[0].(protocol.TextDocumentContentChangeEventWhole).Text common.ClearDiagnostics(context, params.TextDocument.URI) - parser := documentParserMap[params.TextDocument.URI] - parser.clear() + p := documentParserMap[params.TextDocument.URI] + p.Clear() diagnostics := make([]protocol.Diagnostic, 0) - errors := parser.parseFromString(content) + errors := p.ParseFromString(content) if len(errors) > 0 { diagnostics = append(diagnostics, utils.Map( @@ -30,7 +31,7 @@ func TextDocumentDidChange( )...) } - diagnostics = append(diagnostics, parser.analyze()...) + diagnostics = append(diagnostics, handlers.Analyze(*p)...) if len(diagnostics) > 0 { common.SendDiagnostics(context, params.TextDocument.URI, diagnostics) diff --git a/handlers/wireguard/text-document-did-close.go b/handlers/wireguard/lsp/text-document-did-close.go similarity index 93% rename from handlers/wireguard/text-document-did-close.go rename to handlers/wireguard/lsp/text-document-did-close.go index 84ccc20..3961658 100644 --- a/handlers/wireguard/text-document-did-close.go +++ b/handlers/wireguard/lsp/text-document-did-close.go @@ -1,4 +1,4 @@ -package wireguard +package lsp import ( "github.com/tliron/glsp" diff --git a/handlers/wireguard/text-document-did-open.go b/handlers/wireguard/lsp/text-document-did-open.go similarity index 73% rename from handlers/wireguard/text-document-did-open.go rename to handlers/wireguard/lsp/text-document-did-open.go index 061a0cb..fe003d4 100644 --- a/handlers/wireguard/text-document-did-open.go +++ b/handlers/wireguard/lsp/text-document-did-open.go @@ -1,7 +1,8 @@ -package wireguard +package lsp import ( "config-lsp/common" + "config-lsp/handlers/wireguard/parser" "config-lsp/utils" "github.com/tliron/glsp" @@ -14,10 +15,10 @@ func TextDocumentDidOpen( ) error { common.ClearDiagnostics(context, params.TextDocument.URI) - parser := createWireguardParser() - documentParserMap[params.TextDocument.URI] = &parser + p := parser.CreateWireguardParser() + documentParserMap[params.TextDocument.URI] = &p - errors := parser.parseFromString(params.TextDocument.Text) + errors := p.ParseFromString(params.TextDocument.Text) diagnostics := utils.Map( errors, diff --git a/handlers/wireguard/text-document-hover.go b/handlers/wireguard/lsp/text-document-hover.go similarity index 52% rename from handlers/wireguard/text-document-hover.go rename to handlers/wireguard/lsp/text-document-hover.go index f4a3965..d75dab3 100644 --- a/handlers/wireguard/text-document-hover.go +++ b/handlers/wireguard/lsp/text-document-hover.go @@ -1,6 +1,8 @@ -package wireguard +package lsp import ( + "config-lsp/handlers/wireguard/handlers" + "config-lsp/handlers/wireguard/parser" "strings" "github.com/tliron/glsp" @@ -11,17 +13,21 @@ func TextDocumentHover( context *glsp.Context, params *protocol.HoverParams, ) (*protocol.Hover, error) { - parser := documentParserMap[params.TextDocument.URI] + p := documentParserMap[params.TextDocument.URI] - switch parser.getTypeByLine(params.Position.Line) { - case LineTypeComment: + switch p.GetTypeByLine(params.Position.Line) { + case parser.LineTypeComment: return nil, nil - case LineTypeEmpty: + case parser.LineTypeEmpty: return nil, nil - case LineTypeHeader: + case parser.LineTypeHeader: fallthrough - case LineTypeProperty: - documentation := parser.getHeaderInfo(params.Position.Line, params.Position.Character) + case parser.LineTypeProperty: + documentation := handlers.GetHoverContent( + *p, + params.Position.Line, + params.Position.Character, + ) hover := protocol.Hover{ Contents: protocol.MarkupContent{ diff --git a/handlers/wireguard/lsp/workspace-execute-command.go b/handlers/wireguard/lsp/workspace-execute-command.go new file mode 100644 index 0000000..cbae011 --- /dev/null +++ b/handlers/wireguard/lsp/workspace-execute-command.go @@ -0,0 +1,36 @@ +package lsp + +import ( + "config-lsp/handlers/wireguard/handlers" + "strings" + + "github.com/tliron/glsp" + protocol "github.com/tliron/glsp/protocol_3_16" +) + +func WorkspaceExecuteCommand(context *glsp.Context, params *protocol.ExecuteCommandParams) (*protocol.ApplyWorkspaceEditParams, error) { + _, command, _ := strings.Cut(params.Command, ".") + + switch command { + case string(handlers.CodeActionGeneratePrivateKey): + args := handlers.CodeActionGeneratePrivateKeyArgsFromArguments(params.Arguments[0].(map[string]any)) + + p := documentParserMap[args.URI] + + return args.RunCommand(p) + case string(handlers.CodeActionGeneratePresharedKey): + args := handlers.CodeActionGeneratePresharedKeyArgsFromArguments(params.Arguments[0].(map[string]any)) + + parser := 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 nil, nil +} diff --git a/handlers/wireguard/parser_type_test.go b/handlers/wireguard/parser/wg-parser-type_test.go similarity index 73% rename from handlers/wireguard/parser_type_test.go rename to handlers/wireguard/parser/wg-parser-type_test.go index ff11fad..53ee275 100644 --- a/handlers/wireguard/parser_type_test.go +++ b/handlers/wireguard/parser/wg-parser-type_test.go @@ -1,11 +1,14 @@ -package wireguard +package parser -import "testing" +import ( + "config-lsp/utils" + "testing" +) func TestGetLineTypeWorksCorrectly( t *testing.T, ) { - sample := dedent(` + sample := utils.Dedent(` # A comment at the very top Test=Hello @@ -21,35 +24,35 @@ PublicKey = 1234567890 ; I'm a comment `) - parser := createWireguardParser() - parser.parseFromString(sample) + parser := CreateWireguardParser() + parser.ParseFromString(sample) - lineType := parser.getTypeByLine(0) + lineType := parser.GetTypeByLine(0) if lineType != LineTypeComment { t.Fatalf("getTypeByLine: Expected line 0 to be a comment, but it is %v", lineType) } - lineType = parser.getTypeByLine(1) + lineType = parser.GetTypeByLine(1) if lineType != LineTypeProperty { t.Fatalf("getTypeByLine: Expected line 1 to be a property, but it is %v", lineType) } - lineType = parser.getTypeByLine(2) + lineType = parser.GetTypeByLine(2) if lineType != LineTypeEmpty { t.Fatalf("getTypeByLine: Expected line 2 to be empty, but it is %v", lineType) } - lineType = parser.getTypeByLine(3) + lineType = parser.GetTypeByLine(3) if lineType != LineTypeHeader { t.Fatalf("getTypeByLine: Expected line 3 to be a header, but it is %v", lineType) } - lineType = parser.getTypeByLine(4) + lineType = parser.GetTypeByLine(4) if lineType != LineTypeProperty { t.Fatalf("getTypeByLine: Expected line 4 to be a property, but it is %v", lineType) } - lineType = parser.getTypeByLine(12) + lineType = parser.GetTypeByLine(12) if lineType != LineTypeComment { t.Fatalf("getTypeByLine: Expected line 12 to be a comment, but it is %v", lineType) } @@ -58,7 +61,7 @@ PublicKey = 1234567890 func TestGetBelongingSectionWorksCorrectly( t *testing.T, ) { - sample := dedent(` + sample := utils.Dedent(` # A comment at the very top Test=Hello @@ -74,48 +77,48 @@ PublicKey = 1234567890 ; I'm a comment `) - parser := createWireguardParser() - parser.parseFromString(sample) + parser := CreateWireguardParser() + parser.ParseFromString(sample) - section := parser.getBelongingSectionByLine(0) + section := parser.GetBelongingSectionByLine(0) // Comment if section != nil { t.Fatalf("getBelongingSectionByLine: Expected line 0 to be in no section, but it is in %v", section) } - section = parser.getBelongingSectionByLine(1) + section = parser.GetBelongingSectionByLine(1) if section != parser.Sections[1] { t.Fatalf("getBelongingSectionByLine: Expected line 1 to be in global section, but it is in %v", section) } - section = parser.getBelongingSectionByLine(2) + section = parser.GetBelongingSectionByLine(2) if section != parser.Sections[1] { t.Fatalf("getBelongingSectionByLine: Expected line 2 to be in global section, but it is in %v", section) } - section = parser.getBelongingSectionByLine(3) + section = parser.GetBelongingSectionByLine(3) if section != parser.Sections[2] { t.Fatalf("getBelongingSectionByLine: Expected line 3 to be in section Interface, but it is in %v", section) } - section = parser.getBelongingSectionByLine(4) + section = parser.GetBelongingSectionByLine(4) if section != parser.Sections[2] { t.Fatalf("getBelongingSectionByLine: Expected line 4 to be in section Interface, but it is in %v", section) } - section = parser.getBelongingSectionByLine(6) + section = parser.GetBelongingSectionByLine(6) if section != parser.Sections[2] { t.Fatalf("getBelongingSectionByLine: Expected line 6 to be in section Interface, but it is in %v", section) } - section = parser.getBelongingSectionByLine(10) + section = parser.GetBelongingSectionByLine(10) if section != parser.Sections[3] { t.Fatalf("getBelongingSectionByLine: Expected line 10 to be in section Peer, but it is in %v", section) } - section = parser.getBelongingSectionByLine(12) + section = parser.GetBelongingSectionByLine(12) // Comment if section != nil { diff --git a/handlers/wireguard/wg-parser.go b/handlers/wireguard/parser/wg-parser.go similarity index 60% rename from handlers/wireguard/wg-parser.go rename to handlers/wireguard/parser/wg-parser.go index b93e73f..a85d262 100644 --- a/handlers/wireguard/wg-parser.go +++ b/handlers/wireguard/parser/wg-parser.go @@ -1,138 +1,43 @@ -package wireguard +package parser import ( "config-lsp/common" "regexp" "slices" "strings" - - protocol "github.com/tliron/glsp/protocol_3_16" ) var commentPattern = regexp.MustCompile(`^\s*(;|#)`) var emptyLinePattern = regexp.MustCompile(`^\s*$`) var headerPattern = regexp.MustCompile(`^\s*\[`) -type characterLocation struct { +type CharacterLocation struct { Start uint32 End uint32 } type wireguardLineIndex struct { - Type lineType - BelongingSection *wireguardSection + Type LineType + BelongingSection *WireguardSection } -type wireguardParser struct { +type WireguardParser struct { // : if nil then does not belong to a section - Sections []*wireguardSection + Sections []*WireguardSection // Used to identify where not to show diagnostics - CommentLines map[uint32]struct{} + commentLines map[uint32]struct{} // Indexes - LineIndexes map[uint32]wireguardLineIndex + linesIndexes map[uint32]wireguardLineIndex } -func (p *wireguardParser) getSectionByLine(line uint32) *wireguardSection { - for _, section := range p.Sections { - if section.StartLine <= line && section.EndLine >= line { - return section - } - } - - return nil +func (p *WireguardParser) Clear() { + p.Sections = []*WireguardSection{} + p.commentLines = map[uint32]struct{}{} + p.linesIndexes = map[uint32]wireguardLineIndex{} } -// Search for a property by name -// Returns (line number, property) -func (p *wireguardParser) fetchPropertyByName(name string) (*uint32, *wireguardProperty) { - for _, section := range p.Sections { - for lineNumber, property := range section.Properties { - if property.Key.Name == name { - return &lineNumber, &property - } - } - } - - return nil, nil -} - -func (p *wireguardParser) clear() { - p.Sections = []*wireguardSection{} - p.CommentLines = map[uint32]struct{}{} - p.LineIndexes = map[uint32]wireguardLineIndex{} -} - -func (p wireguardParser) getInterfaceSection() (*wireguardSection, bool) { - for _, section := range p.Sections { - if section.Name != nil && *section.Name == "Interface" { - return section, true - } - } - - return nil, false -} - -func getHeaderCompletion(name string, documentation string) protocol.CompletionItem { - textFormat := protocol.InsertTextFormatPlainText - kind := protocol.CompletionItemKindEnum - - insertText := "[" + name + "]\n" - - return protocol.CompletionItem{ - Label: "[" + name + "]", - InsertTextFormat: &textFormat, - InsertText: &insertText, - Kind: &kind, - Documentation: &documentation, - } -} - -func (p wireguardParser) getRootCompletionsForEmptyLine() []protocol.CompletionItem { - completions := []protocol.CompletionItem{} - - if _, found := p.getInterfaceSection(); !found { - completions = append(completions, getHeaderCompletion("Interface", headerInterfaceEnum.Documentation)) - } - - completions = append(completions, getHeaderCompletion("Peer", headerPeerEnum.Documentation)) - - return completions -} - -func createWireguardParser() wireguardParser { - parser := wireguardParser{} - parser.clear() - - return parser -} - -type lineType string - -const ( - LineTypeComment lineType = "comment" - LineTypeEmpty lineType = "empty" - LineTypeHeader lineType = "header" - LineTypeProperty lineType = "property" -) - -func getLineType(line string) lineType { - if commentPattern.MatchString(line) { - return LineTypeComment - } - - if emptyLinePattern.MatchString(line) { - return LineTypeEmpty - } - - if headerPattern.MatchString(line) { - return LineTypeHeader - } - - return LineTypeProperty -} - -func (p *wireguardParser) parseFromString(input string) []common.ParseError { +func (p *WireguardParser) ParseFromString(input string) []common.ParseError { var errors []common.ParseError lines := strings.Split( input, @@ -141,7 +46,7 @@ func (p *wireguardParser) parseFromString(input string) []common.ParseError { slices.Reverse(lines) - collectedProperties := wireguardProperties{} + collectedProperties := WireguardProperties{} var lastPropertyLine *uint32 for index, line := range lines { @@ -150,8 +55,8 @@ func (p *wireguardParser) parseFromString(input string) []common.ParseError { switch lineType { case LineTypeComment: - p.CommentLines[currentLineNumber] = struct{}{} - p.LineIndexes[currentLineNumber] = wireguardLineIndex{ + p.commentLines[currentLineNumber] = struct{}{} + p.linesIndexes[currentLineNumber] = wireguardLineIndex{ Type: LineTypeComment, BelongingSection: nil, } @@ -184,7 +89,7 @@ func (p *wireguardParser) parseFromString(input string) []common.ParseError { lastLine = *lastPropertyLine } - section := createWireguardSection( + section := CreateWireguardSection( currentLineNumber, lastLine, line, @@ -195,23 +100,23 @@ func (p *wireguardParser) parseFromString(input string) []common.ParseError { // Add indexes for lineNumber := range collectedProperties { - p.LineIndexes[lineNumber] = wireguardLineIndex{ + p.linesIndexes[lineNumber] = wireguardLineIndex{ Type: LineTypeProperty, BelongingSection: §ion, } } - p.LineIndexes[currentLineNumber] = wireguardLineIndex{ + p.linesIndexes[currentLineNumber] = wireguardLineIndex{ Type: LineTypeHeader, BelongingSection: §ion, } // Reset - collectedProperties = wireguardProperties{} + collectedProperties = WireguardProperties{} lastPropertyLine = nil } } - var emptySection *wireguardSection + var emptySection *WireguardSection if len(collectedProperties) > 0 { var endLine uint32 @@ -222,7 +127,7 @@ func (p *wireguardParser) parseFromString(input string) []common.ParseError { endLine = p.Sections[len(p.Sections)-1].StartLine } - emptySection = &wireguardSection{ + emptySection = &WireguardSection{ StartLine: 0, EndLine: endLine, Properties: collectedProperties, @@ -231,7 +136,7 @@ func (p *wireguardParser) parseFromString(input string) []common.ParseError { p.Sections = append(p.Sections, emptySection) for lineNumber := range collectedProperties { - p.LineIndexes[lineNumber] = wireguardLineIndex{ + p.linesIndexes[lineNumber] = wireguardLineIndex{ Type: LineTypeProperty, BelongingSection: emptySection, } @@ -247,7 +152,7 @@ func (p *wireguardParser) parseFromString(input string) []common.ParseError { // Add empty section if endLine != 0 { - emptySection = &wireguardSection{ + emptySection = &WireguardSection{ StartLine: 0, EndLine: endLine, Properties: collectedProperties, @@ -256,11 +161,11 @@ func (p *wireguardParser) parseFromString(input string) []common.ParseError { p.Sections = append(p.Sections, emptySection) for newLine := uint32(0); newLine < endLine; newLine++ { - if _, found := p.LineIndexes[newLine]; found { + if _, found := p.linesIndexes[newLine]; found { continue } - p.LineIndexes[newLine] = wireguardLineIndex{ + p.linesIndexes[newLine] = wireguardLineIndex{ Type: LineTypeEmpty, BelongingSection: emptySection, } @@ -284,11 +189,11 @@ func (p *wireguardParser) parseFromString(input string) []common.ParseError { } for newLine := section.StartLine; newLine < endLine; newLine++ { - if _, found := p.LineIndexes[newLine]; found { + if _, found := p.linesIndexes[newLine]; found { continue } - p.LineIndexes[newLine] = wireguardLineIndex{ + p.linesIndexes[newLine] = wireguardLineIndex{ Type: LineTypeEmpty, BelongingSection: section, } @@ -298,13 +203,47 @@ func (p *wireguardParser) parseFromString(input string) []common.ParseError { return errors } -func (p wireguardParser) getTypeByLine(line uint32) lineType { +func (p *WireguardParser) GetSectionByLine(line uint32) *WireguardSection { + for _, section := range p.Sections { + if section.StartLine <= line && section.EndLine >= line { + return section + } + } + + return nil +} + +// Search for a property by name +// Returns (line number, property) +func (p *WireguardParser) FindFirstPropertyByName(name string) (*uint32, *WireguardProperty) { + for _, section := range p.Sections { + for lineNumber, property := range section.Properties { + if property.Key.Name == name { + return &lineNumber, &property + } + } + } + + return nil, nil +} + +func (p WireguardParser) GetInterfaceSection() (*WireguardSection, bool) { + for _, section := range p.Sections { + if section.Name != nil && *section.Name == "Interface" { + return section, true + } + } + + return nil, false +} + +func (p WireguardParser) GetTypeByLine(line uint32) LineType { // Check if line is a comment - if _, found := p.CommentLines[line]; found { + if _, found := p.commentLines[line]; found { return LineTypeComment } - if info, found := p.LineIndexes[line]; found { + if info, found := p.linesIndexes[line]; found { return info.Type } @@ -320,22 +259,22 @@ func (p wireguardParser) getTypeByLine(line uint32) lineType { // [Peer] // // This would return the section [Interface] -func (p *wireguardParser) getBelongingSectionByLine(line uint32) *wireguardSection { - if info, found := p.LineIndexes[line]; found { +func (p *WireguardParser) GetBelongingSectionByLine(line uint32) *WireguardSection { + if info, found := p.linesIndexes[line]; found { return info.BelongingSection } return nil } -func (p *wireguardParser) getPropertyByLine(line uint32) (*wireguardSection, *wireguardProperty) { - section := p.getSectionByLine(line) +func (p *WireguardParser) GetPropertyByLine(line uint32) (*WireguardSection, *WireguardProperty) { + section := p.GetSectionByLine(line) if section == nil || section.Name == nil { return nil, nil } - property, _ := section.findProperty(line) + property, _ := section.GetPropertyByLine(line) if property == nil { return nil, nil @@ -344,8 +283,8 @@ func (p *wireguardParser) getPropertyByLine(line uint32) (*wireguardSection, *wi return section, property } -func (p *wireguardParser) getSectionsByName(name string) []*wireguardSection { - var sections []*wireguardSection +func (p *WireguardParser) GetSectionsByName(name string) []*WireguardSection { + var sections []*WireguardSection for _, section := range p.Sections { if section.Name != nil && *section.Name == name { @@ -355,3 +294,35 @@ func (p *wireguardParser) getSectionsByName(name string) []*wireguardSection { return sections } + +func CreateWireguardParser() WireguardParser { + parser := WireguardParser{} + parser.Clear() + + return parser +} + +type LineType string + +const ( + LineTypeComment LineType = "comment" + LineTypeEmpty LineType = "empty" + LineTypeHeader LineType = "header" + LineTypeProperty LineType = "property" +) + +func getLineType(line string) LineType { + if commentPattern.MatchString(line) { + return LineTypeComment + } + + if emptyLinePattern.MatchString(line) { + return LineTypeEmpty + } + + if headerPattern.MatchString(line) { + return LineTypeHeader + } + + return LineTypeProperty +} diff --git a/handlers/wireguard/parser_test.go b/handlers/wireguard/parser/wg-parser_test.go similarity index 77% rename from handlers/wireguard/parser_test.go rename to handlers/wireguard/parser/wg-parser_test.go index cd7addc..eeee672 100644 --- a/handlers/wireguard/parser_test.go +++ b/handlers/wireguard/parser/wg-parser_test.go @@ -1,26 +1,16 @@ -package wireguard +package parser import ( - "strings" + "config-lsp/utils" "testing" "github.com/k0kubun/pp" ) -func dedent(s string) string { - return strings.TrimLeft(s, "\n") -} - -func keyExists[T comparable, V any](keys map[T]V, key T) bool { - _, found := keys[key] - - return found -} - func TestValidWildTestWorksFine( t *testing.T, ) { - sample := dedent(` + sample := utils.Dedent(` [Interface] PrivateKey = 1234567890 Address = 192.168.1.0/24 @@ -34,15 +24,15 @@ Endpoint = 1.2.3.4 ; I'm just a comment PublicKey = 5555 `) - parser := createWireguardParser() - errors := parser.parseFromString(sample) + parser := CreateWireguardParser() + errors := parser.ParseFromString(sample) if len(errors) > 0 { t.Fatalf("parseFromString failed with error %v", errors) } - if !(len(parser.CommentLines) == 1 && keyExists(parser.CommentLines, 4)) { - t.Fatalf("parseFromString failed to collect comment lines %v", parser.CommentLines) + if !(len(parser.commentLines) == 1 && utils.KeyExists(parser.commentLines, 4)) { + t.Fatalf("parseFromString failed to collect comment lines %v", parser.commentLines) } if !((len(parser.Sections) == 3) && (*parser.Sections[0].Name == "Interface") && (*parser.Sections[1].Name == "Peer") && (*parser.Sections[2].Name == "Peer")) { @@ -70,18 +60,18 @@ PublicKey = 5555 } // Check if line indexes are correct - if !(parser.LineIndexes[0].Type == LineTypeHeader && - parser.LineIndexes[1].Type == LineTypeProperty && - parser.LineIndexes[2].Type == LineTypeProperty && - parser.LineIndexes[3].Type == LineTypeEmpty && - parser.LineIndexes[4].Type == LineTypeComment && - parser.LineIndexes[5].Type == LineTypeHeader && - parser.LineIndexes[6].Type == LineTypeProperty && - parser.LineIndexes[7].Type == LineTypeProperty && - parser.LineIndexes[8].Type == LineTypeEmpty && - parser.LineIndexes[9].Type == LineTypeHeader && - parser.LineIndexes[10].Type == LineTypeProperty) { - pp.Println(parser.LineIndexes) + if !(parser.linesIndexes[0].Type == LineTypeHeader && + parser.linesIndexes[1].Type == LineTypeProperty && + parser.linesIndexes[2].Type == LineTypeProperty && + parser.linesIndexes[3].Type == LineTypeEmpty && + parser.linesIndexes[4].Type == LineTypeComment && + parser.linesIndexes[5].Type == LineTypeHeader && + parser.linesIndexes[6].Type == LineTypeProperty && + parser.linesIndexes[7].Type == LineTypeProperty && + parser.linesIndexes[8].Type == LineTypeEmpty && + parser.linesIndexes[9].Type == LineTypeHeader && + parser.linesIndexes[10].Type == LineTypeProperty) { + pp.Println(parser.linesIndexes) t.Fatal("parseFromString: Invalid line indexes") } } @@ -89,15 +79,15 @@ PublicKey = 5555 func TestEmptySectionAtStartWorksFine( t *testing.T, ) { - sample := dedent(` + sample := utils.Dedent(` [Interface] [Peer] PublicKey = 1234567890 `) - parser := createWireguardParser() - errors := parser.parseFromString(sample) + parser := CreateWireguardParser() + errors := parser.ParseFromString(sample) if len(errors) > 0 { t.Fatalf("parseFromString failed with error %v", errors) @@ -115,7 +105,7 @@ PublicKey = 1234567890 func TestEmptySectionAtEndWorksFine( t *testing.T, ) { - sample := dedent(` + sample := utils.Dedent(` [Inteface] PrivateKey = 1234567890 @@ -123,8 +113,8 @@ PrivateKey = 1234567890 # Just sneaking in here, hehe `) - parser := createWireguardParser() - errors := parser.parseFromString(sample) + parser := CreateWireguardParser() + errors := parser.ParseFromString(sample) if len(errors) > 0 { t.Fatalf("parseFromString failed with error %v", errors) @@ -138,19 +128,19 @@ PrivateKey = 1234567890 t.Fatalf("parseFromString failed to collect properties %v", parser.Sections) } - if !(len(parser.CommentLines) == 1 && keyExists(parser.CommentLines, 4)) { - t.Fatalf("parseFromString failed to collect comment lines %v", parser.CommentLines) + if !(len(parser.commentLines) == 1 && utils.KeyExists(parser.commentLines, 4)) { + t.Fatalf("parseFromString failed to collect comment lines %v", parser.commentLines) } } func TestEmptyFileWorksFine( t *testing.T, ) { - sample := dedent(` + sample := utils.Dedent(` `) - parser := createWireguardParser() - errors := parser.parseFromString(sample) + parser := CreateWireguardParser() + errors := parser.ParseFromString(sample) if len(errors) > 0 { t.Fatalf("parseFromString failed with error %v", errors) @@ -164,15 +154,15 @@ func TestEmptyFileWorksFine( func TestPartialSectionWithNoPropertiesWorksFine( t *testing.T, ) { - sample := dedent(` + sample := utils.Dedent(` [Inte [Peer] PublicKey = 1234567890 `) - parser := createWireguardParser() - errors := parser.parseFromString(sample) + parser := CreateWireguardParser() + errors := parser.ParseFromString(sample) if len(errors) > 0 { t.Fatalf("parseFromString failed with error %v", errors) @@ -186,8 +176,8 @@ PublicKey = 1234567890 t.Fatalf("parseFromString failed to collect properties: %v", parser.Sections) } - if !(len(parser.CommentLines) == 0) { - t.Fatalf("parseFromString failed to collect comment lines: %v", parser.CommentLines) + if !(len(parser.commentLines) == 0) { + t.Fatalf("parseFromString failed to collect comment lines: %v", parser.commentLines) } if !(parser.Sections[1].Properties[3].Key.Name == "PublicKey") { @@ -198,15 +188,15 @@ PublicKey = 1234567890 func TestPartialSectionWithPropertiesWorksFine( t *testing.T, ) { - sample := dedent(` + sample := utils.Dedent(` [Inte PrivateKey = 1234567890 [Peer] `) - parser := createWireguardParser() - errors := parser.parseFromString(sample) + parser := CreateWireguardParser() + errors := parser.ParseFromString(sample) if len(errors) > 0 { t.Fatalf("parseFromString failed with error: %v", errors) @@ -228,12 +218,12 @@ PrivateKey = 1234567890 func TestFileWithOnlyComments( t *testing.T, ) { - sample := dedent(` + sample := utils.Dedent(` # This is a comment # Another comment `) - parser := createWireguardParser() - errors := parser.parseFromString(sample) + parser := CreateWireguardParser() + errors := parser.ParseFromString(sample) if len(errors) > 0 { t.Fatalf("parseFromString failed with error: %v", errors) @@ -243,26 +233,26 @@ func TestFileWithOnlyComments( t.Fatalf("parseFromString failed to collect sections: %v", parser.Sections) } - if !(len(parser.CommentLines) == 2) { - t.Fatalf("parseFromString failed to collect comment lines: %v", parser.CommentLines) + if !(len(parser.commentLines) == 2) { + t.Fatalf("parseFromString failed to collect comment lines: %v", parser.commentLines) } - if !(keyExists(parser.CommentLines, 0) && keyExists(parser.CommentLines, 1)) { - t.Fatalf("parseFromString failed to collect comment lines: %v", parser.CommentLines) + if !(utils.KeyExists(parser.commentLines, 0) && utils.KeyExists(parser.commentLines, 1)) { + t.Fatalf("parseFromString failed to collect comment lines: %v", parser.commentLines) } } func TestMultipleSectionsNoProperties( t *testing.T, ) { - sample := dedent(` + sample := utils.Dedent(` [Interface] [Peer] [Peer] `) - parser := createWireguardParser() - errors := parser.parseFromString(sample) + parser := CreateWireguardParser() + errors := parser.ParseFromString(sample) if len(errors) > 0 { t.Fatalf("parseFromString failed with error: %v", errors) @@ -282,15 +272,14 @@ func TestMultipleSectionsNoProperties( func TestWildTest1WorksCorrectly( t *testing.T, ) { - sample := dedent(` + sample := utils.Dedent(` [Interface] DNS=1.1.1.1 `) - - parser := createWireguardParser() - errors := parser.parseFromString(sample) + parser := CreateWireguardParser() + errors := parser.ParseFromString(sample) if len(errors) > 0 { t.Fatalf("parseFromString failed with error: %v", errors) @@ -312,8 +301,8 @@ DNS=1.1.1.1 t.Fatalf("parseFromString failed to collect properties of section 0: %v", parser.Sections[0].Properties) } - if !(len(parser.CommentLines) == 0) { - t.Fatalf("parseFromString failed to collect comment lines: %v", parser.CommentLines) + if !(len(parser.commentLines) == 0) { + t.Fatalf("parseFromString failed to collect comment lines: %v", parser.commentLines) } if !(parser.Sections[0].StartLine == 0 && parser.Sections[0].EndLine == 1) { @@ -324,12 +313,12 @@ DNS=1.1.1.1 func TestPartialKeyWorksCorrectly( t *testing.T, ) { - sample := dedent(` + sample := utils.Dedent(` [Interface] DNS `) - parser := createWireguardParser() - errors := parser.parseFromString(sample) + parser := CreateWireguardParser() + errors := parser.ParseFromString(sample) if len(errors) > 0 { t.Fatalf("parseFromString failed with error: %v", errors) @@ -347,12 +336,12 @@ DNS func TestPartialValueWithSeparatorWorksCorrectly( t *testing.T, ) { - sample := dedent(` + sample := utils.Dedent(` [Interface] DNS= `) - parser := createWireguardParser() - errors := parser.parseFromString(sample) + parser := CreateWireguardParser() + errors := parser.ParseFromString(sample) if len(errors) > 0 { t.Fatalf("parseFromString failed with error: %v", errors) diff --git a/handlers/wireguard/wg-property.go b/handlers/wireguard/parser/wg-property.go similarity index 51% rename from handlers/wireguard/wg-property.go rename to handlers/wireguard/parser/wg-property.go index 5913c92..5deae64 100644 --- a/handlers/wireguard/wg-property.go +++ b/handlers/wireguard/parser/wg-property.go @@ -1,4 +1,4 @@ -package wireguard +package parser import ( docvalues "config-lsp/doc-values" @@ -11,27 +11,27 @@ import ( var linePattern = regexp.MustCompile(`^\s*(?P.+?)\s*(?P=)\s*(?P\S.*?)?\s*(?:(?:;|#).*)?\s*$`) -type wireguardPropertyKey struct { - Location characterLocation +type WireguardPropertyKey struct { + Location CharacterLocation Name string } -type wireguardPropertyValue struct { - Location characterLocation +type WireguardPropertyValue struct { + Location CharacterLocation Value string } -type wireguardPropertySeparator struct { - Location characterLocation +type WireguardPropertySeparator struct { + Location CharacterLocation } -type wireguardProperty struct { - Key wireguardPropertyKey - Separator *wireguardPropertySeparator - Value *wireguardPropertyValue +type WireguardProperty struct { + Key WireguardPropertyKey + Separator *WireguardPropertySeparator + Value *WireguardPropertyValue } -func (p wireguardProperty) String() string { +func (p WireguardProperty) String() string { if p.Value == nil { return p.Key.Name } @@ -39,7 +39,7 @@ func (p wireguardProperty) String() string { return p.Key.Name + "=" + p.Value.Value } -func (p wireguardProperty) getLineRange(line uint32) protocol.Range { +func (p WireguardProperty) GetLineRange(line uint32) protocol.Range { return protocol.Range{ Start: protocol.Position{ Line: line, @@ -52,7 +52,44 @@ func (p wireguardProperty) getLineRange(line uint32) protocol.Range { } } -func createWireguardProperty(line string) (*wireguardProperty, error) { +func (p WireguardProperty) GetInsertRange(line uint32) protocol.Range { + var insertPosition uint32 = p.Separator.Location.End + var length uint32 = 0 + + if p.Value != nil { + insertPosition = p.Value.Location.Start - 1 + // Length of the value; +1 because of the starting space + length = (p.Value.Location.End - p.Value.Location.Start) + 1 + } + + return protocol.Range{ + Start: protocol.Position{ + Line: line, + Character: insertPosition, + }, + End: protocol.Position{ + Line: line, + Character: insertPosition + length, + }, + } +} + +// WireguardProperties []: +type WireguardProperties map[uint32]WireguardProperty + +func (p *WireguardProperties) AddLine(lineNumber uint32, line string) error { + property, err := CreateWireguardProperty(line) + + if err != nil { + return err + } + + (*p)[lineNumber] = *property + + return nil +} + +func CreateWireguardProperty(line string) (*WireguardProperty, error) { if !strings.Contains(line, "=") { indexes := utils.GetTrimIndex(line) @@ -61,10 +98,10 @@ func createWireguardProperty(line string) (*wireguardProperty, error) { return nil, &docvalues.MalformedLineError{} } - return &wireguardProperty{ - Key: wireguardPropertyKey{ + return &WireguardProperty{ + Key: WireguardPropertyKey{ Name: line[indexes[0]:indexes[1]], - Location: characterLocation{ + Location: CharacterLocation{ Start: uint32(indexes[0]), End: uint32(indexes[1]), }, @@ -80,8 +117,8 @@ func createWireguardProperty(line string) (*wireguardProperty, error) { keyStart := uint32(indexes[2]) keyEnd := uint32(indexes[3]) - key := wireguardPropertyKey{ - Location: characterLocation{ + key := WireguardPropertyKey{ + Location: CharacterLocation{ Start: keyStart, End: keyEnd, }, @@ -90,22 +127,22 @@ func createWireguardProperty(line string) (*wireguardProperty, error) { separatorStart := uint32(indexes[4]) separatorEnd := uint32(indexes[5]) - separator := wireguardPropertySeparator{ - Location: characterLocation{ + separator := WireguardPropertySeparator{ + Location: CharacterLocation{ Start: separatorStart, End: separatorEnd, }, } - var value *wireguardPropertyValue + var value *WireguardPropertyValue if indexes[6] != -1 && indexes[7] != -1 { // value exists valueStart := uint32(indexes[6]) valueEnd := uint32(indexes[7]) - value = &wireguardPropertyValue{ - Location: characterLocation{ + value = &WireguardPropertyValue{ + Location: CharacterLocation{ Start: valueStart, End: valueEnd, }, @@ -113,24 +150,9 @@ func createWireguardProperty(line string) (*wireguardProperty, error) { } } - return &wireguardProperty{ + return &WireguardProperty{ Key: key, Separator: &separator, Value: value, }, nil } - -// []: -type wireguardProperties map[uint32]wireguardProperty - -func (p *wireguardProperties) AddLine(lineNumber uint32, line string) error { - property, err := createWireguardProperty(line) - - if err != nil { - return err - } - - (*p)[lineNumber] = *property - - return nil -} diff --git a/handlers/wireguard/parser/wg-section.go b/handlers/wireguard/parser/wg-section.go new file mode 100644 index 0000000..6441d93 --- /dev/null +++ b/handlers/wireguard/parser/wg-section.go @@ -0,0 +1,120 @@ +package parser + +import ( + "fmt" + "regexp" + + protocol "github.com/tliron/glsp/protocol_3_16" +) + +type PropertyNotFoundError struct{} + +func (e PropertyNotFoundError) Error() string { + return "Property not found" +} + +type PropertyNotFullyTypedError struct{} + +func (e PropertyNotFullyTypedError) Error() string { + return "Property not fully typed" +} + +type WireguardSection struct { + Name *string + StartLine uint32 + EndLine uint32 + Properties WireguardProperties +} + +func (s WireguardSection) String() string { + var name string + + if s.Name == nil { + name = "" + } else { + name = *s.Name + } + + return fmt.Sprintf("[%s]; %d-%d: %v", name, s.StartLine, s.EndLine, s.Properties) +} + +func (s WireguardSection) GetHeaderLineRange() protocol.Range { + return protocol.Range{ + Start: protocol.Position{ + Line: s.StartLine, + Character: 0, + }, + End: protocol.Position{ + Line: s.StartLine, + Character: 99999999, + }, + } +} + +func (s WireguardSection) GetRange() protocol.Range { + return protocol.Range{ + Start: protocol.Position{ + Line: s.StartLine, + Character: 0, + }, + End: protocol.Position{ + Line: s.EndLine, + Character: 99999999, + }, + } +} + +func (s *WireguardSection) FetchFirstProperty(name string) (*uint32, *WireguardProperty) { + for line, property := range s.Properties { + if property.Key.Name == name { + return &line, &property + } + } + + return nil, nil +} + +func (s *WireguardSection) ExistsProperty(name string) bool { + _, property := s.FetchFirstProperty(name) + + return property != nil +} + +func (s *WireguardSection) GetPropertyByLine(lineNumber uint32) (*WireguardProperty, error) { + property, found := s.Properties[lineNumber] + + if !found { + return nil, PropertyNotFoundError{} + } + + return &property, nil +} + +var validHeaderPattern = regexp.MustCompile(`^\s*\[(?P
.+?)\]\s*$`) + +// Create a new create section +// Return (, ) +func CreateWireguardSection( + startLine uint32, + endLine uint32, + headerLine string, + props WireguardProperties, +) WireguardSection { + match := validHeaderPattern.FindStringSubmatch(headerLine) + + var header string + + if match == nil { + // Still typing it + header = headerLine[1:] + } else { + header = match[1] + } + + return WireguardSection{ + Name: &header, + StartLine: startLine, + EndLine: endLine, + Properties: props, + } +} diff --git a/handlers/wireguard/shared.go b/handlers/wireguard/shared.go deleted file mode 100644 index 075f798..0000000 --- a/handlers/wireguard/shared.go +++ /dev/null @@ -1,7 +0,0 @@ -package wireguard - -import ( - protocol "github.com/tliron/glsp/protocol_3_16" -) - -var documentParserMap = map[protocol.DocumentUri]*wireguardParser{} diff --git a/handlers/wireguard/text-document-completion.go b/handlers/wireguard/text-document-completion.go deleted file mode 100644 index 3a8c06d..0000000 --- a/handlers/wireguard/text-document-completion.go +++ /dev/null @@ -1,45 +0,0 @@ -package wireguard - -import ( - "github.com/tliron/glsp" - protocol "github.com/tliron/glsp/protocol_3_16" -) - -func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionParams) (any, error) { - parser := documentParserMap[params.TextDocument.URI] - - lineNumber := params.Position.Line - - section := parser.getBelongingSectionByLine(lineNumber) - lineType := parser.getTypeByLine(lineNumber) - - switch lineType { - case LineTypeComment: - return nil, nil - case LineTypeHeader: - return parser.getRootCompletionsForEmptyLine(), nil - case LineTypeEmpty: - if section.Name == nil { - // Root completions - return parser.getRootCompletionsForEmptyLine(), nil - } - - return section.getCompletionsForEmptyLine() - case LineTypeProperty: - completions, err := section.getCompletionsForPropertyLine(lineNumber, params.Position.Character) - - if completions == nil && err != nil { - switch err.(type) { - // Ignore - case propertyNotFullyTypedError: - return section.getCompletionsForEmptyLine() - default: - return nil, err - } - } - - return completions, nil - } - - panic("TextDocumentCompletion: unexpected line type") -} diff --git a/handlers/wireguard/wg-section.go b/handlers/wireguard/wg-section.go deleted file mode 100644 index f84a13f..0000000 --- a/handlers/wireguard/wg-section.go +++ /dev/null @@ -1,249 +0,0 @@ -package wireguard - -import ( - docvalues "config-lsp/doc-values" - "config-lsp/utils" - "fmt" - "maps" - "regexp" - - protocol "github.com/tliron/glsp/protocol_3_16" -) - -type propertyNotFoundError struct{} - -func (e propertyNotFoundError) Error() string { - return "Property not found" -} - -type propertyNotFullyTypedError struct{} - -func (e propertyNotFullyTypedError) Error() string { - return "Property not fully typed" -} - -type wireguardSectionType uint - -const ( - wireguardSectionUnknownType wireguardSectionType = 0 - wireguardSectionInterfaceType wireguardSectionType = 1 - wireguardSectionPeerType wireguardSectionType = 2 -) - -type wireguardSection struct { - Name *string - StartLine uint32 - EndLine uint32 - Properties wireguardProperties -} - -func (s wireguardSection) getHeaderLineRange() protocol.Range { - return protocol.Range{ - Start: protocol.Position{ - Line: s.StartLine, - Character: 0, - }, - End: protocol.Position{ - Line: s.StartLine, - Character: 99999999, - }, - } -} - -func (s wireguardSection) getRange() protocol.Range { - return protocol.Range{ - Start: protocol.Position{ - Line: s.StartLine, - Character: 0, - }, - End: protocol.Position{ - Line: s.EndLine, - Character: 99999999, - }, - } -} - -func (s wireguardSection) String() string { - var name string - - if s.Name == nil { - name = "" - } else { - name = *s.Name - } - - return fmt.Sprintf("[%s]; %d-%d: %v", name, s.StartLine, s.EndLine, s.Properties) -} - -func (s *wireguardSection) fetchFirstProperty(name string) (*uint32, *wireguardProperty) { - for line, property := range s.Properties { - if property.Key.Name == name { - return &line, &property - } - } - - return nil, nil -} - -func (s *wireguardSection) existsProperty(name string) bool { - _, property := s.fetchFirstProperty(name) - - return property != nil -} - -func (s *wireguardSection) findProperty(lineNumber uint32) (*wireguardProperty, error) { - property, found := s.Properties[lineNumber] - - if !found { - return nil, propertyNotFoundError{} - } - - return &property, nil -} - -func (s wireguardSection) getCompletionsForEmptyLine() ([]protocol.CompletionItem, error) { - if s.Name == nil { - return nil, nil - } - - options := make(map[string]docvalues.DocumentationValue) - - switch *s.Name { - case "Interface": - maps.Copy(options, interfaceOptions) - - // Remove existing options - for _, property := range s.Properties { - if _, found := interfaceAllowedDuplicateFields[property.Key.Name]; found { - continue - } - - delete(options, property.Key.Name) - } - case "Peer": - maps.Copy(options, peerOptions) - - // Remove existing options - for _, property := range s.Properties { - if _, found := peerAllowedDuplicateFields[property.Key.Name]; found { - continue - } - - delete(options, property.Key.Name) - } - } - - kind := protocol.CompletionItemKindProperty - - return utils.MapMapToSlice( - options, - func(optionName string, value docvalues.DocumentationValue) protocol.CompletionItem { - insertText := optionName + " = " - - return protocol.CompletionItem{ - Kind: &kind, - Documentation: value.Documentation, - Label: optionName, - InsertText: &insertText, - } - }, - ), nil -} - -func getSeparatorCompletion(property wireguardProperty, character uint32) ([]protocol.CompletionItem, error) { - var insertText string - - if character == property.Key.Location.End { - insertText = property.Key.Name + " = " - } else { - insertText = "= " - } - - kind := protocol.CompletionItemKindValue - - return []protocol.CompletionItem{ - { - Label: insertText, - InsertText: &insertText, - Kind: &kind, - }, - }, propertyNotFullyTypedError{} -} - -func (p wireguardSection) getCompletionsForPropertyLine( - lineNumber uint32, - character uint32, -) ([]protocol.CompletionItem, error) { - property, err := p.findProperty(lineNumber) - - if err != nil { - return nil, err - } - - if p.Name == nil { - return nil, propertyNotFoundError{} - } - - options, found := optionsHeaderMap[*p.Name] - - if !found { - return nil, propertyNotFoundError{} - } - - if property.Separator == nil { - if _, found := options[property.Key.Name]; found { - return getSeparatorCompletion(*property, character) - } - // Get empty line completions - return nil, propertyNotFullyTypedError{} - } - - option, found := options[property.Key.Name] - - if !found { - if character < property.Separator.Location.Start { - return nil, propertyNotFullyTypedError{} - } else { - return nil, propertyNotFoundError{} - } - } - - if property.Value == nil { - if character >= property.Separator.Location.End { - return option.FetchCompletions("", 0), nil - } - } - - relativeCursor := character - property.Value.Location.Start - - return option.FetchCompletions(property.Value.Value, relativeCursor), nil -} - -var validHeaderPattern = regexp.MustCompile(`^\s*\[(?P
.+?)\]\s*$`) - -// Create a new create section -// Return (, ) -func createWireguardSection( - startLine uint32, - endLine uint32, - headerLine string, - props wireguardProperties, -) wireguardSection { - match := validHeaderPattern.FindStringSubmatch(headerLine) - - var header string - - if match == nil { - // Still typing it - header = headerLine[1:] - } else { - header = match[1] - } - - return wireguardSection{ - Name: &header, - StartLine: startLine, - EndLine: endLine, - Properties: props, - } -} diff --git a/handlers/wireguard/workspace-execute-command.go b/handlers/wireguard/workspace-execute-command.go deleted file mode 100644 index 296e2a5..0000000 --- a/handlers/wireguard/workspace-execute-command.go +++ /dev/null @@ -1,35 +0,0 @@ -package wireguard - -import ( - "strings" - - "github.com/tliron/glsp" - protocol "github.com/tliron/glsp/protocol_3_16" -) - -func WorkspaceExecuteCommand(context *glsp.Context, params *protocol.ExecuteCommandParams) (*protocol.ApplyWorkspaceEditParams, error) { - _, command, _ := strings.Cut(params.Command, ".") - - switch command { - case string(codeActionGeneratePrivateKey): - args := codeActionGeneratePrivateKeyArgsFromArguments(params.Arguments[0].(map[string]any)) - - parser := documentParserMap[args.URI] - - return parser.runGeneratePrivateKey(args) - case string(codeActionGeneratePresharedKey): - args := codeActionGeneratePresharedKeyArgsFromArguments(params.Arguments[0].(map[string]any)) - - parser := documentParserMap[args.URI] - - return parser.runGeneratePresharedKey(args) - case string(codeActionAddKeepalive): - args := codeActionAddKeepaliveArgsFromArguments(params.Arguments[0].(map[string]any)) - - parser := documentParserMap[args.URI] - - return parser.runAddKeepalive(args) - } - - return nil, nil -} diff --git a/root-handler/text-document-code-action.go b/root-handler/text-document-code-action.go index a2f98e5..5504676 100644 --- a/root-handler/text-document-code-action.go +++ b/root-handler/text-document-code-action.go @@ -1,8 +1,7 @@ package roothandler import ( - "config-lsp/handlers/wireguard" - + wireguard "config-lsp/handlers/wireguard/lsp" "github.com/tliron/glsp" protocol "github.com/tliron/glsp/protocol_3_16" ) diff --git a/root-handler/text-document-completion.go b/root-handler/text-document-completion.go index 24adf54..0ee3292 100644 --- a/root-handler/text-document-completion.go +++ b/root-handler/text-document-completion.go @@ -2,7 +2,7 @@ package roothandler import ( "config-lsp/handlers/fstab" - "config-lsp/handlers/wireguard" + wireguard "config-lsp/handlers/wireguard/lsp" "github.com/tliron/glsp" protocol "github.com/tliron/glsp/protocol_3_16" diff --git a/root-handler/text-document-did-change.go b/root-handler/text-document-did-change.go index 21d51fc..5b4af9e 100644 --- a/root-handler/text-document-did-change.go +++ b/root-handler/text-document-did-change.go @@ -2,7 +2,7 @@ package roothandler import ( "config-lsp/handlers/fstab" - "config-lsp/handlers/wireguard" + wireguard "config-lsp/handlers/wireguard/lsp" "github.com/tliron/glsp" protocol "github.com/tliron/glsp/protocol_3_16" diff --git a/root-handler/text-document-did-close.go b/root-handler/text-document-did-close.go index 47943f4..fbf90e1 100644 --- a/root-handler/text-document-did-close.go +++ b/root-handler/text-document-did-close.go @@ -1,7 +1,7 @@ package roothandler import ( - "config-lsp/handlers/wireguard" + wireguard "config-lsp/handlers/wireguard/lsp" "github.com/tliron/glsp" protocol "github.com/tliron/glsp/protocol_3_16" diff --git a/root-handler/text-document-did-open.go b/root-handler/text-document-did-open.go index befd8c7..5623fd8 100644 --- a/root-handler/text-document-did-open.go +++ b/root-handler/text-document-did-open.go @@ -3,7 +3,7 @@ package roothandler import ( "config-lsp/common" fstab "config-lsp/handlers/fstab" - "config-lsp/handlers/wireguard" + wireguard "config-lsp/handlers/wireguard/lsp" "fmt" "github.com/tliron/glsp" diff --git a/root-handler/text-document-hover.go b/root-handler/text-document-hover.go index 28ec43f..a973f46 100644 --- a/root-handler/text-document-hover.go +++ b/root-handler/text-document-hover.go @@ -2,7 +2,7 @@ package roothandler import ( "config-lsp/handlers/fstab" - "config-lsp/handlers/wireguard" + wireguard "config-lsp/handlers/wireguard/lsp" "github.com/tliron/glsp" protocol "github.com/tliron/glsp/protocol_3_16" diff --git a/root-handler/workspace-execute-command.go b/root-handler/workspace-execute-command.go index 90b2fcb..934fd9e 100644 --- a/root-handler/workspace-execute-command.go +++ b/root-handler/workspace-execute-command.go @@ -1,7 +1,7 @@ package roothandler import ( - "config-lsp/handlers/wireguard" + wireguard "config-lsp/handlers/wireguard/lsp" "strings" "github.com/tliron/glsp" diff --git a/utils/tests.go b/utils/tests.go new file mode 100644 index 0000000..0c09cf8 --- /dev/null +++ b/utils/tests.go @@ -0,0 +1,13 @@ +package utils + +import "strings" + +func Dedent(s string) string { + return strings.TrimLeft(s, "\n") +} + +func KeyExists[T comparable, V any](keys map[T]V, key T) bool { + _, found := keys[key] + + return found +}