From a0dca94b9d926c49225120abbd5ea7cf17bde52f Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sat, 8 Mar 2025 14:44:59 +0100 Subject: [PATCH] refactor(server): Refactor Wireguard config; Improve completions + bunch of other stuff Signed-off-by: Myzel394 --- .../wireguard/analyzer/analyzer_test.go | 34 +- server/handlers/wireguard/ast/parser.go | 20 +- server/handlers/wireguard/ast/parser_test.go | 8 + server/handlers/wireguard/ast/wireguard.go | 1 + .../wireguard/ast/wireguard_fields.go | 6 +- .../handlers/wireguard/handlers/analyzer.go | 452 ------------------ .../wireguard/handlers/analyzer_test.go | 57 --- .../wireguard/handlers/code-actions.go | 12 +- .../wireguard/handlers/completions.go | 162 +------ .../wireguard/handlers/completions_body.go | 182 +++++++ .../wireguard/handlers/completions_header.go | 46 ++ .../wireguard/handlers/completions_test.go | 266 +++++++---- .../wireguard/handlers/fetch-code-actions.go | 10 +- server/handlers/wireguard/handlers/hover.go | 22 +- .../wireguard/lsp/text-document-completion.go | 56 +-- .../wireguard/parser/wg-parser-type_test.go | 127 ----- server/handlers/wireguard/parser/wg-parser.go | 258 ---------- .../wireguard/parser/wg-parser_test.go | 357 -------------- .../handlers/wireguard/parser/wg-property.go | 96 ---- .../handlers/wireguard/parser/wg-section.go | 73 --- server/handlers/wireguard/shared.go | 13 + 21 files changed, 506 insertions(+), 1752 deletions(-) delete mode 100644 server/handlers/wireguard/handlers/analyzer.go delete mode 100644 server/handlers/wireguard/handlers/analyzer_test.go create mode 100644 server/handlers/wireguard/handlers/completions_body.go create mode 100644 server/handlers/wireguard/handlers/completions_header.go delete mode 100644 server/handlers/wireguard/parser/wg-parser-type_test.go delete mode 100644 server/handlers/wireguard/parser/wg-parser.go delete mode 100644 server/handlers/wireguard/parser/wg-parser_test.go delete mode 100644 server/handlers/wireguard/parser/wg-property.go delete mode 100644 server/handlers/wireguard/parser/wg-section.go diff --git a/server/handlers/wireguard/analyzer/analyzer_test.go b/server/handlers/wireguard/analyzer/analyzer_test.go index 50ac57c..bf0aa05 100644 --- a/server/handlers/wireguard/analyzer/analyzer_test.go +++ b/server/handlers/wireguard/analyzer/analyzer_test.go @@ -1,7 +1,8 @@ package analyzer import ( - "config-lsp/handlers/wireguard/parser" + "config-lsp/handlers/wireguard" + "config-lsp/handlers/wireguard/ast" "config-lsp/utils" "testing" ) @@ -14,12 +15,14 @@ PrivateKey = abc [Interface] PrivateKey = def `) - p := parser.CreateWireguardParser() - p.ParseFromString(content) + d := &wireguard.WGDocument{ + Config: ast.NewWGConfig(), + } + d.Config.Parse(content) - diagnostics := Analyze(p) + diagnostics := Analyze(d) - if len(diagnostics) == 0 { + if !(len(diagnostics) > 0) { t.Errorf("Expected diagnostic errors, got %d", len(diagnostics)) } } @@ -29,12 +32,14 @@ func TestInvalidValue(t *testing.T) { [Interface] DNS = nope `) - p := parser.CreateWireguardParser() - p.ParseFromString(content) + d := &wireguard.WGDocument{ + Config: ast.NewWGConfig(), + } + d.Config.Parse(content) - diagnostics := Analyze(p) + diagnostics := Analyze(d) - if len(diagnostics) == 0 { + if !(len(diagnostics) > 0) { t.Errorf("Expected diagnostic errors, got %d", len(diagnostics)) } } @@ -46,12 +51,15 @@ PrivateKey = abc DNS = 1.1.1.1 PrivateKey = def `) - p := parser.CreateWireguardParser() - p.ParseFromString(content) - diagnostics := Analyze(p) + d := &wireguard.WGDocument{ + Config: ast.NewWGConfig(), + } + d.Config.Parse(content) - if len(diagnostics) == 0 { + diagnostics := Analyze(d) + + if !(len(diagnostics) > 0) { t.Errorf("Expected diagnostic errors, got %d", len(diagnostics)) } } diff --git a/server/handlers/wireguard/ast/parser.go b/server/handlers/wireguard/ast/parser.go index 8d2c066..441232c 100644 --- a/server/handlers/wireguard/ast/parser.go +++ b/server/handlers/wireguard/ast/parser.go @@ -35,12 +35,21 @@ func (c *WGConfig) Parse(input string) []common.LSPError { lineNumber := uint32(rawLineNumber) if emptyPattern.MatchString(line) { + // Set end of last section + if currentSection != nil { + currentSection.End.Line = lineNumber + currentSection.End.Character = 0 + } continue } if commentPattern.MatchString(line) { c.CommentLines[lineNumber] = struct{}{} - + // Set end of last section + if currentSection != nil { + currentSection.End.Line = lineNumber + currentSection.End.Character = uint32(len(line)) + } continue } @@ -80,6 +89,13 @@ func (c *WGConfig) Parse(input string) []common.LSPError { } // Else property + + // Set end of last section + if currentSection != nil { + currentSection.End.Line = lineNumber + currentSection.End.Character = uint32(len(line)) + } + if currentSection == nil { // Root properties are not allowed errors = append(errors, common.LSPError{ @@ -212,12 +228,12 @@ func (c *WGConfig) Parse(input string) []common.LSPError { Character: propertyEnd, }, }, + RawValue: line, Key: key, Separator: &separator, Value: value, } } - } return errors diff --git a/server/handlers/wireguard/ast/parser_test.go b/server/handlers/wireguard/ast/parser_test.go index 761baa8..2a0981b 100644 --- a/server/handlers/wireguard/ast/parser_test.go +++ b/server/handlers/wireguard/ast/parser_test.go @@ -36,6 +36,14 @@ PublicKey = 1234567890 t.Errorf("Parse: Expected comments to be present on lines 0 and 12") } + if !(config.Sections[0].Start.Line == 3 && config.Sections[0].End.Line == 8) { + t.Errorf("Parse: Expected section 0 to be present on lines 3 and 6, but it is: %v", config.Sections[0].End) + } + + if !(config.Sections[0].Start.Character == 0 && config.Sections[0].End.Character == 0) { + t.Errorf("Parse: Expected section 0 to be present on characters 0 and 0, but it is: %v", config.Sections[0].End) + } + if !(config.Sections[0].Header.Name == "Interface" && config.Sections[1].Header.Name == "Peer") { t.Errorf("Parse: Expected sections to be present on lines 0, 1, and 2") } diff --git a/server/handlers/wireguard/ast/wireguard.go b/server/handlers/wireguard/ast/wireguard.go index 9f1274c..ed29233 100644 --- a/server/handlers/wireguard/ast/wireguard.go +++ b/server/handlers/wireguard/ast/wireguard.go @@ -20,6 +20,7 @@ type WGPropertySeparator struct { type WGProperty struct { common.LocationRange + RawValue string Key WGPropertyKey Separator *WGPropertySeparator Value *WGPropertyValue diff --git a/server/handlers/wireguard/ast/wireguard_fields.go b/server/handlers/wireguard/ast/wireguard_fields.go index 3904220..68d9694 100644 --- a/server/handlers/wireguard/ast/wireguard_fields.go +++ b/server/handlers/wireguard/ast/wireguard_fields.go @@ -10,11 +10,11 @@ func (c *WGConfig) FindSectionByLine(line uint32) *WGSection { line, func(current *WGSection, target uint32) int { if target < current.Start.Line { - return 1 + return -1 } - if target > current.Start.Line { - return -1 + if target > current.End.Line { + return 1 } return 0 diff --git a/server/handlers/wireguard/handlers/analyzer.go b/server/handlers/wireguard/handlers/analyzer.go deleted file mode 100644 index 79e2734..0000000 --- a/server/handlers/wireguard/handlers/analyzer.go +++ /dev/null @@ -1,452 +0,0 @@ -package handlers - -import ( - docvalues "config-lsp/doc-values" - "config-lsp/handlers/wireguard/fields" - "config-lsp/handlers/wireguard/parser" - "config-lsp/utils" - "context" - "fmt" - "net/netip" - "slices" - "strings" - - protocol "github.com/tliron/glsp/protocol_3_16" -) - -func Analyze( - p parser.WireguardParser, -) []protocol.Diagnostic { - sectionsErrors := analyzeSections(p.Sections) - sectionsErrors = append(sectionsErrors, analyzeOnlyOneInterfaceSectionSpecified(p)...) - - if len(sectionsErrors) > 0 { - return sectionsErrors - } - - validCheckErrors := checkIfValuesAreValid(p.Sections) - - if len(validCheckErrors) > 0 { - return validCheckErrors - } - - 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)...) - diagnostics = append(diagnostics, analyzeDuplicateAllowedIPs(p)...) - - return diagnostics -} - -func analyzeSections( - sections []*parser.WireguardSection, -) []protocol.Diagnostic { - var diagnostics []protocol.Diagnostic - - for _, section := range sections { - sectionDiagnostics := analyzeSection(*section) - - if len(sectionDiagnostics) > 0 { - diagnostics = append(diagnostics, sectionDiagnostics...) - } - } - - if len(diagnostics) > 0 { - return diagnostics - } - - return diagnostics -} - -func analyzeOnlyOneInterfaceSectionSpecified( - p parser.WireguardParser, -) []protocol.Diagnostic { - var diagnostics []protocol.Diagnostic - alreadyFound := false - - 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(), - }) - } - - alreadyFound = true - } - - return diagnostics -} - -func analyzeDNSContainsFallback( - p parser.WireguardParser, -) []protocol.Diagnostic { - lineNumber, property := p.FindFirstPropertyByName("DNS") - - if property == nil { - return []protocol.Diagnostic{} - } - - dnsAmount := len(strings.Split(property.Value.Value, ",")) - - if dnsAmount == 1 { - severity := protocol.DiagnosticSeverityHint - - return []protocol.Diagnostic{ - { - Message: "There is only one DNS server specified. It is recommended to set up fallback DNS servers", - Severity: &severity, - Range: protocol.Range{ - Start: protocol.Position{ - Line: *lineNumber, - Character: property.Value.Location.Start, - }, - End: protocol.Position{ - Line: *lineNumber, - Character: property.Value.Location.End, - }, - }, - }, - } - } - - return []protocol.Diagnostic{} -} - -func analyzeKeepAliveIsSet( - p parser.WireguardParser, -) []protocol.Diagnostic { - var diagnostics []protocol.Diagnostic - - 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") { - 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(), - }) - } - } - - return diagnostics -} - -// Check if the values are valid. -// Assumes that sections have been analyzed already. -func checkIfValuesAreValid( - sections []*parser.WireguardSection, -) []protocol.Diagnostic { - var diagnostics []protocol.Diagnostic - - for _, section := range sections { - for lineNumber, property := range section.Properties { - diagnostics = append( - diagnostics, - analyzeProperty(property, section, lineNumber)..., - ) - } - } - - return diagnostics -} - -func analyzeSection( - s parser.WireguardSection, -) []protocol.Diagnostic { - var diagnostics []protocol.Diagnostic - - if s.Name == nil { - // No section name - severity := protocol.DiagnosticSeverityError - diagnostics = append(diagnostics, protocol.Diagnostic{ - Message: "This section is missing a name", - Severity: &severity, - Range: s.GetRange(), - }) - return diagnostics - } - - 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(), - }) - - return diagnostics - } - - return diagnostics -} - -// Check if the property is valid. -// 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 analyzeProperty( - p parser.WireguardProperty, - belongingSection *parser.WireguardSection, - propertyLine uint32, -) []protocol.Diagnostic { - sectionOptions := fields.OptionsHeaderMap[*belongingSection.Name] - option, found := sectionOptions[p.Key.Name] - - if !found { - // Unknown property - severity := protocol.DiagnosticSeverityError - return []protocol.Diagnostic{ - { - Message: fmt.Sprintf("Unknown property '%s'", p.Key.Name), - Severity: &severity, - Range: protocol.Range{ - Start: protocol.Position{ - Line: propertyLine, - Character: p.Key.Location.Start, - }, - End: protocol.Position{ - Line: propertyLine, - Character: p.Key.Location.End, - }, - }, - }, - } - } - - if p.Value == nil { - // No value to check - severity := protocol.DiagnosticSeverityWarning - return []protocol.Diagnostic{ - { - Message: "Property is missing a value", - Severity: &severity, - Range: p.GetLineRange(propertyLine), - }, - } - } - - errors := option.DeprecatedCheckIsValid(p.Value.Value) - - return utils.Map(errors, func(err *docvalues.InvalidValue) protocol.Diagnostic { - severity := protocol.DiagnosticSeverityError - return protocol.Diagnostic{ - Message: err.GetMessage(), - Severity: &severity, - Range: protocol.Range{ - Start: protocol.Position{ - Line: propertyLine, - Character: p.Value.Location.Start + err.Start, - }, - End: protocol.Position{ - Line: propertyLine, - Character: p.Value.Location.Start + err.End, - }, - }, - } - }) -} - -func analyzeParserForDuplicateProperties( - p parser.WireguardParser, -) []protocol.Diagnostic { - diagnostics := make([]protocol.Diagnostic, 0) - - for _, section := range p.Sections { - diagnostics = append(diagnostics, analyzeDuplicateProperties(*section)...) - } - - return diagnostics -} - -func analyzeDuplicateProperties( - s parser.WireguardSection, -) []protocol.Diagnostic { - var diagnostics []protocol.Diagnostic - - existingProperties := make(map[string]uint32) - - lines := utils.KeysOfMap(s.Properties) - slices.Sort(lines) - - for _, currentLineNumber := range lines { - property := s.Properties[currentLineNumber] - var skipCheck = false - - if s.Name != nil { - switch *s.Name { - case "Interface": - if _, found := fields.InterfaceAllowedDuplicateFields[property.Key.Name]; found { - skipCheck = true - } - case "Peer": - if _, found := fields.PeerAllowedDuplicateFields[property.Key.Name]; found { - skipCheck = true - } - } - } - - if skipCheck { - continue - } - - if existingLineNumber, found := existingProperties[property.Key.Name]; found { - severity := protocol.DiagnosticSeverityError - diagnostics = append(diagnostics, protocol.Diagnostic{ - Message: fmt.Sprintf("Property '%s' is already defined on line %d", property.Key.Name, existingLineNumber+1), - Severity: &severity, - Range: protocol.Range{ - Start: protocol.Position{ - Line: currentLineNumber, - Character: 0, - }, - End: protocol.Position{ - Line: currentLineNumber, - Character: 99999, - }, - }, - }) - } else { - existingProperties[property.Key.Name] = currentLineNumber - } - } - - return diagnostics -} - -type propertyWithLine struct { - Line uint32 - Property parser.WireguardProperty - IpPrefix netip.Prefix -} - -func mapAllowedIPsToMasks(p parser.WireguardParser) map[uint8][]propertyWithLine { - ips := make(map[uint8][]propertyWithLine) - - for _, section := range p.GetSectionsByName("Peer") { - for lineNumber, property := range section.Properties { - if property.Key.Name == "AllowedIPs" { - ipAddress, err := netip.ParsePrefix(property.Value.Value) - - if err != nil { - // This should not happen... - continue - } - - hostBits := uint8(ipAddress.Bits()) - - if _, found := ips[uint8(hostBits)]; !found { - ips[hostBits] = make([]propertyWithLine, 0) - } - - ips[hostBits] = append(ips[hostBits], propertyWithLine{ - Line: uint32(lineNumber), - Property: property, - IpPrefix: ipAddress, - }) - } - } - } - - return ips -} - -// Strategy -// Simply compare the host bits of the IP addresses. -// Use a binary tree to store the host bits. -func analyzeDuplicateAllowedIPs(p parser.WireguardParser) []protocol.Diagnostic { - diagnostics := make([]protocol.Diagnostic, 0) - - maskedIPs := mapAllowedIPsToMasks(p) - hostBits := utils.KeysOfMap(maskedIPs) - slices.Sort(hostBits) - - ipHostSet := utils.CreateIPv4HostSet() - - for _, hostBit := range hostBits { - ips := maskedIPs[hostBit] - - for _, ipInfo := range ips { - if ctx, _ := ipHostSet.ContainsIP(ipInfo.IpPrefix); ctx != nil { - severity := protocol.DiagnosticSeverityError - definedLine := (*ctx).Value("line").(uint32) - - diagnostics = append(diagnostics, protocol.Diagnostic{ - Message: fmt.Sprintf("This IP range is already covered on line %d", definedLine), - Severity: &severity, - Range: protocol.Range{ - Start: protocol.Position{ - Line: ipInfo.Line, - Character: ipInfo.Property.Key.Location.Start, - }, - End: protocol.Position{ - Line: ipInfo.Line, - Character: ipInfo.Property.Value.Location.End, - }, - }, - }) - } else { - humanLineNumber := ipInfo.Line + 1 - ctx := context.WithValue(context.Background(), "line", humanLineNumber) - - ipHostSet.AddIP( - ipInfo.IpPrefix, - ctx, - ) - } - } - } - - return diagnostics -} - -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") - - 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), - 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), - Severity: &severity, - }) - } - - 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), - 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), - Severity: &severity, - }) - } - } - - return diagnostics -} diff --git a/server/handlers/wireguard/handlers/analyzer_test.go b/server/handlers/wireguard/handlers/analyzer_test.go deleted file mode 100644 index 345e3ad..0000000 --- a/server/handlers/wireguard/handlers/analyzer_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package handlers - -import ( - "config-lsp/handlers/wireguard/parser" - "config-lsp/utils" - "testing" -) - -func TestMultipleIntefaces(t *testing.T) { - content := utils.Dedent(` -[Interface] -PrivateKey = abc - -[Interface] -PrivateKey = def -`) - p := parser.CreateWireguardParser() - p.ParseFromString(content) - - diagnostics := Analyze(p) - - if len(diagnostics) == 0 { - t.Errorf("Expected diagnostic errors, got %d", len(diagnostics)) - } -} - -func TestInvalidValue(t *testing.T) { - content := utils.Dedent(` -[Interface] -DNS = nope -`) - p := parser.CreateWireguardParser() - p.ParseFromString(content) - - diagnostics := Analyze(p) - - if len(diagnostics) == 0 { - t.Errorf("Expected diagnostic errors, got %d", len(diagnostics)) - } -} - -func TestDuplicateProperties(t *testing.T) { - content := utils.Dedent(` -[Interface] -PrivateKey = abc -DNS = 1.1.1.1 -PrivateKey = def -`) - p := parser.CreateWireguardParser() - p.ParseFromString(content) - - diagnostics := Analyze(p) - - if len(diagnostics) == 0 { - t.Errorf("Expected diagnostic errors, got %d", len(diagnostics)) - } -} diff --git a/server/handlers/wireguard/handlers/code-actions.go b/server/handlers/wireguard/handlers/code-actions.go index ecb37cd..32dbf57 100644 --- a/server/handlers/wireguard/handlers/code-actions.go +++ b/server/handlers/wireguard/handlers/code-actions.go @@ -1,8 +1,9 @@ package handlers +/* import ( + "config-lsp/handlers/wireguard/ast" wgcommands "config-lsp/handlers/wireguard/commands" - "config-lsp/handlers/wireguard/parser" protocol "github.com/tliron/glsp/protocol_3_16" ) @@ -15,7 +16,7 @@ const ( ) type CodeAction interface { - RunCommand(*parser.WireguardParser) (*protocol.ApplyWorkspaceEditParams, error) + RunCommand(*ast.WGConfig) (*protocol.ApplyWorkspaceEditParams, error) } type CodeActionArgs interface{} @@ -32,7 +33,7 @@ func CodeActionGeneratePrivateKeyArgsFromArguments(arguments map[string]any) Cod } } -func (args CodeActionGeneratePrivateKeyArgs) RunCommand(p *parser.WireguardParser) (*protocol.ApplyWorkspaceEditParams, error) { +func (args CodeActionGeneratePrivateKeyArgs) RunCommand(p *ast.WGConfig) (*protocol.ApplyWorkspaceEditParams, error) { privateKey, err := wgcommands.CreateNewPrivateKey() if err != nil { @@ -73,7 +74,7 @@ func CodeActionGeneratePresharedKeyArgsFromArguments(arguments map[string]any) C } } -func (args CodeActionGeneratePresharedKeyArgs) RunCommand(p *parser.WireguardParser) (*protocol.ApplyWorkspaceEditParams, error) { +func (args CodeActionGeneratePresharedKeyArgs) RunCommand(p *ast.WGConfig) (*protocol.ApplyWorkspaceEditParams, error) { presharedKey, err := wgcommands.CreatePresharedKey() if err != nil { @@ -114,7 +115,7 @@ func CodeActionAddKeepaliveArgsFromArguments(arguments map[string]any) CodeActio } } -func (args CodeActionAddKeepaliveArgs) RunCommand(p *parser.WireguardParser) (*protocol.ApplyWorkspaceEditParams, error) { +func (args CodeActionAddKeepaliveArgs) RunCommand(p *ast.WGConfig) (*protocol.ApplyWorkspaceEditParams, error) { section := p.Sections[args.SectionIndex] label := "Add PersistentKeepalive" @@ -141,3 +142,4 @@ func (args CodeActionAddKeepaliveArgs) RunCommand(p *parser.WireguardParser) (*p }, }, nil } +*/ diff --git a/server/handlers/wireguard/handlers/completions.go b/server/handlers/wireguard/handlers/completions.go index 4ebecf6..96fd37d 100644 --- a/server/handlers/wireguard/handlers/completions.go +++ b/server/handlers/wireguard/handlers/completions.go @@ -1,160 +1,34 @@ package handlers import ( - docvalues "config-lsp/doc-values" - "config-lsp/handlers/wireguard/fields" - "config-lsp/handlers/wireguard/parser" - "config-lsp/utils" + "config-lsp/handlers/wireguard" + 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, +func SuggestCompletions( + d *wireguard.WGDocument, + params *protocol.CompletionParams, ) ([]protocol.CompletionItem, error) { - completions := make([]protocol.CompletionItem, 0) + lineNumber := params.Position.Line - 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 { + if _, found := d.Config.CommentLines[lineNumber]; found { return nil, nil } - options := make(map[string]docvalues.DocumentationValue) + section := d.Config.FindSectionByLine(lineNumber) + property := d.Config.FindPropertyByLine(lineNumber) - 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{} + if section == nil { + // First, the user needs to define a section header + if property == nil { + return GetSectionHeaderCompletions(d) } else { - return nil, parser.PropertyNotFoundError{} + // However, if they start typing a property - we should not + // show anything to signal them that they can't write a property yet. + return nil, nil } + } else { + return GetSectionBodyCompletions(d, *section, property, params) } - - if property.Value == nil { - if character >= property.Separator.Location.End { - return option.DeprecatedFetchCompletions("", 0), nil - } - } - - relativeCursor := character - property.Value.Location.Start - - return option.DeprecatedFetchCompletions(property.Value.Value, relativeCursor), nil } diff --git a/server/handlers/wireguard/handlers/completions_body.go b/server/handlers/wireguard/handlers/completions_body.go new file mode 100644 index 0000000..e553dfc --- /dev/null +++ b/server/handlers/wireguard/handlers/completions_body.go @@ -0,0 +1,182 @@ +package handlers + +import ( + "config-lsp/common" + docvalues "config-lsp/doc-values" + "config-lsp/handlers/wireguard" + "config-lsp/handlers/wireguard/ast" + "config-lsp/handlers/wireguard/fields" + "config-lsp/utils" + "maps" + + protocol "github.com/tliron/glsp/protocol_3_16" +) + +func GetSectionBodyCompletions( + d *wireguard.WGDocument, + section ast.WGSection, + property *ast.WGProperty, + params *protocol.CompletionParams, +) ([]protocol.CompletionItem, error) { + // These are the possible scenarios: + // | = Cursor position + /* + [Inter| + */ + /* + [Interface] + | + */ + /* + [Interface] + Add| + */ + /* + [Interface] + Address = 10.0.0.1/24 + + | + */ + + // First scenario, user is typing the section name + if params.Position.Line == section.Start.Line { + return GetSectionHeaderCompletions(d) + } + + // Third and fourth scenarios, the user wants to add a new property + completions, err := getPropertyCompletions(d, section, property, params) + + if err != nil { + // Something weird happened + return completions, err + } + + // Fourth scenario may arrive here, the user is typing a property name, but the previous line is empty. + // In this case, the user may want to add a property or add a new section. + // We should therefore suggest both options. + + // Check if previous line is empty + previousLineProperty := d.Config.FindPropertyByLine(params.Position.Line - 1) + + if previousLineProperty == nil && params.Position.Line-1 != section.Start.Line { + sectionCompletions, err := GetSectionHeaderCompletions(d) + + if err != nil { + return sectionCompletions, err + } + + completions = append(completions, sectionCompletions...) + } + + return completions, nil +} + +func getPropertyCompletions( + d *wireguard.WGDocument, + section ast.WGSection, + property *ast.WGProperty, + params *protocol.CompletionParams, +) ([]protocol.CompletionItem, error) { + // These are the possible scenarios: + /* Empty line / Key started / Separator missing: + Add| + Address | + */ + /* Value missing or started: + Address = 10.| + */ + + if property == nil || property.Separator == nil { + // First scenario + return getKeyCompletions(section), nil + } + + // Check if the cursor it outside the value + position := common.LSPCharacterAsCursorPosition(params.Position.Character) + if property.Value != nil && property.Value.IsPositionAfterEnd(position) { + // Then we don't show anything + return nil, nil + } + + // Otherwise, suggest value completions + return getValueCompletions(section, *property, position), nil +} + +func getKeyCompletions( + section ast.WGSection, +) []protocol.CompletionItem { + options := make(map[string]docvalues.DocumentationValue) + + switch section.Header.Name { + case "Interface": + maps.Copy(options, fields.InterfaceOptions) + + // Remove existing, non-duplicate options + for _, property := range section.Properties { + if _, found := fields.InterfaceAllowedDuplicateFields[property.Key.Name]; found { + continue + } + + delete(options, property.Key.Name) + } + case "Peer": + maps.Copy(options, fields.PeerOptions) + + // Remove existing, non-duplicate options + for _, property := range section.Properties { + if _, found := fields.PeerAllowedDuplicateFields[property.Key.Name]; found { + continue + } + + delete(options, property.Key.Name) + } + } + + kind := protocol.CompletionItemKindField + + 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, + } + }, + ) +} + +func getValueCompletions( + section ast.WGSection, + property ast.WGProperty, + cursorPosition common.CursorPosition, +) []protocol.CompletionItem { + // TODO: Normalize section header name + options, found := fields.OptionsHeaderMap[section.Header.Name] + + if !found { + return nil + } + + option, found := options[property.Key.Name] + + if !found { + return nil + } + + if property.Value == nil { + return option.DeprecatedFetchCompletions("", 0) + } else { + return option.DeprecatedFetchCompletions( + property.Value.Value, + common.DeprecatedImprovedCursorToIndex( + cursorPosition, + property.Value.Value, + property.Value.Start.Character, + ), + ) + } +} diff --git a/server/handlers/wireguard/handlers/completions_header.go b/server/handlers/wireguard/handlers/completions_header.go new file mode 100644 index 0000000..a7e11f0 --- /dev/null +++ b/server/handlers/wireguard/handlers/completions_header.go @@ -0,0 +1,46 @@ +package handlers + +import ( + "config-lsp/handlers/wireguard" + "config-lsp/handlers/wireguard/fields" + + protocol "github.com/tliron/glsp/protocol_3_16" +) + +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 GetSectionHeaderCompletions( + d *wireguard.WGDocument, +) ([]protocol.CompletionItem, error) { + completions := make([]protocol.CompletionItem, 0) + + containsInterfaceSection := false + + for _, section := range d.Config.Sections { + if section.Header.Name == "Interface" { + containsInterfaceSection = true + break + } + } + + if !containsInterfaceSection { + completions = append(completions, getHeaderCompletion("Interface", fields.HeaderInterfaceEnum.Documentation)) + } + + completions = append(completions, getHeaderCompletion("Peer", fields.HeaderPeerEnum.Documentation)) + + return completions, nil +} diff --git a/server/handlers/wireguard/handlers/completions_test.go b/server/handlers/wireguard/handlers/completions_test.go index 5db4edc..c8f4118 100644 --- a/server/handlers/wireguard/handlers/completions_test.go +++ b/server/handlers/wireguard/handlers/completions_test.go @@ -1,10 +1,13 @@ package handlers import ( + "config-lsp/handlers/wireguard" + "config-lsp/handlers/wireguard/ast" "config-lsp/handlers/wireguard/fields" - "config-lsp/handlers/wireguard/parser" "config-lsp/utils" "testing" + + protocol "github.com/tliron/glsp/protocol_3_16" ) func TestSimplePropertyInInterface( @@ -14,17 +17,33 @@ func TestSimplePropertyInInterface( [Interface] `) - p := parser.CreateWireguardParser() - p.ParseFromString(sample) + p := ast.NewWGConfig() + parseErrors := p.Parse(sample) - completions, err := GetCompletionsForSectionEmptyLine(*p.Sections[0]) + if len(parseErrors) > 0 { + t.Fatalf("Parser failed with error %v", parseErrors) + } + + d := &wireguard.WGDocument{ + Config: p, + } + + params := &protocol.CompletionParams{ + TextDocumentPositionParams: protocol.TextDocumentPositionParams{ + Position: protocol.Position{ + Line: 1, + Character: 0, + }, + }, + } + completions, err := SuggestCompletions(d, params) if err != nil { - t.Fatalf("getCompletionsForEmptyLine failed with error: %v", err) + t.Errorf("getCompletionsForEmptyLine failed with error: %v", err) } if len(completions) != len(fields.InterfaceOptions) { - t.Fatalf("getCompletionsForEmptyLine: Expected %v completions, but got %v", len(fields.InterfaceOptions), len(completions)) + t.Errorf("getCompletionsForEmptyLine: Expected %v completions, but got %v", len(fields.InterfaceOptions), len(completions)) } } @@ -36,10 +55,26 @@ func TestSimpleOneExistingPropertyInInterface( PrivateKey = 1234567890 `) - p := parser.CreateWireguardParser() - p.ParseFromString(sample) + p := ast.NewWGConfig() + parseErrors := p.Parse(sample) - completions, err := GetCompletionsForSectionEmptyLine(*p.Sections[0]) + if len(parseErrors) > 0 { + t.Fatalf("Parser failed with error %v", parseErrors) + } + + d := &wireguard.WGDocument{ + Config: p, + } + + params := &protocol.CompletionParams{ + TextDocumentPositionParams: protocol.TextDocumentPositionParams{ + Position: protocol.Position{ + Line: 2, + Character: 0, + }, + }, + } + completions, err := SuggestCompletions(d, params) if err != nil { t.Fatalf("getCompletionsForEmptyLine failed with error: %v", err) @@ -51,124 +86,151 @@ PrivateKey = 1234567890 } } -func TestEmptyRootCompletionsWork( +func TestEmptyCompletions( t *testing.T, ) { sample := utils.Dedent(` - `) - p := parser.CreateWireguardParser() - p.ParseFromString(sample) +`) + p := ast.NewWGConfig() + parseErrors := p.Parse(sample) - completions, _ := GetRootCompletionsForEmptyLine(p) + if len(parseErrors) > 0 { + t.Fatalf("Parser failed with error %v", parseErrors) + } + + d := &wireguard.WGDocument{ + Config: p, + } + + params := &protocol.CompletionParams{ + TextDocumentPositionParams: protocol.TextDocumentPositionParams{ + Position: protocol.Position{ + Line: 0, + Character: 0, + }, + }, + } + completions, err := SuggestCompletions(d, params) + + if err != nil { + t.Fatalf("getCompletionsForEmptyLine failed with error: %v", err) + } if len(completions) != 2 { t.Fatalf("getRootCompletionsForEmptyLine: Expected 2 completions, but got %v", len(completions)) } } -func TestInterfaceSectionRootCompletionsBeforeWork( +func TestIncompletePropertyCompletions( t *testing.T, ) { sample := utils.Dedent(` - -[Interface] -`) - p := parser.CreateWireguardParser() - p.ParseFromString(sample) - - completions, _ := GetRootCompletionsForEmptyLine(p) - - if len(completions) != 1 { - t.Fatalf("getRootCompletionsForEmptyLine: Expected 1 completions, but got %v", len(completions)) - } -} - -func TestInterfaceAndPeerSectionRootCompletionsWork( - t *testing.T, -) { - sample := utils.Dedent(` -[Interface] - [Peer] +Add `) - p := parser.CreateWireguardParser() - p.ParseFromString(sample) + p := ast.NewWGConfig() + parseErrors := p.Parse(sample) - completions, _ := GetRootCompletionsForEmptyLine(p) - - if len(completions) != 1 { - t.Fatalf("getRootCompletionsForEmptyLine: Expected 1 completions, but got %v", len(completions)) - } -} - -func TestPropertyNoSepatorShouldCompleteSeparator( - t *testing.T, -) { - sample := utils.Dedent(` -[Interface] -DNS -`) - p := parser.CreateWireguardParser() - p.ParseFromString(sample) - - completions, err := GetCompletionsForSectionPropertyLine(*p.Sections[0], 1, 3) - - if err == nil { - t.Fatalf("getCompletionsForPropertyLine err is nil but should not be") + if len(parseErrors) > 0 { + t.Fatalf("Parser failed with error %v", parseErrors) } - if len(completions) != 1 { - t.Fatalf("getCompletionsForPropertyLine: Expected 1 completion, but got %v", len(completions)) + d := &wireguard.WGDocument{ + Config: p, } - if *completions[0].InsertText != "DNS = " { - t.Fatalf("getCompletionsForPropertyLine: Expected completion to be 'DNS = ', but got '%v'", completions[0].Label) + params := &protocol.CompletionParams{ + TextDocumentPositionParams: protocol.TextDocumentPositionParams{ + Position: protocol.Position{ + Line: 1, + Character: 3, + }, + }, } -} - -func TestPropertyNoSeparatorWithSpaceShouldCompleteSeparator( - t *testing.T, -) { - sample := utils.Dedent(` -[Interface] -DNS -`) - p := parser.CreateWireguardParser() - p.ParseFromString(sample) - - completions, err := GetCompletionsForSectionPropertyLine(*p.Sections[0], 1, 4) - - if err == nil { - t.Fatalf("getCompletionsForPropertyLine err is nil but should not be") - } - - if len(completions) != 1 { - t.Fatalf("getCompletionsForPropertyLine: Expected 1 completion, but got %v", len(completions)) - } - - if *completions[0].InsertText != "= " { - t.Fatalf("getCompletionsForPropertyLine: Expected completion to be '= ', but got '%v'", completions[0].Label) - } -} - -func TestHeaderButNoProperty( - t *testing.T, -) { - sample := utils.Dedent(` -[Interface] - -`) - p := parser.CreateWireguardParser() - p.ParseFromString(sample) - - completions, err := GetCompletionsForSectionEmptyLine(*p.Sections[0]) + completions, err := SuggestCompletions(d, params) if err != nil { - t.Fatalf("getCompletionsForEmptyLine failed with error: %v", err) + t.Errorf("getCompletionsForEmptyLine failed with error: %v", err) } - if len(completions) != len(fields.InterfaceOptions) { - t.Fatalf("getCompletionsForEmptyLine: Expected %v completions, but got %v", len(fields.InterfaceOptions), len(completions)) + if len(completions) != len(fields.PeerOptions) { + t.Errorf("getRootCompletionsForEmptyLine: Expected 1 completions, but got %v", len(completions)) + } +} + +func TestPropertyBeforeLineIsEmpty( + t *testing.T, +) { + sample := utils.Dedent(` +[Interface] +DNS = 1.1.1.1 + + +`) + p := ast.NewWGConfig() + parseErrors := p.Parse(sample) + + if len(parseErrors) > 0 { + t.Fatalf("Parser failed with error %v", parseErrors) + } + + d := &wireguard.WGDocument{ + Config: p, + } + + params := &protocol.CompletionParams{ + TextDocumentPositionParams: protocol.TextDocumentPositionParams{ + Position: protocol.Position{ + Line: 3, + Character: 0, + }, + }, + } + completions, err := SuggestCompletions(d, params) + + if err != nil { + t.Errorf("getCompletionsForPropertyLine failed with error: %v", err) + } + + if len(completions) != len(fields.InterfaceOptions)+1-1 { + t.Errorf("getCompletionsForPropertyLine: Expected completions, but got %v", len(completions)) + } +} + +func TestPropertyValueCompletions( + t *testing.T, +) { + sample := utils.Dedent(` +[Interface] +Table = +`) + p := ast.NewWGConfig() + parseErrors := p.Parse(sample) + + if len(parseErrors) > 0 { + t.Fatalf("Parser failed with error %v", parseErrors) + } + + d := &wireguard.WGDocument{ + Config: p, + } + + params := &protocol.CompletionParams{ + TextDocumentPositionParams: protocol.TextDocumentPositionParams{ + Position: protocol.Position{ + Line: 1, + Character: 8, + }, + }, + } + completions, err := SuggestCompletions(d, params) + + if err != nil { + t.Errorf("getCompletionsForPropertyLine failed with error: %v", err) + } + + if !(len(completions) == 2) { + t.Errorf("getCompletionsForPropertyLine: Expected completions, but got %v", len(completions)) } } diff --git a/server/handlers/wireguard/handlers/fetch-code-actions.go b/server/handlers/wireguard/handlers/fetch-code-actions.go index 84dfead..7a5a86b 100644 --- a/server/handlers/wireguard/handlers/fetch-code-actions.go +++ b/server/handlers/wireguard/handlers/fetch-code-actions.go @@ -1,19 +1,20 @@ package handlers +/* import ( + "config-lsp/handlers/wireguard/ast" "config-lsp/handlers/wireguard/commands" - "config-lsp/handlers/wireguard/parser" protocol "github.com/tliron/glsp/protocol_3_16" ) func GetKeepaliveCodeActions( - p *parser.WireguardParser, + p *ast.WGConfig, params *protocol.CodeActionParams, ) []protocol.CodeAction { line := params.Range.Start.Line for index, section := range p.Sections { - if section.StartLine >= line && line <= section.EndLine && section.Name != nil && *section.Name == "Peer" { + if section.StartLine >= line && line <= section.EndLine && section.Header != nil && *section.Header == "Peer" { if section.ExistsProperty("Endpoint") && !section.ExistsProperty("PersistentKeepalive") { commandID := "wireguard." + CodeActionAddKeepalive command := protocol.Command{ @@ -41,7 +42,7 @@ func GetKeepaliveCodeActions( } func GetKeyGenerationCodeActions( - p *parser.WireguardParser, + p *ast.WGConfig, params *protocol.CodeActionParams, ) []protocol.CodeAction { line := params.Range.Start.Line @@ -102,3 +103,4 @@ func GetKeyGenerationCodeActions( return nil } +*/ diff --git a/server/handlers/wireguard/handlers/hover.go b/server/handlers/wireguard/handlers/hover.go index 7ac6660..8d652d1 100644 --- a/server/handlers/wireguard/handlers/hover.go +++ b/server/handlers/wireguard/handlers/hover.go @@ -1,20 +1,21 @@ package handlers +/* import ( docvalues "config-lsp/doc-values" + "config-lsp/handlers/wireguard/ast" "config-lsp/handlers/wireguard/fields" - "config-lsp/handlers/wireguard/parser" "fmt" "strings" ) func getPropertyInfo( - p parser.WireguardProperty, + p ast.WGProperty, cursor uint32, - section parser.WireguardSection, + section ast.WGSection, ) []string { if cursor <= p.Key.Location.End { - options, found := fields.OptionsHeaderMap[*section.Name] + options, found := fields.OptionsHeaderMap[*section.Header] if !found { return []string{} @@ -29,7 +30,7 @@ func getPropertyInfo( return strings.Split(option.Documentation, "\n") } - options, found := fields.OptionsHeaderMap[*section.Name] + options, found := fields.OptionsHeaderMap[*section.Header] if !found { return []string{} @@ -42,19 +43,19 @@ func getPropertyInfo( return []string{} } -func getSectionInfo(s parser.WireguardSection) []string { - if s.Name == nil { +func getSectionInfo(s ast.WGSection) []string { + if s.Header == nil { return []string{} } contents := []string{ - fmt.Sprintf("## [%s]", *s.Name), + fmt.Sprintf("## [%s]", *s.Header), "", } var option *docvalues.EnumString = nil - switch *s.Name { + switch *s.Header { case "Interface": option = &fields.HeaderInterfaceEnum case "Peer": @@ -71,7 +72,7 @@ func getSectionInfo(s parser.WireguardSection) []string { } func GetHoverContent( - p parser.WireguardParser, + p ast.WGConfig, line uint32, cursor uint32, ) []string { @@ -103,3 +104,4 @@ func GetHoverContent( return contents } +*/ diff --git a/server/handlers/wireguard/lsp/text-document-completion.go b/server/handlers/wireguard/lsp/text-document-completion.go index 069cac9..28fdbfb 100644 --- a/server/handlers/wireguard/lsp/text-document-completion.go +++ b/server/handlers/wireguard/lsp/text-document-completion.go @@ -1,60 +1,18 @@ package lsp import ( + "config-lsp/handlers/wireguard" "config-lsp/handlers/wireguard/handlers" - "config-lsp/handlers/wireguard/parser" + "regexp" + "github.com/tliron/glsp" protocol "github.com/tliron/glsp/protocol_3_16" ) +var headerPattern = regexp.MustCompile(`^\s*\[(\w+)?]?`) + func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionParams) (any, error) { - p := documentParserMap[params.TextDocument.URI] + d := wireguard.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) - } - - completions, err := handlers.GetCompletionsForSectionEmptyLine(*section) - - // === Smart rules === - - // If previous line is empty too, maybe new section? - if lineNumber >= 1 && p.GetTypeByLine(lineNumber-1) == parser.LineTypeEmpty && len(p.GetBelongingSectionByLine(lineNumber).Properties) > 0 { - rootCompletions, err := handlers.GetRootCompletionsForEmptyLine(*p) - - if err == nil { - completions = append(completions, rootCompletions...) - } - } - - return completions, err - 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") + return handlers.SuggestCompletions(d, params) } diff --git a/server/handlers/wireguard/parser/wg-parser-type_test.go b/server/handlers/wireguard/parser/wg-parser-type_test.go deleted file mode 100644 index 53ee275..0000000 --- a/server/handlers/wireguard/parser/wg-parser-type_test.go +++ /dev/null @@ -1,127 +0,0 @@ -package parser - -import ( - "config-lsp/utils" - "testing" -) - -func TestGetLineTypeWorksCorrectly( - t *testing.T, -) { - sample := utils.Dedent(` -# A comment at the very top -Test=Hello - -[Interface] -PrivateKey = 1234567890 # Some comment -Address = 10.0.0.1 - - - -[Peer] -PublicKey = 1234567890 - -; I'm a comment -`) - - parser := CreateWireguardParser() - parser.ParseFromString(sample) - - 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) - if lineType != LineTypeProperty { - t.Fatalf("getTypeByLine: Expected line 1 to be a property, but it is %v", lineType) - } - - 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) - if lineType != LineTypeHeader { - t.Fatalf("getTypeByLine: Expected line 3 to be a header, but it is %v", lineType) - } - - 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) - if lineType != LineTypeComment { - t.Fatalf("getTypeByLine: Expected line 12 to be a comment, but it is %v", lineType) - } -} - -func TestGetBelongingSectionWorksCorrectly( - t *testing.T, -) { - sample := utils.Dedent(` -# A comment at the very top -Test=Hello - -[Interface] -PrivateKey = 1234567890 # Some comment -Address = 10.0.0.1 - - - -[Peer] -PublicKey = 1234567890 - -; I'm a comment -`) - - parser := CreateWireguardParser() - parser.ParseFromString(sample) - - 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) - - 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) - 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) - 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) - 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) - 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) - 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) - - // Comment - if section != nil { - t.Fatalf("getBelongingSectionByLine: Expected line 12 to be in no section, but it is in %v", section) - } -} diff --git a/server/handlers/wireguard/parser/wg-parser.go b/server/handlers/wireguard/parser/wg-parser.go deleted file mode 100644 index 688dc5c..0000000 --- a/server/handlers/wireguard/parser/wg-parser.go +++ /dev/null @@ -1,258 +0,0 @@ -package parser - -import ( - "config-lsp/common" - "config-lsp/handlers/wireguard/ast" - "regexp" - "slices" - "strings" -) - -var commentPattern = regexp.MustCompile(`^\s*(;|#)`) -var emptyLinePattern = regexp.MustCompile(`^\s*$`) -var headerPattern = regexp.MustCompile(`^\s*\[`) - -func (p *ast.WGConfig) ParseFromString(input string) []common.ParseError { - var errors []common.ParseError - lines := strings.Split( - input, - "\n", - ) - - slices.Reverse(lines) - - collectedProperties := WireguardProperties{} - var lastPropertyLine *uint32 - - for index, line := range lines { - currentLineNumber := uint32(len(lines) - index - 1) - lineType := getLineType(line) - - switch lineType { - case LineTypeComment: - p.commentLines[currentLineNumber] = struct{}{} - p.linesIndexes[currentLineNumber] = wireguardLineIndex{ - Type: LineTypeComment, - BelongingSection: nil, - } - - case LineTypeEmpty: - continue - - case LineTypeProperty: - err := collectedProperties.AddLine(currentLineNumber, line) - - if err != nil { - errors = append(errors, common.ParseError{ - Line: currentLineNumber, - Err: err, - }) - continue - } - - if lastPropertyLine == nil { - lastPropertyLine = ¤tLineNumber - } - - case LineTypeHeader: - var lastLine uint32 - - if lastPropertyLine == nil { - // Current line - lastLine = currentLineNumber - } else { - lastLine = *lastPropertyLine - } - - section := CreateWireguardSection( - currentLineNumber, - lastLine, - line, - collectedProperties, - ) - - p.Sections = append(p.Sections, §ion) - - // Add indexes - for lineNumber := range collectedProperties { - p.linesIndexes[lineNumber] = wireguardLineIndex{ - Type: LineTypeProperty, - BelongingSection: §ion, - } - } - p.linesIndexes[currentLineNumber] = wireguardLineIndex{ - Type: LineTypeHeader, - BelongingSection: §ion, - } - - // Reset - collectedProperties = WireguardProperties{} - lastPropertyLine = nil - } - } - - var emptySection *ast.WGSection - - if len(collectedProperties) > 0 { - var endLine uint32 - - if len(p.Sections) == 0 { - endLine = uint32(len(lines)) - } else { - endLine = p.Sections[len(p.Sections)-1].StartLine - } - - emptySection = &ast.WGSection{ - StartLine: 0, - EndLine: endLine, - Properties: collectedProperties, - } - - p.Sections = append(p.Sections, emptySection) - - for lineNumber := range collectedProperties { - p.linesIndexes[lineNumber] = wireguardLineIndex{ - Type: LineTypeProperty, - BelongingSection: emptySection, - } - } - p.Sections = append(p.Sections, emptySection) - } else { - // Add empty section - var endLine = uint32(len(lines)) - - if len(p.Sections) > 0 { - endLine = p.Sections[len(p.Sections)-1].StartLine - } - - // Add empty section - if endLine != 0 { - emptySection = &ast.WGSection{ - StartLine: 0, - EndLine: endLine, - Properties: collectedProperties, - } - - p.Sections = append(p.Sections, emptySection) - - for newLine := uint32(0); newLine < endLine; newLine++ { - if _, found := p.linesIndexes[newLine]; found { - continue - } - - p.linesIndexes[newLine] = wireguardLineIndex{ - Type: LineTypeEmpty, - BelongingSection: emptySection, - } - } - } - } - - // Since we parse the content from bottom to top, we need to reverse the sections - // so its in correct order - slices.Reverse(p.Sections) - - // Fill empty lines between sections - for lineNumber, section := range p.Sections { - var endLine uint32 - - if len(p.Sections) > lineNumber+1 { - nextSection := p.Sections[lineNumber+1] - endLine = nextSection.StartLine - } else { - endLine = uint32(len(lines)) - } - - for newLine := section.StartLine; newLine < endLine; newLine++ { - if _, found := p.linesIndexes[newLine]; found { - continue - } - - p.linesIndexes[newLine] = wireguardLineIndex{ - Type: LineTypeEmpty, - BelongingSection: section, - } - } - } - - return errors -} - -func (p *ast.WGConfig) GetSectionByLine(line uint32) *ast.WGSection { - 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 *ast.WGConfig) FindFirstPropertyByName(name string) (*uint32, *ast.WGProperty) { - for _, section := range p.Sections { - for lineNumber, property := range section.Properties { - if property.Key.Name == name { - return &lineNumber, &property - } - } - } - - return nil, nil -} - -func (p ast.WGConfig) GetInterfaceSection() (*ast.WGSection, bool) { - for _, section := range p.Sections { - if section.Header != nil && *section.Header == "Interface" { - return section, true - } - } - - return nil, false -} - -func (p ast.WGConfig) GetTypeByLine(line uint32) LineType { - // Check if line is a comment - if _, found := p.commentLines[line]; found { - return LineTypeComment - } - - if info, found := p.linesIndexes[line]; found { - return info.Type - } - - return LineTypeEmpty -} - -func CreateWireguardParser() ast.WGConfig { - parser := ast.WGConfig{} - 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/server/handlers/wireguard/parser/wg-parser_test.go b/server/handlers/wireguard/parser/wg-parser_test.go deleted file mode 100644 index c49fa54..0000000 --- a/server/handlers/wireguard/parser/wg-parser_test.go +++ /dev/null @@ -1,357 +0,0 @@ -package parser - -import ( - "config-lsp/utils" - "testing" - - "github.com/k0kubun/pp" -) - -func TestValidWildTestWorksFine( - t *testing.T, -) { - sample := utils.Dedent(` -[Interface] -PrivateKey = 1234567890 -Address = 192.168.1.0/24 - -# I'm a comment -[Peer] -PublicKey = 1234567890 -Endpoint = 1.2.3.4 ; I'm just a comment - -[Peer] -PublicKey = 5555 - `) - - parser := CreateWireguardParser() - errors := parser.ParseFromString(sample) - - if len(errors) > 0 { - t.Fatalf("parseFromString failed with error %v", errors) - } - - 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].Header == "Interface") && (*parser.Sections[1].Header == "Peer") && (*parser.Sections[2].Header == "Peer")) { - t.Fatalf("parseFromString failed to collect sections %v", parser.Sections) - } - - if !(parser.Sections[0].StartLine == 0 && parser.Sections[0].EndLine == 2 && parser.Sections[1].StartLine == 5 && parser.Sections[1].EndLine == 7 && parser.Sections[2].StartLine == 9 && parser.Sections[2].EndLine == 10) { - t.Fatalf("parseFromString: Invalid start and end lines %v", parser.Sections) - } - - if !((len(parser.Sections[0].Properties) == 2) && (len(parser.Sections[1].Properties) == 2) && (len(parser.Sections[2].Properties) == 1)) { - t.Fatalf("parseFromString: Invalid amount of properties %v", parser.Sections) - } - - if !((parser.Sections[0].Properties[1].Key.Name == "PrivateKey") && (parser.Sections[0].Properties[2].Key.Name == "Address")) { - t.Fatalf("parseFromString failed to collect properties of section 0 %v", parser.Sections[0].Properties) - } - - if !((parser.Sections[1].Properties[6].Key.Name == "PublicKey") && (parser.Sections[1].Properties[7].Key.Name == "Endpoint")) { - t.Fatalf("parseFromString failed to collect properties of section 1 %v", parser.Sections[1].Properties) - } - - if !(parser.Sections[2].Properties[10].Key.Name == "PublicKey") { - t.Fatalf("parseFromString failed to collect properties of section 2 %v", parser.Sections[2].Properties) - } - - // Check if line indexes are correct - 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") - } -} - -func TestEmptySectionAtStartWorksFine( - t *testing.T, -) { - sample := utils.Dedent(` -[Interface] - -[Peer] -PublicKey = 1234567890 -`) - - parser := CreateWireguardParser() - errors := parser.ParseFromString(sample) - - if len(errors) > 0 { - t.Fatalf("parseFromString failed with error %v", errors) - } - - if !((len(parser.Sections) == 2) && (*parser.Sections[0].Header == "Interface") && (*parser.Sections[1].Header == "Peer")) { - t.Fatalf("parseFromString failed to collect sections %v", parser.Sections) - } - - if !(len(parser.Sections[0].Properties) == 0 && len(parser.Sections[1].Properties) == 1) { - t.Fatalf("parseFromString failed to collect properties %v", parser.Sections) - } -} - -func TestEmptySectionAtEndWorksFine( - t *testing.T, -) { - sample := utils.Dedent(` -[Inteface] -PrivateKey = 1234567890 - -[Peer] -# Just sneaking in here, hehe -`) - - parser := CreateWireguardParser() - errors := parser.ParseFromString(sample) - - if len(errors) > 0 { - t.Fatalf("parseFromString failed with error %v", errors) - } - - if !((len(parser.Sections) == 2) && (*parser.Sections[0].Header == "Inteface") && (*parser.Sections[1].Header == "Peer")) { - t.Fatalf("parseFromString failed to collect sections %v", parser.Sections) - } - - if !(len(parser.Sections[0].Properties) == 1 && len(parser.Sections[1].Properties) == 0) { - t.Fatalf("parseFromString failed to collect properties %v", parser.Sections) - } - - 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 := utils.Dedent(` -`) - - parser := CreateWireguardParser() - errors := parser.ParseFromString(sample) - - if len(errors) > 0 { - t.Fatalf("parseFromString failed with error %v", errors) - } - - if !(len(parser.Sections) == 1) { - t.Fatalf("parseFromString failed to collect sections %v", parser.Sections) - } -} - -func TestPartialSectionWithNoPropertiesWorksFine( - t *testing.T, -) { - sample := utils.Dedent(` -[Inte - -[Peer] -PublicKey = 1234567890 -`) - - parser := CreateWireguardParser() - errors := parser.ParseFromString(sample) - - if len(errors) > 0 { - t.Fatalf("parseFromString failed with error %v", errors) - } - - if !((len(parser.Sections) == 2) && (*parser.Sections[0].Header == "Inte") && (*parser.Sections[1].Header == "Peer")) { - t.Fatalf("parseFromString failed to collect sections: %v", parser.Sections) - } - - if !(len(parser.Sections[0].Properties) == 0 && len(parser.Sections[1].Properties) == 1) { - 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 !(parser.Sections[1].Properties[3].Key.Name == "PublicKey") { - t.Fatalf("parseFromString failed to collect properties of section 1: %v", parser.Sections[1].Properties) - } -} - -func TestPartialSectionWithPropertiesWorksFine( - t *testing.T, -) { - sample := utils.Dedent(` -[Inte -PrivateKey = 1234567890 - -[Peer] -`) - - parser := CreateWireguardParser() - errors := parser.ParseFromString(sample) - - if len(errors) > 0 { - t.Fatalf("parseFromString failed with error: %v", errors) - } - - if !((len(parser.Sections) == 2) && (*parser.Sections[0].Header == "Inte") && (*parser.Sections[1].Header == "Peer")) { - t.Fatalf("parseFromString failed to collect sections: %v", parser.Sections) - } - - if !(len(parser.Sections[0].Properties) == 1 && len(parser.Sections[1].Properties) == 0) { - t.Fatalf("parseFromString failed to collect properties: %v", parser.Sections) - } - - if !(parser.Sections[0].Properties[1].Key.Name == "PrivateKey") { - t.Fatalf("parseFromString failed to collect properties of section 0: %v", parser.Sections[0].Properties) - } -} - -func TestFileWithOnlyComments( - t *testing.T, -) { - sample := utils.Dedent(` -# This is a comment -# Another comment -`) - parser := CreateWireguardParser() - errors := parser.ParseFromString(sample) - - if len(errors) > 0 { - t.Fatalf("parseFromString failed with error: %v", errors) - } - - if !(len(parser.Sections) == 1) { - 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 !(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 := utils.Dedent(` -[Interface] -[Peer] -[Peer] -`) - - parser := CreateWireguardParser() - errors := parser.ParseFromString(sample) - - if len(errors) > 0 { - t.Fatalf("parseFromString failed with error: %v", errors) - } - - if !(len(parser.Sections) == 3) { - t.Fatalf("parseFromString failed to collect sections: %v", parser.Sections) - } - - for _, section := range parser.Sections { - if len(section.Properties) != 0 { - t.Fatalf("parseFromString failed to collect properties: %v", section.Properties) - } - } -} - -func TestWildTest1WorksCorrectly( - t *testing.T, -) { - sample := utils.Dedent(` -[Interface] -DNS=1.1.1.1 - - -`) - parser := CreateWireguardParser() - errors := parser.ParseFromString(sample) - - if len(errors) > 0 { - t.Fatalf("parseFromString failed with error: %v", errors) - } - - if !(len(parser.Sections) == 1) { - t.Fatalf("parseFromString failed to collect sections: %v", parser.Sections) - } - - if !(len(parser.Sections[0].Properties) == 1) { - t.Fatalf("parseFromString failed to collect properties: %v", parser.Sections[0].Properties) - } - - if !(parser.Sections[0].Properties[1].Key.Name == "DNS") { - t.Fatalf("parseFromString failed to collect properties of section 0: %v", parser.Sections[0].Properties) - } - - if !(parser.Sections[0].Properties[1].Value.Value == "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 !(parser.Sections[0].StartLine == 0 && parser.Sections[0].EndLine == 1) { - t.Fatalf("parseFromString: Invalid start and end lines %v", parser.Sections) - } -} - -func TestPartialKeyWorksCorrectly( - t *testing.T, -) { - sample := utils.Dedent(` -[Interface] -DNS -`) - parser := CreateWireguardParser() - errors := parser.ParseFromString(sample) - - if len(errors) > 0 { - t.Fatalf("parseFromString failed with error: %v", errors) - } - - if !(parser.Sections[0].Properties[1].Key.Name == "DNS") { - t.Fatalf("parseFromString failed to collect properties of section 0: %v", parser.Sections[0].Properties) - } - - if !(parser.Sections[0].Properties[1].Separator == nil) { - t.Fatalf("parseFromString failed to collect properties of section 0: %v", parser.Sections[0].Properties) - } -} - -func TestPartialValueWithSeparatorWorksCorrectly( - t *testing.T, -) { - sample := utils.Dedent(` -[Interface] -DNS= -`) - parser := CreateWireguardParser() - errors := parser.ParseFromString(sample) - - if len(errors) > 0 { - t.Fatalf("parseFromString failed with error: %v", errors) - } - - if !(parser.Sections[0].Properties[1].Value == nil) { - t.Fatalf("parseFromString failed to collect properties of section 0: %v", parser.Sections[0].Properties) - } - - if !(parser.Sections[0].Properties[1].Separator != nil) { - t.Fatalf("parseFromString failed to collect properties of section 0: %v", parser.Sections[0].Properties) - } -} diff --git a/server/handlers/wireguard/parser/wg-property.go b/server/handlers/wireguard/parser/wg-property.go deleted file mode 100644 index a96937b..0000000 --- a/server/handlers/wireguard/parser/wg-property.go +++ /dev/null @@ -1,96 +0,0 @@ -package parser - -import ( - docvalues "config-lsp/doc-values" - "config-lsp/handlers/wireguard/ast" - "config-lsp/utils" - "regexp" - "strings" - - protocol "github.com/tliron/glsp/protocol_3_16" -) - -var linePattern = regexp.MustCompile(`^\s*(?P.+?)\s*(?P=)\s*(?P\S.*?)?\s*(?:(?:;|#).*)?\s*$`) - -// WireguardProperties []: -type WireguardProperties map[uint32]ast.WGProperty - -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) (*ast.WGProperty, error) { - if !strings.Contains(line, "=") { - indexes := utils.GetTrimIndex(line) - - if indexes == nil { - // weird, should not happen - return nil, &docvalues.MalformedLineError{} - } - - return &ast.WGProperty{ - Key: ast.WGPropertyKey{ - Name: line[indexes[0]:indexes[1]], - Location: CharacterLocation{ - Start: uint32(indexes[0]), - End: uint32(indexes[1]), - }, - }, - }, nil - } - - indexes := linePattern.FindStringSubmatchIndex(line) - - if indexes == nil || len(indexes) == 0 { - return nil, &docvalues.MalformedLineError{} - } - - keyStart := uint32(indexes[2]) - keyEnd := uint32(indexes[3]) - key := ast.WGPropertyKey{ - Location: CharacterLocation{ - Start: keyStart, - End: keyEnd, - }, - Name: line[keyStart:keyEnd], - } - - separatorStart := uint32(indexes[4]) - separatorEnd := uint32(indexes[5]) - separator := ast.WGPropertySeparator{ - Location: CharacterLocation{ - Start: separatorStart, - End: separatorEnd, - }, - } - - var value *ast.WGPropertyValue - - if indexes[6] != -1 && indexes[7] != -1 { - // value exists - valueStart := uint32(indexes[6]) - valueEnd := uint32(indexes[7]) - - value = &ast.WGPropertyValue{ - Location: CharacterLocation{ - Start: valueStart, - End: valueEnd, - }, - Value: line[valueStart:valueEnd], - } - } - - return &ast.WGProperty{ - Key: key, - Separator: &separator, - Value: value, - }, nil -} diff --git a/server/handlers/wireguard/parser/wg-section.go b/server/handlers/wireguard/parser/wg-section.go deleted file mode 100644 index 8dc5097..0000000 --- a/server/handlers/wireguard/parser/wg-section.go +++ /dev/null @@ -1,73 +0,0 @@ -package parser - -import ( - "config-lsp/handlers/wireguard/ast" - "regexp" -) - -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" -} - -func (s ast.WGSection) FetchFirstProperty(name string) (*uint32, *ast.WGProperty) { - for line, property := range s.Properties { - if property.Key.Name == name { - return &line, &property - } - } - - return nil, nil -} - -func (s ast.WGSection) ExistsProperty(name string) bool { - _, property := s.FetchFirstProperty(name) - - return property != nil -} - -func (s ast.WGSection) GetPropertyByLine(lineNumber uint32) (*ast.WGProperty, 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, -) ast.WGSection { - match := validHeaderPattern.FindStringSubmatch(headerLine) - - var header string - - if match == nil { - // Still typing it - header = headerLine[1:] - } else { - header = match[1] - } - - return ast.WGSection{ - Header: &header, - StartLine: startLine, - EndLine: endLine, - Properties: props, - } -} diff --git a/server/handlers/wireguard/shared.go b/server/handlers/wireguard/shared.go index 0b280a9..3c243e6 100644 --- a/server/handlers/wireguard/shared.go +++ b/server/handlers/wireguard/shared.go @@ -1 +1,14 @@ package wireguard + +import ( + "config-lsp/handlers/wireguard/ast" + "config-lsp/handlers/wireguard/indexes" + protocol "github.com/tliron/glsp/protocol_3_16" +) + +type WGDocument struct { + Config *ast.WGConfig + Indexes *indexes.WGIndexes +} + +var DocumentParserMap = map[protocol.DocumentUri]*WGDocument{}