diff --git a/go.mod b/go.mod index 66ea144..7d7476f 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,9 @@ require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/iancoleman/strcase v0.3.0 // indirect + github.com/k0kubun/pp v3.0.1+incompatible // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/muesli/termenv v0.15.2 // indirect diff --git a/go.sum b/go.sum index 158c4d1..b884e4f 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,13 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/k0kubun/pp v3.0.1+incompatible h1:3tqvf7QgUnZ5tXO6pNAZlrvHgl6DvifjDrd9g2S9Z40= +github.com/k0kubun/pp v3.0.1+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= @@ -37,6 +42,7 @@ golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/handlers/wireguard/analyzer.go b/handlers/wireguard/analyzer.go index d5a30df..e34e069 100644 --- a/handlers/wireguard/analyzer.go +++ b/handlers/wireguard/analyzer.go @@ -86,5 +86,7 @@ func (p wireguardSection) analyzeInterfaceSection() []protocol.Diagnostic { } func (p wireguardParser) analyzeAllowedIPIsInRange() []protocol.Diagnostic { + diagnostics := []protocol.Diagnostic{} + return diagnostics } diff --git a/handlers/wireguard/documentation-fields.go b/handlers/wireguard/documentation-fields.go index 6bf383f..403fdc9 100644 --- a/handlers/wireguard/documentation-fields.go +++ b/handlers/wireguard/documentation-fields.go @@ -314,3 +314,8 @@ Oocal NAT-ed node to remote public node var peerAllowedDuplicateFields = map[string]struct{}{ "AllowedIPs": {}, } + +var optionsHeaderMap = map[string](map[string]docvalues.DocumentationValue){ + "Interface": interfaceOptions, + "Peer": peerOptions, +} diff --git a/handlers/wireguard/parser_test.go b/handlers/wireguard/parser_test.go index 7dacc51..cd7addc 100644 --- a/handlers/wireguard/parser_test.go +++ b/handlers/wireguard/parser_test.go @@ -3,6 +3,8 @@ package wireguard import ( "strings" "testing" + + "github.com/k0kubun/pp" ) func dedent(s string) string { @@ -66,6 +68,22 @@ PublicKey = 5555 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.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) + t.Fatal("parseFromString: Invalid line indexes") + } } func TestEmptySectionAtStartWorksFine( @@ -138,7 +156,7 @@ func TestEmptyFileWorksFine( t.Fatalf("parseFromString failed with error %v", errors) } - if !(len(parser.Sections) == 0) { + if !(len(parser.Sections) == 1) { t.Fatalf("parseFromString failed to collect sections %v", parser.Sections) } } @@ -221,7 +239,7 @@ func TestFileWithOnlyComments( t.Fatalf("parseFromString failed with error: %v", errors) } - if !(len(parser.Sections) == 0) { + if !(len(parser.Sections) == 1) { t.Fatalf("parseFromString failed to collect sections: %v", parser.Sections) } diff --git a/handlers/wireguard/parser_type_test.go b/handlers/wireguard/parser_type_test.go index 6551ce9..ff11fad 100644 --- a/handlers/wireguard/parser_type_test.go +++ b/handlers/wireguard/parser_type_test.go @@ -79,38 +79,46 @@ PublicKey = 1234567890 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 != nil { - t.Fatalf("getBelongingSectionByLine: Expected line 1 to be in no section, but it is in %v", section) + 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 != nil { - t.Fatalf("getBelongingSectionByLine: Expected line 2 to be in no section, but it is in %v", section) + 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 == nil || *section.Name != "Interface" { + 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 == nil || *section.Name != "Interface" { + 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 == nil || *section.Name != "Interface" { + 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 == nil || *section.Name != "Peer" { + 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/handlers/wireguard/text-document-completion.go b/handlers/wireguard/text-document-completion.go index a8c39c8..0c81f53 100644 --- a/handlers/wireguard/text-document-completion.go +++ b/handlers/wireguard/text-document-completion.go @@ -25,10 +25,6 @@ func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionPa return section.getCompletionsForEmptyLine() case LineTypeProperty: - if section == nil { - return nil, nil - } - completions, err := section.getCompletionsForPropertyLine(lineNumber, params.Position.Character) if completions == nil && err != nil { diff --git a/handlers/wireguard/wg-parser.go b/handlers/wireguard/wg-parser.go index a9ece8e..63e7836 100644 --- a/handlers/wireguard/wg-parser.go +++ b/handlers/wireguard/wg-parser.go @@ -18,23 +18,41 @@ type characterLocation struct { End uint32 } +type wireguardLineIndex struct { + Type lineType + BelongingSection *wireguardSection +} + type wireguardParser struct { - GlobalSection *wireguardSection // : if nil then does not belong to a section - Sections map[string]wireguardSection + Sections []*wireguardSection // Used to identify where not to show diagnostics CommentLines map[uint32]struct{} + + // Indexes + LineIndexes map[uint32]wireguardLineIndex } func (p *wireguardParser) clear() { - p.Sections = map[string]wireguardSection{} + 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 (p wireguardParser) getRootCompletionsForEmptyLine() []protocol.CompletionItem { completions := []protocol.CompletionItem{} - if _, found := p.Sections["Interface"]; !found { + if _, found := p.getInterfaceSection(); !found { completions = append(completions, headerInterfaceEnum.ToCompletionItem()) } @@ -76,7 +94,7 @@ func getLineType(line string) lineType { } func (p *wireguardParser) parseFromString(input string) []common.ParseError { - errors := []common.ParseError{} + var errors []common.ParseError lines := strings.Split( input, "\n", @@ -86,7 +104,6 @@ func (p *wireguardParser) parseFromString(input string) []common.ParseError { collectedProperties := wireguardProperties{} var lastPropertyLine *uint32 - var earliestPropertyLine *uint32 for index, line := range lines { currentLineNumber := uint32(len(lines) - index - 1) @@ -95,6 +112,10 @@ func (p *wireguardParser) parseFromString(input string) []common.ParseError { switch lineType { case LineTypeComment: p.CommentLines[currentLineNumber] = struct{}{} + p.LineIndexes[currentLineNumber] = wireguardLineIndex{ + Type: LineTypeComment, + BelongingSection: nil, + } case LineTypeEmpty: continue @@ -110,8 +131,6 @@ func (p *wireguardParser) parseFromString(input string) []common.ParseError { continue } - earliestPropertyLine = ¤tLineNumber - if lastPropertyLine == nil { lastPropertyLine = ¤tLineNumber } @@ -126,14 +145,26 @@ func (p *wireguardParser) parseFromString(input string) []common.ParseError { lastLine = *lastPropertyLine } - name, section := createWireguardSection( + section := createWireguardSection( currentLineNumber, lastLine, line, collectedProperties, ) - p.Sections[name] = section + p.Sections = append(p.Sections, §ion) + + // Add indexes + for lineNumber := range collectedProperties { + p.LineIndexes[lineNumber] = wireguardLineIndex{ + Type: LineTypeProperty, + BelongingSection: §ion, + } + } + p.LineIndexes[currentLineNumber] = wireguardLineIndex{ + Type: LineTypeHeader, + BelongingSection: §ion, + } // Reset collectedProperties = wireguardProperties{} @@ -141,12 +172,80 @@ func (p *wireguardParser) parseFromString(input string) []common.ParseError { } } + var emptySection *wireguardSection + if len(collectedProperties) > 0 { - p.GlobalSection = &wireguardSection{ - StartLine: *earliestPropertyLine, - EndLine: *lastPropertyLine, + emptySection = &wireguardSection{ + StartLine: 0, + EndLine: p.Sections[len(p.Sections)-1].StartLine - 1, Properties: collectedProperties, } + + p.Sections = append(p.Sections, emptySection) + + for lineNumber := range collectedProperties { + p.LineIndexes[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 = &wireguardSection{ + StartLine: 0, + EndLine: endLine, + Properties: collectedProperties, + } + + p.Sections = append(p.Sections, emptySection) + + for newLine := uint32(0); newLine < endLine; newLine++ { + if _, found := p.LineIndexes[newLine]; found { + continue + } + + p.LineIndexes[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.LineIndexes[newLine]; found { + continue + } + + p.LineIndexes[newLine] = wireguardLineIndex{ + Type: LineTypeEmpty, + BelongingSection: section, + } + } } return errors @@ -158,20 +257,8 @@ func (p wireguardParser) getTypeByLine(line uint32) lineType { return LineTypeComment } - // Check if line is a section - for _, section := range p.Sections { - if section.StartLine <= line && section.EndLine >= line { - if section.StartLine == line && section.Name != nil { - return LineTypeHeader - } - - // Check for properties - for propertyLineNumber := range section.Properties { - if propertyLineNumber == line { - return LineTypeProperty - } - } - } + if info, found := p.LineIndexes[line]; found { + return info.Type } return LineTypeEmpty @@ -186,15 +273,10 @@ func (p wireguardParser) getTypeByLine(line uint32) lineType { // [Peer] // // This would return the section [Interface] -func (p wireguardParser) getBelongingSectionByLine(line uint32) *wireguardSection { - for index := range p.Sections { - section := p.Sections[len(p.Sections)-index-1] - - if section.StartLine <= line && section.Name != nil { - return §ion - } +func (p *wireguardParser) getBelongingSectionByLine(line uint32) *wireguardSection { + if info, found := p.LineIndexes[line]; found { + return info.BelongingSection } - // Global section return nil } diff --git a/handlers/wireguard/wg-section.go b/handlers/wireguard/wg-section.go index f14f3a3..44d403e 100644 --- a/handlers/wireguard/wg-section.go +++ b/handlers/wireguard/wg-section.go @@ -5,7 +5,6 @@ import ( "config-lsp/utils" "fmt" "maps" - "math" "regexp" protocol "github.com/tliron/glsp/protocol_3_16" @@ -23,9 +22,18 @@ 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 { - StartLine uint32 - EndLine uint32 + Name *string + StartLine uint32 + EndLine uint32 Properties wireguardProperties } @@ -130,36 +138,27 @@ func (p wireguardSection) getCompletionsForPropertyLine( return nil, err } - if property.Separator == nil { - if p.Name != nil { - switch *p.Name { - case "Interface": - if _, found := interfaceOptions[property.Key.Name]; found { - return getSeparatorCompletion(*property, character) - } - case "Peer": - if _, found := peerOptions[property.Key.Name]; found { - return getSeparatorCompletion(*property, character) - } - } - - // Get empty line completions - return nil, propertyNotFullyTypedError{} - } - + if p.Name == nil { return nil, propertyNotFoundError{} } - var option docvalues.Value + options, found := optionsHeaderMap[*p.Name] - switch *p.Name { - case "Interface": - option = interfaceOptions[property.Key.Name] - case "Peer": - option = peerOptions[property.Key.Name] + if !found { + return nil, propertyNotFoundError{} } - if option == nil { + 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 { return nil, propertyNotFoundError{} } @@ -183,7 +182,7 @@ func createWireguardSection( endLine uint32, headerLine string, props wireguardProperties, -) (string, wireguardSection) { +) wireguardSection { match := validHeaderPattern.FindStringSubmatch(headerLine) var header string @@ -195,7 +194,8 @@ func createWireguardSection( header = match[1] } - return header, wireguardSection{ + return wireguardSection{ + Name: &header, StartLine: startLine, EndLine: endLine, Properties: props,