refactor(wireguard): Move stuff into own packages; Improvements

This commit is contained in:
Myzel394 2024-08-21 21:36:15 +02:00
parent 91f0b0b52b
commit fabdd83eda
No known key found for this signature in database
GPG Key ID: DEC4AAB876F73185
37 changed files with 1052 additions and 973 deletions

Binary file not shown.

View File

@ -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
}

View File

@ -1,4 +1,4 @@
package wireguard package wgcommands
import ( import (
"os/exec" "os/exec"
@ -8,13 +8,13 @@ import (
var whitespacePattern = regexp.MustCompile(`[\s\n]+`) var whitespacePattern = regexp.MustCompile(`[\s\n]+`)
func areWireguardToolsAvailable() bool { func AreWireguardToolsAvailable() bool {
_, err := exec.LookPath("wg") _, err := exec.LookPath("wg")
return err == nil return err == nil
} }
func createNewPrivateKey() (string, error) { func CreateNewPrivateKey() (string, error) {
cmd := exec.Command("wg", "genkey") cmd := exec.Command("wg", "genkey")
bytes, err := cmd.Output() bytes, err := cmd.Output()
@ -26,7 +26,7 @@ func createNewPrivateKey() (string, error) {
return string(whitespacePattern.ReplaceAll(bytes, []byte(""))), nil 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 := exec.Command("wg", "pubkey")
cmd.Stdin = strings.NewReader(privateKey) cmd.Stdin = strings.NewReader(privateKey)
@ -39,7 +39,7 @@ func createPublicKey(privateKey string) (string, error) {
return string(whitespacePattern.ReplaceAll(bytes, []byte(""))), nil return string(whitespacePattern.ReplaceAll(bytes, []byte(""))), nil
} }
func createPresharedKey() (string, error) { func CreatePresharedKey() (string, error) {
cmd := exec.Command("wg", "genpsk") cmd := exec.Command("wg", "genpsk")
bytes, err := cmd.Output() bytes, err := cmd.Output()

View File

@ -1,11 +1,11 @@
package wireguard package wgcommands
import "testing" import "testing"
func TestWireguardAvailable( func TestWireguardAvailable(
t *testing.T, t *testing.T,
) { ) {
if !areWireguardToolsAvailable() { if !AreWireguardToolsAvailable() {
t.Skip("Wireguard tools not available") t.Skip("Wireguard tools not available")
} }
} }
@ -13,7 +13,7 @@ func TestWireguardAvailable(
func TestWireguardPrivateKey( func TestWireguardPrivateKey(
t *testing.T, t *testing.T,
) { ) {
privateKey, err := createNewPrivateKey() privateKey, err := CreateNewPrivateKey()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -26,7 +26,7 @@ func TestWireguardPublicKey(
t *testing.T, t *testing.T,
) { ) {
privateKey := "UPBKR0kLF2C/+Ei5fwN5KHsAcon9xfBX+RWhebYFGWg=" privateKey := "UPBKR0kLF2C/+Ei5fwN5KHsAcon9xfBX+RWhebYFGWg="
publicKey, err := createPublicKey(privateKey) publicKey, err := CreatePublicKey(privateKey)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

View File

@ -1,15 +1,15 @@
// Documentation taken from https://github.com/pirate/wireguard-docs package fields
package wireguard
import ( import (
docvalues "config-lsp/doc-values" docvalues "config-lsp/doc-values"
) )
var headerInterfaceEnum = docvalues.CreateEnumStringWithDoc( // Documentation taken from https://github.com/pirate/wireguard-docs
var HeaderInterfaceEnum = docvalues.CreateEnumStringWithDoc(
"[Interface]", "[Interface]",
"Defines the VPN settings for the local node.", "Defines the VPN settings for the local node.",
) )
var headerPeerEnum = docvalues.CreateEnumStringWithDoc( var HeaderPeerEnum = docvalues.CreateEnumStringWithDoc(
"[Peer]", "[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. `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 minMTUValue = 68
var maxMTUValue = 1500 var maxMTUValue = 1500
var interfaceOptions = map[string]docvalues.DocumentationValue{ var InterfaceOptions = map[string]docvalues.DocumentationValue{
"Address": { "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. 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": {}, "PreUp": {},
"PostUp": {}, "PostUp": {},
"PreDown": {}, "PreDown": {},
"PostDown": {}, "PostDown": {},
} }
var peerOptions = map[string]docvalues.DocumentationValue{ var PeerOptions = map[string]docvalues.DocumentationValue{
"Endpoint": { "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. 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": {}, "AllowedIPs": {},
} }
var optionsHeaderMap = map[string](map[string]docvalues.DocumentationValue){ var OptionsHeaderMap = map[string](map[string]docvalues.DocumentationValue){
"Interface": interfaceOptions, "Interface": InterfaceOptions,
"Peer": peerOptions, "Peer": PeerOptions,
} }

View File

@ -1,7 +1,9 @@
package wireguard package handlers
import ( import (
docvalues "config-lsp/doc-values" docvalues "config-lsp/doc-values"
"config-lsp/handlers/wireguard/fields"
"config-lsp/handlers/wireguard/parser"
"config-lsp/utils" "config-lsp/utils"
"fmt" "fmt"
"slices" "slices"
@ -10,33 +12,38 @@ import (
protocol "github.com/tliron/glsp/protocol_3_16" protocol "github.com/tliron/glsp/protocol_3_16"
) )
func (p wireguardParser) analyze() []protocol.Diagnostic { func Analyze(
sectionsErrors := p.analyzeSections() p parser.WireguardParser,
) []protocol.Diagnostic {
sectionsErrors := analyzeSections(p.Sections)
sectionsErrors = append(analyzeOnlyOneInterfaceSectionSpecified(p))
if len(sectionsErrors) > 0 { if len(sectionsErrors) > 0 {
return sectionsErrors return sectionsErrors
} }
validCheckErrors := p.checkIfValuesAreValid() validCheckErrors := checkIfValuesAreValid(p.Sections)
if len(validCheckErrors) > 0 { if len(validCheckErrors) > 0 {
return validCheckErrors return validCheckErrors
} }
diagnostics := []protocol.Diagnostic{} diagnostics := make([]protocol.Diagnostic, 0)
diagnostics = append(diagnostics, p.checkForDuplicateProperties()...) diagnostics = append(diagnostics, analyzeParserForDuplicateProperties(p)...)
diagnostics = append(diagnostics, p.analyzeDNSContainsFallback()...) diagnostics = append(diagnostics, analyzeDNSContainsFallback(p)...)
diagnostics = append(diagnostics, p.analyzeKeepAliveIsSet()...) diagnostics = append(diagnostics, analyzeKeepAliveIsSet(p)...)
diagnostics = append(diagnostics, p.analyzeSymmetricPropertiesExist()...) diagnostics = append(diagnostics, analyzeSymmetricPropertiesExist(p)...)
return diagnostics return diagnostics
} }
func (p wireguardParser) analyzeSections() []protocol.Diagnostic { func analyzeSections(
diagnostics := []protocol.Diagnostic{} sections []*parser.WireguardSection,
) []protocol.Diagnostic {
var diagnostics []protocol.Diagnostic
for _, section := range p.Sections { for _, section := range sections {
sectionDiagnostics := section.analyzeSection() sectionDiagnostics := analyzeSection(*section)
if len(sectionDiagnostics) > 0 { if len(sectionDiagnostics) > 0 {
diagnostics = append(diagnostics, sectionDiagnostics...) diagnostics = append(diagnostics, sectionDiagnostics...)
@ -47,20 +54,22 @@ func (p wireguardParser) analyzeSections() []protocol.Diagnostic {
return diagnostics return diagnostics
} }
return p.analyzeOnlyOneInterfaceSectionSpecified() return diagnostics
} }
func (p wireguardParser) analyzeOnlyOneInterfaceSectionSpecified() []protocol.Diagnostic { func analyzeOnlyOneInterfaceSectionSpecified(
diagnostics := []protocol.Diagnostic{} p parser.WireguardParser,
) []protocol.Diagnostic {
var diagnostics []protocol.Diagnostic
alreadyFound := false alreadyFound := false
for _, section := range p.getSectionsByName("Interface") { for _, section := range p.GetSectionsByName("Interface") {
if alreadyFound { if alreadyFound {
severity := protocol.DiagnosticSeverityError severity := protocol.DiagnosticSeverityError
diagnostics = append(diagnostics, protocol.Diagnostic{ diagnostics = append(diagnostics, protocol.Diagnostic{
Message: "Only one [Interface] section is allowed", Message: "Only one [Interface] section is allowed",
Severity: &severity, Severity: &severity,
Range: section.getHeaderLineRange(), Range: section.GetHeaderLineRange(),
}) })
} }
@ -70,8 +79,10 @@ func (p wireguardParser) analyzeOnlyOneInterfaceSectionSpecified() []protocol.Di
return diagnostics return diagnostics
} }
func (p wireguardParser) analyzeDNSContainsFallback() []protocol.Diagnostic { func analyzeDNSContainsFallback(
lineNumber, property := p.fetchPropertyByName("DNS") p parser.WireguardParser,
) []protocol.Diagnostic {
lineNumber, property := p.FindFirstPropertyByName("DNS")
if property == nil { if property == nil {
return []protocol.Diagnostic{} return []protocol.Diagnostic{}
@ -103,17 +114,19 @@ func (p wireguardParser) analyzeDNSContainsFallback() []protocol.Diagnostic {
return []protocol.Diagnostic{} return []protocol.Diagnostic{}
} }
func (p wireguardParser) analyzeKeepAliveIsSet() []protocol.Diagnostic { func analyzeKeepAliveIsSet(
diagnostics := make([]protocol.Diagnostic, 0) 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 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 severity := protocol.DiagnosticSeverityHint
diagnostics = append(diagnostics, protocol.Diagnostic{ 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", 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, Severity: &severity,
Range: section.getRange(), Range: section.GetRange(),
}) })
} }
} }
@ -123,14 +136,16 @@ func (p wireguardParser) analyzeKeepAliveIsSet() []protocol.Diagnostic {
// Check if the values are valid. // Check if the values are valid.
// Assumes that sections have been analyzed already. // Assumes that sections have been analyzed already.
func (p wireguardParser) checkIfValuesAreValid() []protocol.Diagnostic { func checkIfValuesAreValid(
diagnostics := []protocol.Diagnostic{} sections []*parser.WireguardSection,
) []protocol.Diagnostic {
var diagnostics []protocol.Diagnostic
for _, section := range p.Sections { for _, section := range sections {
for lineNumber, property := range section.Properties { for lineNumber, property := range section.Properties {
diagnostics = append( diagnostics = append(
diagnostics, diagnostics,
property.analyzeProperty(section, lineNumber)..., analyzeProperty(property, section, lineNumber)...,
) )
} }
} }
@ -138,8 +153,10 @@ func (p wireguardParser) checkIfValuesAreValid() []protocol.Diagnostic {
return diagnostics return diagnostics
} }
func (s wireguardSection) analyzeSection() []protocol.Diagnostic { func analyzeSection(
diagnostics := []protocol.Diagnostic{} s parser.WireguardSection,
) []protocol.Diagnostic {
var diagnostics []protocol.Diagnostic
if s.Name == nil { if s.Name == nil {
// No section name // No section name
@ -147,18 +164,18 @@ func (s wireguardSection) analyzeSection() []protocol.Diagnostic {
diagnostics = append(diagnostics, protocol.Diagnostic{ diagnostics = append(diagnostics, protocol.Diagnostic{
Message: "This section is missing a name", Message: "This section is missing a name",
Severity: &severity, Severity: &severity,
Range: s.getRange(), Range: s.GetRange(),
}) })
return diagnostics return diagnostics
} }
if _, found := optionsHeaderMap[*s.Name]; !found { if _, found := fields.OptionsHeaderMap[*s.Name]; !found {
// Unknown section // Unknown section
severity := protocol.DiagnosticSeverityError severity := protocol.DiagnosticSeverityError
diagnostics = append(diagnostics, protocol.Diagnostic{ diagnostics = append(diagnostics, protocol.Diagnostic{
Message: fmt.Sprintf("Unknown section '%s'. It must be one of: [Interface], [Peer]", *s.Name), Message: fmt.Sprintf("Unknown section '%s'. It must be one of: [Interface], [Peer]", *s.Name),
Severity: &severity, Severity: &severity,
Range: s.getHeaderLineRange(), Range: s.GetHeaderLineRange(),
}) })
return diagnostics return diagnostics
@ -171,11 +188,12 @@ func (s wireguardSection) analyzeSection() []protocol.Diagnostic {
// Returns a list of diagnostics. // Returns a list of diagnostics.
// `belongingSection` is the section to which the property belongs. This value is // `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. // expected to be non-nil and expected to be a valid Wireguard section.
func (p wireguardProperty) analyzeProperty( func analyzeProperty(
belongingSection *wireguardSection, p parser.WireguardProperty,
belongingSection *parser.WireguardSection,
propertyLine uint32, propertyLine uint32,
) []protocol.Diagnostic { ) []protocol.Diagnostic {
sectionOptions := optionsHeaderMap[*belongingSection.Name] sectionOptions := fields.OptionsHeaderMap[*belongingSection.Name]
option, found := sectionOptions[p.Key.Name] option, found := sectionOptions[p.Key.Name]
if !found { if !found {
@ -206,7 +224,7 @@ func (p wireguardProperty) analyzeProperty(
{ {
Message: "Property is missing a value", Message: "Property is missing a value",
Severity: &severity, 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) diagnostics := make([]protocol.Diagnostic, 0)
for _, section := range p.Sections { for _, section := range p.Sections {
diagnostics = append(diagnostics, section.analyzeDuplicateProperties()...) diagnostics = append(diagnostics, analyzeDuplicateProperties(*section)...)
} }
return diagnostics return diagnostics
} }
func (p wireguardSection) analyzeDuplicateProperties() []protocol.Diagnostic { func analyzeDuplicateProperties(
diagnostics := make([]protocol.Diagnostic, 0) s parser.WireguardSection,
) []protocol.Diagnostic {
var diagnostics []protocol.Diagnostic
existingProperties := make(map[string]uint32) existingProperties := make(map[string]uint32)
lines := utils.KeysOfMap(p.Properties) lines := utils.KeysOfMap(s.Properties)
slices.Sort(lines) slices.Sort(lines)
for _, currentLineNumber := range lines { for _, currentLineNumber := range lines {
property := p.Properties[currentLineNumber] property := s.Properties[currentLineNumber]
var skipCheck = false var skipCheck = false
if p.Name != nil { if s.Name != nil {
switch *p.Name { switch *s.Name {
case "Interface": case "Interface":
if _, found := interfaceAllowedDuplicateFields[property.Key.Name]; found { if _, found := fields.InterfaceAllowedDuplicateFields[property.Key.Name]; found {
skipCheck = true skipCheck = true
} }
case "Peer": case "Peer":
if _, found := peerAllowedDuplicateFields[property.Key.Name]; found { if _, found := fields.PeerAllowedDuplicateFields[property.Key.Name]; found {
skipCheck = true skipCheck = true
} }
} }
@ -295,33 +317,40 @@ func (p wireguardSection) analyzeDuplicateProperties() []protocol.Diagnostic {
return diagnostics 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 { func (p wireguardParser) analyzeAllowedIPIsInRange() []protocol.Diagnostic {
diagnostics := make([]protocol.Diagnostic, 0) diagnostics := make([]protocol.Diagnostic, 0)
return diagnostics return diagnostics
} }
*/
func (p wireguardParser) analyzeSymmetricPropertiesExist() []protocol.Diagnostic { func analyzeSymmetricPropertiesExist(
p parser.WireguardParser,
) []protocol.Diagnostic {
diagnostics := make([]protocol.Diagnostic, 0, 4) diagnostics := make([]protocol.Diagnostic, 0, 4)
severity := protocol.DiagnosticSeverityHint severity := protocol.DiagnosticSeverityHint
for _, section := range p.getSectionsByName("Interface") { for _, section := range p.GetSectionsByName("Interface") {
preUpLine, preUpProperty := section.fetchFirstProperty("PreUp") preUpLine, preUpProperty := section.FetchFirstProperty("PreUp")
preDownLine, preDownProperty := section.fetchFirstProperty("PreDown") preDownLine, preDownProperty := section.FetchFirstProperty("PreDown")
postUpLine, postUpProperty := section.fetchFirstProperty("PostUp") postUpLine, postUpProperty := section.FetchFirstProperty("PostUp")
postDownLine, postDownProperty := section.fetchFirstProperty("PostDown") postDownLine, postDownProperty := section.FetchFirstProperty("PostDown")
if preUpProperty != nil && preDownProperty == nil { if preUpProperty != nil && preDownProperty == nil {
diagnostics = append(diagnostics, protocol.Diagnostic{ diagnostics = append(diagnostics, protocol.Diagnostic{
Message: "PreUp is set, but PreDown is not. It is recommended to set both properties symmetrically", 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, Severity: &severity,
}) })
} else if preUpProperty == nil && preDownProperty != nil { } else if preUpProperty == nil && preDownProperty != nil {
diagnostics = append(diagnostics, protocol.Diagnostic{ diagnostics = append(diagnostics, protocol.Diagnostic{
Message: "PreDown is set, but PreUp is not. It is recommended to set both properties symmetrically", 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, Severity: &severity,
}) })
} }
@ -329,13 +358,13 @@ func (p wireguardParser) analyzeSymmetricPropertiesExist() []protocol.Diagnostic
if postUpProperty != nil && postDownProperty == nil { if postUpProperty != nil && postDownProperty == nil {
diagnostics = append(diagnostics, protocol.Diagnostic{ diagnostics = append(diagnostics, protocol.Diagnostic{
Message: "PostUp is set, but PostDown is not. It is recommended to set both properties symmetrically", 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, Severity: &severity,
}) })
} else if postUpProperty == nil && postDownProperty != nil { } else if postUpProperty == nil && postDownProperty != nil {
diagnostics = append(diagnostics, protocol.Diagnostic{ diagnostics = append(diagnostics, protocol.Diagnostic{
Message: "PostDown is set, but PostUp is not. It is recommended to set both properties symmetrically", 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, Severity: &severity,
}) })
} }

View File

@ -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) { func TestMultipleIntefaces(t *testing.T) {
content := dedent(` content := utils.Dedent(`
[Interface] [Interface]
PrivateKey = abc PrivateKey = abc
[Interface] [Interface]
PrivateKey = def PrivateKey = def
`) `)
parser := createWireguardParser() p := parser.CreateWireguardParser()
parser.parseFromString(content) p.ParseFromString(content)
diagnostics := parser.analyze() diagnostics := Analyze(p)
if len(diagnostics) == 0 { if len(diagnostics) == 0 {
t.Errorf("Expected diagnostic errors, got %d", len(diagnostics)) t.Errorf("Expected diagnostic errors, got %d", len(diagnostics))
@ -21,14 +25,14 @@ PrivateKey = def
} }
func TestInvalidValue(t *testing.T) { func TestInvalidValue(t *testing.T) {
content := dedent(` content := utils.Dedent(`
[Interface] [Interface]
DNS = nope DNS = nope
`) `)
parser := createWireguardParser() p := parser.CreateWireguardParser()
parser.parseFromString(content) p.ParseFromString(content)
diagnostics := parser.analyze() diagnostics := Analyze(p)
if len(diagnostics) == 0 { if len(diagnostics) == 0 {
t.Errorf("Expected diagnostic errors, got %d", len(diagnostics)) t.Errorf("Expected diagnostic errors, got %d", len(diagnostics))
@ -36,16 +40,16 @@ DNS = nope
} }
func TestDuplicateProperties(t *testing.T) { func TestDuplicateProperties(t *testing.T) {
content := dedent(` content := utils.Dedent(`
[Interface] [Interface]
PrivateKey = abc PrivateKey = abc
DNS = 1.1.1.1 DNS = 1.1.1.1
PrivateKey = def PrivateKey = def
`) `)
parser := createWireguardParser() p := parser.CreateWireguardParser()
parser.parseFromString(content) p.ParseFromString(content)
diagnostics := parser.analyze() diagnostics := Analyze(p)
if len(diagnostics) == 0 { if len(diagnostics) == 0 {
t.Errorf("Expected diagnostic errors, got %d", len(diagnostics)) t.Errorf("Expected diagnostic errors, got %d", len(diagnostics))

View File

@ -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
}

View File

@ -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
}

View File

@ -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( func TestSimplePropertyInInterface(
t *testing.T, t *testing.T,
) { ) {
sample := dedent(` sample := utils.Dedent(`
[Interface] [Interface]
`) `)
parser := createWireguardParser() p := parser.CreateWireguardParser()
parser.parseFromString(sample) p.ParseFromString(sample)
completions, err := parser.Sections[0].getCompletionsForEmptyLine() completions, err := GetCompletionsForSectionEmptyLine(*p.Sections[0])
if err != nil { if err != nil {
t.Fatalf("getCompletionsForEmptyLine failed with error: %v", err) t.Fatalf("getCompletionsForEmptyLine failed with error: %v", err)
} }
if len(completions) != len(interfaceOptions) { if len(completions) != len(fields.InterfaceOptions) {
t.Fatalf("getCompletionsForEmptyLine: Expected %v completions, but got %v", len(interfaceOptions), len(completions)) t.Fatalf("getCompletionsForEmptyLine: Expected %v completions, but got %v", len(fields.InterfaceOptions), len(completions))
} }
} }
func TestSimpleOneExistingPropertyInInterface( func TestSimpleOneExistingPropertyInInterface(
t *testing.T, t *testing.T,
) { ) {
sample := dedent(` sample := utils.Dedent(`
[Interface] [Interface]
PrivateKey = 1234567890 PrivateKey = 1234567890
`) `)
parser := createWireguardParser() p := parser.CreateWireguardParser()
parser.parseFromString(sample) p.ParseFromString(sample)
completions, err := parser.Sections[0].getCompletionsForEmptyLine() completions, err := GetCompletionsForSectionEmptyLine(*p.Sections[0])
if err != nil { if err != nil {
t.Fatalf("getCompletionsForEmptyLine failed with error: %v", err) t.Fatalf("getCompletionsForEmptyLine failed with error: %v", err)
} }
expected := len(interfaceOptions) - 1 expected := len(fields.InterfaceOptions) - 1
if len(completions) != expected { if len(completions) != expected {
t.Fatalf("getCompletionsForEmptyLine: Expected %v completions, but got %v", expected, len(completions)) t.Fatalf("getCompletionsForEmptyLine: Expected %v completions, but got %v", expected, len(completions))
} }
@ -49,13 +54,13 @@ PrivateKey = 1234567890
func TestEmptyRootCompletionsWork( func TestEmptyRootCompletionsWork(
t *testing.T, t *testing.T,
) { ) {
sample := dedent(` sample := utils.Dedent(`
`) `)
parser := createWireguardParser() p := parser.CreateWireguardParser()
parser.parseFromString(sample) p.ParseFromString(sample)
completions := parser.getRootCompletionsForEmptyLine() completions, _ := GetRootCompletionsForEmptyLine(p)
if len(completions) != 2 { if len(completions) != 2 {
t.Fatalf("getRootCompletionsForEmptyLine: Expected 2 completions, but got %v", len(completions)) t.Fatalf("getRootCompletionsForEmptyLine: Expected 2 completions, but got %v", len(completions))
@ -65,14 +70,14 @@ func TestEmptyRootCompletionsWork(
func TestInterfaceSectionRootCompletionsBeforeWork( func TestInterfaceSectionRootCompletionsBeforeWork(
t *testing.T, t *testing.T,
) { ) {
sample := dedent(` sample := utils.Dedent(`
[Interface] [Interface]
`) `)
parser := createWireguardParser() p := parser.CreateWireguardParser()
parser.parseFromString(sample) p.ParseFromString(sample)
completions := parser.getRootCompletionsForEmptyLine() completions, _ := GetRootCompletionsForEmptyLine(p)
if len(completions) != 1 { if len(completions) != 1 {
t.Fatalf("getRootCompletionsForEmptyLine: Expected 1 completions, but got %v", len(completions)) t.Fatalf("getRootCompletionsForEmptyLine: Expected 1 completions, but got %v", len(completions))
@ -82,15 +87,15 @@ func TestInterfaceSectionRootCompletionsBeforeWork(
func TestInterfaceAndPeerSectionRootCompletionsWork( func TestInterfaceAndPeerSectionRootCompletionsWork(
t *testing.T, t *testing.T,
) { ) {
sample := dedent(` sample := utils.Dedent(`
[Interface] [Interface]
[Peer] [Peer]
`) `)
parser := createWireguardParser() p := parser.CreateWireguardParser()
parser.parseFromString(sample) p.ParseFromString(sample)
completions := parser.getRootCompletionsForEmptyLine() completions, _ := GetRootCompletionsForEmptyLine(p)
if len(completions) != 1 { if len(completions) != 1 {
t.Fatalf("getRootCompletionsForEmptyLine: Expected 1 completions, but got %v", len(completions)) t.Fatalf("getRootCompletionsForEmptyLine: Expected 1 completions, but got %v", len(completions))
@ -100,14 +105,14 @@ func TestInterfaceAndPeerSectionRootCompletionsWork(
func TestPropertyNoSepatorShouldCompleteSeparator( func TestPropertyNoSepatorShouldCompleteSeparator(
t *testing.T, t *testing.T,
) { ) {
sample := dedent(` sample := utils.Dedent(`
[Interface] [Interface]
DNS DNS
`) `)
parser := createWireguardParser() p := parser.CreateWireguardParser()
parser.parseFromString(sample) p.ParseFromString(sample)
completions, err := parser.Sections[0].getCompletionsForPropertyLine(1, 3) completions, err := GetCompletionsForSectionPropertyLine(*p.Sections[0], 1, 3)
if err == nil { if err == nil {
t.Fatalf("getCompletionsForPropertyLine err is nil but should not be") t.Fatalf("getCompletionsForPropertyLine err is nil but should not be")
@ -125,14 +130,14 @@ DNS
func TestPropertyNoSeparatorWithSpaceShouldCompleteSeparator( func TestPropertyNoSeparatorWithSpaceShouldCompleteSeparator(
t *testing.T, t *testing.T,
) { ) {
sample := dedent(` sample := utils.Dedent(`
[Interface] [Interface]
DNS DNS
`) `)
parser := createWireguardParser() p := parser.CreateWireguardParser()
parser.parseFromString(sample) p.ParseFromString(sample)
completions, err := parser.Sections[0].getCompletionsForPropertyLine(1, 4) completions, err := GetCompletionsForSectionPropertyLine(*p.Sections[0], 1, 4)
if err == nil { if err == nil {
t.Fatalf("getCompletionsForPropertyLine err is nil but should not be") t.Fatalf("getCompletionsForPropertyLine err is nil but should not be")
@ -150,20 +155,20 @@ DNS
func TestHeaderButNoProperty( func TestHeaderButNoProperty(
t *testing.T, t *testing.T,
) { ) {
sample := dedent(` sample := utils.Dedent(`
[Interface] [Interface]
`) `)
parser := createWireguardParser() p := parser.CreateWireguardParser()
parser.parseFromString(sample) p.ParseFromString(sample)
completions, err := parser.Sections[0].getCompletionsForEmptyLine() completions, err := GetCompletionsForSectionEmptyLine(*p.Sections[0])
if err != nil { if err != nil {
t.Fatalf("getCompletionsForEmptyLine failed with error: %v", err) t.Fatalf("getCompletionsForEmptyLine failed with error: %v", err)
} }
if len(completions) != len(interfaceOptions) { if len(completions) != len(fields.InterfaceOptions) {
t.Fatalf("getCompletionsForEmptyLine: Expected %v completions, but got %v", len(interfaceOptions), len(completions)) t.Fatalf("getCompletionsForEmptyLine: Expected %v completions, but got %v", len(fields.InterfaceOptions), len(completions))
} }
} }

View File

@ -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, params *protocol.CodeActionParams,
parser *wireguardParser,
) []protocol.CodeAction { ) []protocol.CodeAction {
line := params.Range.Start.Line 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.StartLine >= line && line <= section.EndLine && section.Name != nil && *section.Name == "Peer" {
if section.existsProperty("Endpoint") && !section.existsProperty("PersistentKeepalive") { if section.ExistsProperty("Endpoint") && !section.ExistsProperty("PersistentKeepalive") {
commandID := "wireguard." + codeActionAddKeepalive commandID := "wireguard." + CodeActionAddKeepalive
command := protocol.Command{ command := protocol.Command{
Title: "Add PersistentKeepalive", Title: "Add PersistentKeepalive",
Command: string(commandID), Command: string(commandID),
Arguments: []any{ Arguments: []any{
codeActionAddKeepaliveArgs{ CodeActionAddKeepaliveArgs{
URI: params.TextDocument.URI, URI: params.TextDocument.URI,
SectionIndex: uint32(index), SectionIndex: uint32(index),
}, },
@ -36,12 +40,12 @@ func getKeepaliveCodeActions(
return nil return nil
} }
func getKeyGenerationCodeActions( func GetKeyGenerationCodeActions(
p *parser.WireguardParser,
params *protocol.CodeActionParams, params *protocol.CodeActionParams,
parser *wireguardParser,
) []protocol.CodeAction { ) []protocol.CodeAction {
line := params.Range.Start.Line line := params.Range.Start.Line
section, property := parser.getPropertyByLine(line) section, property := p.GetPropertyByLine(line)
if section == nil || property == nil || property.Separator == nil { if section == nil || property == nil || property.Separator == nil {
return nil return nil
@ -49,16 +53,16 @@ func getKeyGenerationCodeActions(
switch property.Key.Name { switch property.Key.Name {
case "PrivateKey": case "PrivateKey":
if !areWireguardToolsAvailable() { if !wgcommands.AreWireguardToolsAvailable() {
return nil return nil
} }
commandID := "wireguard." + codeActionGeneratePrivateKey commandID := "wireguard." + CodeActionGeneratePrivateKey
command := protocol.Command{ command := protocol.Command{
Title: "Generate Private Key", Title: "Generate Private Key",
Command: string(commandID), Command: string(commandID),
Arguments: []any{ Arguments: []any{
codeActionGeneratePrivateKeyArgs{ CodeActionGeneratePrivateKeyArgs{
URI: params.TextDocument.URI, URI: params.TextDocument.URI,
Line: line, Line: line,
}, },
@ -72,16 +76,16 @@ func getKeyGenerationCodeActions(
}, },
} }
case "PresharedKey": case "PresharedKey":
if !areWireguardToolsAvailable() { if !wgcommands.AreWireguardToolsAvailable() {
return nil return nil
} }
commandID := "wireguard." + codeActionGeneratePresharedKey commandID := "wireguard." + CodeActionGeneratePresharedKey
command := protocol.Command{ command := protocol.Command{
Title: "Generate PresharedKey", Title: "Generate PresharedKey",
Command: string(commandID), Command: string(commandID),
Arguments: []any{ Arguments: []any{
codeActionGeneratePresharedKeyArgs{ CodeActionGeneratePresharedKeyArgs{
URI: params.TextDocument.URI, URI: params.TextDocument.URI,
Line: line, Line: line,
}, },

View File

@ -1,14 +1,20 @@
package wireguard package handlers
import ( import (
docvalues "config-lsp/doc-values" docvalues "config-lsp/doc-values"
"config-lsp/handlers/wireguard/fields"
"config-lsp/handlers/wireguard/parser"
"fmt" "fmt"
"strings" "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 { if cursor <= p.Key.Location.End {
options, found := optionsHeaderMap[*section.Name] options, found := fields.OptionsHeaderMap[*section.Name]
if !found { if !found {
return []string{} return []string{}
@ -23,7 +29,7 @@ func (p wireguardProperty) getHoverInfo(cursor uint32, section *wireguardSection
return strings.Split(option.Documentation, "\n") return strings.Split(option.Documentation, "\n")
} }
options, found := optionsHeaderMap[*section.Name] options, found := fields.OptionsHeaderMap[*section.Name]
if !found { if !found {
return []string{} return []string{}
@ -36,22 +42,54 @@ func (p wireguardProperty) getHoverInfo(cursor uint32, section *wireguardSection
return []string{} return []string{}
} }
func (p wireguardParser) getHeaderInfo(line uint32, cursor uint32) []string { func getSectionInfo(s parser.WireguardSection) []string {
section := p.getSectionByLine(line) 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 { if section == nil {
return []string{} return []string{}
} }
sectionInfo := section.getHeaderInfo() sectionInfo := getSectionInfo(*section)
property, _ := section.findProperty(line) property, _ := section.GetPropertyByLine(line)
if property == nil { if property == nil {
return sectionInfo return sectionInfo
} }
propertyInfo := property.getHoverInfo(cursor, section) propertyInfo := getPropertyInfo(*property, cursor, *section)
if len(propertyInfo) == 0 { if len(propertyInfo) == 0 {
return sectionInfo return sectionInfo
@ -65,31 +103,3 @@ func (p wireguardParser) getHeaderInfo(line uint32, cursor uint32) []string {
return contents 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
}

View File

@ -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{}

View File

@ -1,17 +1,18 @@
package wireguard package lsp
import ( import (
"config-lsp/handlers/wireguard/handlers"
"github.com/tliron/glsp" "github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16" protocol "github.com/tliron/glsp/protocol_3_16"
) )
func TextDocumentCodeAction(context *glsp.Context, params *protocol.CodeActionParams) ([]protocol.CodeAction, error) { 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 := make([]protocol.CodeAction, 0, 2)
actions = append(actions, getKeyGenerationCodeActions(params, parser)...) actions = append(actions, handlers.GetKeyGenerationCodeActions(p, params)...)
actions = append(actions, getKeepaliveCodeActions(params, parser)...) actions = append(actions, handlers.GetKeepaliveCodeActions(p, params)...)
if len(actions) > 0 { if len(actions) > 0 {
return actions, nil return actions, nil

View File

@ -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")
}

View File

@ -1,7 +1,8 @@
package wireguard package lsp
import ( import (
"config-lsp/common" "config-lsp/common"
"config-lsp/handlers/wireguard/handlers"
"config-lsp/utils" "config-lsp/utils"
"github.com/tliron/glsp" "github.com/tliron/glsp"
@ -15,11 +16,11 @@ func TextDocumentDidChange(
content := params.ContentChanges[0].(protocol.TextDocumentContentChangeEventWhole).Text content := params.ContentChanges[0].(protocol.TextDocumentContentChangeEventWhole).Text
common.ClearDiagnostics(context, params.TextDocument.URI) common.ClearDiagnostics(context, params.TextDocument.URI)
parser := documentParserMap[params.TextDocument.URI] p := documentParserMap[params.TextDocument.URI]
parser.clear() p.Clear()
diagnostics := make([]protocol.Diagnostic, 0) diagnostics := make([]protocol.Diagnostic, 0)
errors := parser.parseFromString(content) errors := p.ParseFromString(content)
if len(errors) > 0 { if len(errors) > 0 {
diagnostics = append(diagnostics, utils.Map( 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 { if len(diagnostics) > 0 {
common.SendDiagnostics(context, params.TextDocument.URI, diagnostics) common.SendDiagnostics(context, params.TextDocument.URI, diagnostics)

View File

@ -1,4 +1,4 @@
package wireguard package lsp
import ( import (
"github.com/tliron/glsp" "github.com/tliron/glsp"

View File

@ -1,7 +1,8 @@
package wireguard package lsp
import ( import (
"config-lsp/common" "config-lsp/common"
"config-lsp/handlers/wireguard/parser"
"config-lsp/utils" "config-lsp/utils"
"github.com/tliron/glsp" "github.com/tliron/glsp"
@ -14,10 +15,10 @@ func TextDocumentDidOpen(
) error { ) error {
common.ClearDiagnostics(context, params.TextDocument.URI) common.ClearDiagnostics(context, params.TextDocument.URI)
parser := createWireguardParser() p := parser.CreateWireguardParser()
documentParserMap[params.TextDocument.URI] = &parser documentParserMap[params.TextDocument.URI] = &p
errors := parser.parseFromString(params.TextDocument.Text) errors := p.ParseFromString(params.TextDocument.Text)
diagnostics := utils.Map( diagnostics := utils.Map(
errors, errors,

View File

@ -1,6 +1,8 @@
package wireguard package lsp
import ( import (
"config-lsp/handlers/wireguard/handlers"
"config-lsp/handlers/wireguard/parser"
"strings" "strings"
"github.com/tliron/glsp" "github.com/tliron/glsp"
@ -11,17 +13,21 @@ func TextDocumentHover(
context *glsp.Context, context *glsp.Context,
params *protocol.HoverParams, params *protocol.HoverParams,
) (*protocol.Hover, error) { ) (*protocol.Hover, error) {
parser := documentParserMap[params.TextDocument.URI] p := documentParserMap[params.TextDocument.URI]
switch parser.getTypeByLine(params.Position.Line) { switch p.GetTypeByLine(params.Position.Line) {
case LineTypeComment: case parser.LineTypeComment:
return nil, nil return nil, nil
case LineTypeEmpty: case parser.LineTypeEmpty:
return nil, nil return nil, nil
case LineTypeHeader: case parser.LineTypeHeader:
fallthrough fallthrough
case LineTypeProperty: case parser.LineTypeProperty:
documentation := parser.getHeaderInfo(params.Position.Line, params.Position.Character) documentation := handlers.GetHoverContent(
*p,
params.Position.Line,
params.Position.Character,
)
hover := protocol.Hover{ hover := protocol.Hover{
Contents: protocol.MarkupContent{ Contents: protocol.MarkupContent{

View File

@ -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
}

View File

@ -1,11 +1,14 @@
package wireguard package parser
import "testing" import (
"config-lsp/utils"
"testing"
)
func TestGetLineTypeWorksCorrectly( func TestGetLineTypeWorksCorrectly(
t *testing.T, t *testing.T,
) { ) {
sample := dedent(` sample := utils.Dedent(`
# A comment at the very top # A comment at the very top
Test=Hello Test=Hello
@ -21,35 +24,35 @@ PublicKey = 1234567890
; I'm a comment ; I'm a comment
`) `)
parser := createWireguardParser() parser := CreateWireguardParser()
parser.parseFromString(sample) parser.ParseFromString(sample)
lineType := parser.getTypeByLine(0) lineType := parser.GetTypeByLine(0)
if lineType != LineTypeComment { if lineType != LineTypeComment {
t.Fatalf("getTypeByLine: Expected line 0 to be a comment, but it is %v", lineType) 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 { if lineType != LineTypeProperty {
t.Fatalf("getTypeByLine: Expected line 1 to be a property, but it is %v", lineType) 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 { if lineType != LineTypeEmpty {
t.Fatalf("getTypeByLine: Expected line 2 to be empty, but it is %v", lineType) 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 { if lineType != LineTypeHeader {
t.Fatalf("getTypeByLine: Expected line 3 to be a header, but it is %v", lineType) 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 { if lineType != LineTypeProperty {
t.Fatalf("getTypeByLine: Expected line 4 to be a property, but it is %v", lineType) 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 { if lineType != LineTypeComment {
t.Fatalf("getTypeByLine: Expected line 12 to be a comment, but it is %v", lineType) t.Fatalf("getTypeByLine: Expected line 12 to be a comment, but it is %v", lineType)
} }
@ -58,7 +61,7 @@ PublicKey = 1234567890
func TestGetBelongingSectionWorksCorrectly( func TestGetBelongingSectionWorksCorrectly(
t *testing.T, t *testing.T,
) { ) {
sample := dedent(` sample := utils.Dedent(`
# A comment at the very top # A comment at the very top
Test=Hello Test=Hello
@ -74,48 +77,48 @@ PublicKey = 1234567890
; I'm a comment ; I'm a comment
`) `)
parser := createWireguardParser() parser := CreateWireguardParser()
parser.parseFromString(sample) parser.ParseFromString(sample)
section := parser.getBelongingSectionByLine(0) section := parser.GetBelongingSectionByLine(0)
// Comment // Comment
if section != nil { if section != nil {
t.Fatalf("getBelongingSectionByLine: Expected line 0 to be in no section, but it is in %v", section) 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] { if section != parser.Sections[1] {
t.Fatalf("getBelongingSectionByLine: Expected line 1 to be in global section, but it is in %v", section) 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] { if section != parser.Sections[1] {
t.Fatalf("getBelongingSectionByLine: Expected line 2 to be in global section, but it is in %v", section) 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] { if section != parser.Sections[2] {
t.Fatalf("getBelongingSectionByLine: Expected line 3 to be in section Interface, but it is in %v", section) 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] { if section != parser.Sections[2] {
t.Fatalf("getBelongingSectionByLine: Expected line 4 to be in section Interface, but it is in %v", section) 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] { if section != parser.Sections[2] {
t.Fatalf("getBelongingSectionByLine: Expected line 6 to be in section Interface, but it is in %v", section) 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] { if section != parser.Sections[3] {
t.Fatalf("getBelongingSectionByLine: Expected line 10 to be in section Peer, but it is in %v", section) 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 // Comment
if section != nil { if section != nil {

View File

@ -1,138 +1,43 @@
package wireguard package parser
import ( import (
"config-lsp/common" "config-lsp/common"
"regexp" "regexp"
"slices" "slices"
"strings" "strings"
protocol "github.com/tliron/glsp/protocol_3_16"
) )
var commentPattern = regexp.MustCompile(`^\s*(;|#)`) var commentPattern = regexp.MustCompile(`^\s*(;|#)`)
var emptyLinePattern = regexp.MustCompile(`^\s*$`) var emptyLinePattern = regexp.MustCompile(`^\s*$`)
var headerPattern = regexp.MustCompile(`^\s*\[`) var headerPattern = regexp.MustCompile(`^\s*\[`)
type characterLocation struct { type CharacterLocation struct {
Start uint32 Start uint32
End uint32 End uint32
} }
type wireguardLineIndex struct { type wireguardLineIndex struct {
Type lineType Type LineType
BelongingSection *wireguardSection BelongingSection *WireguardSection
} }
type wireguardParser struct { type WireguardParser struct {
// <key = name>: if nil then does not belong to a section // <key = name>: if nil then does not belong to a section
Sections []*wireguardSection Sections []*WireguardSection
// Used to identify where not to show diagnostics // Used to identify where not to show diagnostics
CommentLines map[uint32]struct{} commentLines map[uint32]struct{}
// Indexes // Indexes
LineIndexes map[uint32]wireguardLineIndex linesIndexes map[uint32]wireguardLineIndex
} }
func (p *wireguardParser) getSectionByLine(line uint32) *wireguardSection { func (p *WireguardParser) Clear() {
for _, section := range p.Sections { p.Sections = []*WireguardSection{}
if section.StartLine <= line && section.EndLine >= line { p.commentLines = map[uint32]struct{}{}
return section p.linesIndexes = map[uint32]wireguardLineIndex{}
}
} }
return nil func (p *WireguardParser) ParseFromString(input string) []common.ParseError {
}
// 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 {
var errors []common.ParseError var errors []common.ParseError
lines := strings.Split( lines := strings.Split(
input, input,
@ -141,7 +46,7 @@ func (p *wireguardParser) parseFromString(input string) []common.ParseError {
slices.Reverse(lines) slices.Reverse(lines)
collectedProperties := wireguardProperties{} collectedProperties := WireguardProperties{}
var lastPropertyLine *uint32 var lastPropertyLine *uint32
for index, line := range lines { for index, line := range lines {
@ -150,8 +55,8 @@ func (p *wireguardParser) parseFromString(input string) []common.ParseError {
switch lineType { switch lineType {
case LineTypeComment: case LineTypeComment:
p.CommentLines[currentLineNumber] = struct{}{} p.commentLines[currentLineNumber] = struct{}{}
p.LineIndexes[currentLineNumber] = wireguardLineIndex{ p.linesIndexes[currentLineNumber] = wireguardLineIndex{
Type: LineTypeComment, Type: LineTypeComment,
BelongingSection: nil, BelongingSection: nil,
} }
@ -184,7 +89,7 @@ func (p *wireguardParser) parseFromString(input string) []common.ParseError {
lastLine = *lastPropertyLine lastLine = *lastPropertyLine
} }
section := createWireguardSection( section := CreateWireguardSection(
currentLineNumber, currentLineNumber,
lastLine, lastLine,
line, line,
@ -195,23 +100,23 @@ func (p *wireguardParser) parseFromString(input string) []common.ParseError {
// Add indexes // Add indexes
for lineNumber := range collectedProperties { for lineNumber := range collectedProperties {
p.LineIndexes[lineNumber] = wireguardLineIndex{ p.linesIndexes[lineNumber] = wireguardLineIndex{
Type: LineTypeProperty, Type: LineTypeProperty,
BelongingSection: &section, BelongingSection: &section,
} }
} }
p.LineIndexes[currentLineNumber] = wireguardLineIndex{ p.linesIndexes[currentLineNumber] = wireguardLineIndex{
Type: LineTypeHeader, Type: LineTypeHeader,
BelongingSection: &section, BelongingSection: &section,
} }
// Reset // Reset
collectedProperties = wireguardProperties{} collectedProperties = WireguardProperties{}
lastPropertyLine = nil lastPropertyLine = nil
} }
} }
var emptySection *wireguardSection var emptySection *WireguardSection
if len(collectedProperties) > 0 { if len(collectedProperties) > 0 {
var endLine uint32 var endLine uint32
@ -222,7 +127,7 @@ func (p *wireguardParser) parseFromString(input string) []common.ParseError {
endLine = p.Sections[len(p.Sections)-1].StartLine endLine = p.Sections[len(p.Sections)-1].StartLine
} }
emptySection = &wireguardSection{ emptySection = &WireguardSection{
StartLine: 0, StartLine: 0,
EndLine: endLine, EndLine: endLine,
Properties: collectedProperties, Properties: collectedProperties,
@ -231,7 +136,7 @@ func (p *wireguardParser) parseFromString(input string) []common.ParseError {
p.Sections = append(p.Sections, emptySection) p.Sections = append(p.Sections, emptySection)
for lineNumber := range collectedProperties { for lineNumber := range collectedProperties {
p.LineIndexes[lineNumber] = wireguardLineIndex{ p.linesIndexes[lineNumber] = wireguardLineIndex{
Type: LineTypeProperty, Type: LineTypeProperty,
BelongingSection: emptySection, BelongingSection: emptySection,
} }
@ -247,7 +152,7 @@ func (p *wireguardParser) parseFromString(input string) []common.ParseError {
// Add empty section // Add empty section
if endLine != 0 { if endLine != 0 {
emptySection = &wireguardSection{ emptySection = &WireguardSection{
StartLine: 0, StartLine: 0,
EndLine: endLine, EndLine: endLine,
Properties: collectedProperties, Properties: collectedProperties,
@ -256,11 +161,11 @@ func (p *wireguardParser) parseFromString(input string) []common.ParseError {
p.Sections = append(p.Sections, emptySection) p.Sections = append(p.Sections, emptySection)
for newLine := uint32(0); newLine < endLine; newLine++ { for newLine := uint32(0); newLine < endLine; newLine++ {
if _, found := p.LineIndexes[newLine]; found { if _, found := p.linesIndexes[newLine]; found {
continue continue
} }
p.LineIndexes[newLine] = wireguardLineIndex{ p.linesIndexes[newLine] = wireguardLineIndex{
Type: LineTypeEmpty, Type: LineTypeEmpty,
BelongingSection: emptySection, BelongingSection: emptySection,
} }
@ -284,11 +189,11 @@ func (p *wireguardParser) parseFromString(input string) []common.ParseError {
} }
for newLine := section.StartLine; newLine < endLine; newLine++ { for newLine := section.StartLine; newLine < endLine; newLine++ {
if _, found := p.LineIndexes[newLine]; found { if _, found := p.linesIndexes[newLine]; found {
continue continue
} }
p.LineIndexes[newLine] = wireguardLineIndex{ p.linesIndexes[newLine] = wireguardLineIndex{
Type: LineTypeEmpty, Type: LineTypeEmpty,
BelongingSection: section, BelongingSection: section,
} }
@ -298,13 +203,47 @@ func (p *wireguardParser) parseFromString(input string) []common.ParseError {
return errors 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 // Check if line is a comment
if _, found := p.CommentLines[line]; found { if _, found := p.commentLines[line]; found {
return LineTypeComment return LineTypeComment
} }
if info, found := p.LineIndexes[line]; found { if info, found := p.linesIndexes[line]; found {
return info.Type return info.Type
} }
@ -320,22 +259,22 @@ func (p wireguardParser) getTypeByLine(line uint32) lineType {
// [Peer] // [Peer]
// //
// This would return the section [Interface] // This would return the section [Interface]
func (p *wireguardParser) getBelongingSectionByLine(line uint32) *wireguardSection { func (p *WireguardParser) GetBelongingSectionByLine(line uint32) *WireguardSection {
if info, found := p.LineIndexes[line]; found { if info, found := p.linesIndexes[line]; found {
return info.BelongingSection return info.BelongingSection
} }
return nil return nil
} }
func (p *wireguardParser) getPropertyByLine(line uint32) (*wireguardSection, *wireguardProperty) { func (p *WireguardParser) GetPropertyByLine(line uint32) (*WireguardSection, *WireguardProperty) {
section := p.getSectionByLine(line) section := p.GetSectionByLine(line)
if section == nil || section.Name == nil { if section == nil || section.Name == nil {
return nil, nil return nil, nil
} }
property, _ := section.findProperty(line) property, _ := section.GetPropertyByLine(line)
if property == nil { if property == nil {
return nil, nil return nil, nil
@ -344,8 +283,8 @@ func (p *wireguardParser) getPropertyByLine(line uint32) (*wireguardSection, *wi
return section, property return section, property
} }
func (p *wireguardParser) getSectionsByName(name string) []*wireguardSection { func (p *WireguardParser) GetSectionsByName(name string) []*WireguardSection {
var sections []*wireguardSection var sections []*WireguardSection
for _, section := range p.Sections { for _, section := range p.Sections {
if section.Name != nil && *section.Name == name { if section.Name != nil && *section.Name == name {
@ -355,3 +294,35 @@ func (p *wireguardParser) getSectionsByName(name string) []*wireguardSection {
return sections 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
}

View File

@ -1,26 +1,16 @@
package wireguard package parser
import ( import (
"strings" "config-lsp/utils"
"testing" "testing"
"github.com/k0kubun/pp" "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( func TestValidWildTestWorksFine(
t *testing.T, t *testing.T,
) { ) {
sample := dedent(` sample := utils.Dedent(`
[Interface] [Interface]
PrivateKey = 1234567890 PrivateKey = 1234567890
Address = 192.168.1.0/24 Address = 192.168.1.0/24
@ -34,15 +24,15 @@ Endpoint = 1.2.3.4 ; I'm just a comment
PublicKey = 5555 PublicKey = 5555
`) `)
parser := createWireguardParser() parser := CreateWireguardParser()
errors := parser.parseFromString(sample) errors := parser.ParseFromString(sample)
if len(errors) > 0 { if len(errors) > 0 {
t.Fatalf("parseFromString failed with error %v", errors) t.Fatalf("parseFromString failed with error %v", errors)
} }
if !(len(parser.CommentLines) == 1 && keyExists(parser.CommentLines, 4)) { if !(len(parser.commentLines) == 1 && utils.KeyExists(parser.commentLines, 4)) {
t.Fatalf("parseFromString failed to collect comment lines %v", parser.CommentLines) 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")) { 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 // Check if line indexes are correct
if !(parser.LineIndexes[0].Type == LineTypeHeader && if !(parser.linesIndexes[0].Type == LineTypeHeader &&
parser.LineIndexes[1].Type == LineTypeProperty && parser.linesIndexes[1].Type == LineTypeProperty &&
parser.LineIndexes[2].Type == LineTypeProperty && parser.linesIndexes[2].Type == LineTypeProperty &&
parser.LineIndexes[3].Type == LineTypeEmpty && parser.linesIndexes[3].Type == LineTypeEmpty &&
parser.LineIndexes[4].Type == LineTypeComment && parser.linesIndexes[4].Type == LineTypeComment &&
parser.LineIndexes[5].Type == LineTypeHeader && parser.linesIndexes[5].Type == LineTypeHeader &&
parser.LineIndexes[6].Type == LineTypeProperty && parser.linesIndexes[6].Type == LineTypeProperty &&
parser.LineIndexes[7].Type == LineTypeProperty && parser.linesIndexes[7].Type == LineTypeProperty &&
parser.LineIndexes[8].Type == LineTypeEmpty && parser.linesIndexes[8].Type == LineTypeEmpty &&
parser.LineIndexes[9].Type == LineTypeHeader && parser.linesIndexes[9].Type == LineTypeHeader &&
parser.LineIndexes[10].Type == LineTypeProperty) { parser.linesIndexes[10].Type == LineTypeProperty) {
pp.Println(parser.LineIndexes) pp.Println(parser.linesIndexes)
t.Fatal("parseFromString: Invalid line indexes") t.Fatal("parseFromString: Invalid line indexes")
} }
} }
@ -89,15 +79,15 @@ PublicKey = 5555
func TestEmptySectionAtStartWorksFine( func TestEmptySectionAtStartWorksFine(
t *testing.T, t *testing.T,
) { ) {
sample := dedent(` sample := utils.Dedent(`
[Interface] [Interface]
[Peer] [Peer]
PublicKey = 1234567890 PublicKey = 1234567890
`) `)
parser := createWireguardParser() parser := CreateWireguardParser()
errors := parser.parseFromString(sample) errors := parser.ParseFromString(sample)
if len(errors) > 0 { if len(errors) > 0 {
t.Fatalf("parseFromString failed with error %v", errors) t.Fatalf("parseFromString failed with error %v", errors)
@ -115,7 +105,7 @@ PublicKey = 1234567890
func TestEmptySectionAtEndWorksFine( func TestEmptySectionAtEndWorksFine(
t *testing.T, t *testing.T,
) { ) {
sample := dedent(` sample := utils.Dedent(`
[Inteface] [Inteface]
PrivateKey = 1234567890 PrivateKey = 1234567890
@ -123,8 +113,8 @@ PrivateKey = 1234567890
# Just sneaking in here, hehe # Just sneaking in here, hehe
`) `)
parser := createWireguardParser() parser := CreateWireguardParser()
errors := parser.parseFromString(sample) errors := parser.ParseFromString(sample)
if len(errors) > 0 { if len(errors) > 0 {
t.Fatalf("parseFromString failed with error %v", errors) t.Fatalf("parseFromString failed with error %v", errors)
@ -138,19 +128,19 @@ PrivateKey = 1234567890
t.Fatalf("parseFromString failed to collect properties %v", parser.Sections) t.Fatalf("parseFromString failed to collect properties %v", parser.Sections)
} }
if !(len(parser.CommentLines) == 1 && keyExists(parser.CommentLines, 4)) { if !(len(parser.commentLines) == 1 && utils.KeyExists(parser.commentLines, 4)) {
t.Fatalf("parseFromString failed to collect comment lines %v", parser.CommentLines) t.Fatalf("parseFromString failed to collect comment lines %v", parser.commentLines)
} }
} }
func TestEmptyFileWorksFine( func TestEmptyFileWorksFine(
t *testing.T, t *testing.T,
) { ) {
sample := dedent(` sample := utils.Dedent(`
`) `)
parser := createWireguardParser() parser := CreateWireguardParser()
errors := parser.parseFromString(sample) errors := parser.ParseFromString(sample)
if len(errors) > 0 { if len(errors) > 0 {
t.Fatalf("parseFromString failed with error %v", errors) t.Fatalf("parseFromString failed with error %v", errors)
@ -164,15 +154,15 @@ func TestEmptyFileWorksFine(
func TestPartialSectionWithNoPropertiesWorksFine( func TestPartialSectionWithNoPropertiesWorksFine(
t *testing.T, t *testing.T,
) { ) {
sample := dedent(` sample := utils.Dedent(`
[Inte [Inte
[Peer] [Peer]
PublicKey = 1234567890 PublicKey = 1234567890
`) `)
parser := createWireguardParser() parser := CreateWireguardParser()
errors := parser.parseFromString(sample) errors := parser.ParseFromString(sample)
if len(errors) > 0 { if len(errors) > 0 {
t.Fatalf("parseFromString failed with error %v", errors) t.Fatalf("parseFromString failed with error %v", errors)
@ -186,8 +176,8 @@ PublicKey = 1234567890
t.Fatalf("parseFromString failed to collect properties: %v", parser.Sections) t.Fatalf("parseFromString failed to collect properties: %v", parser.Sections)
} }
if !(len(parser.CommentLines) == 0) { if !(len(parser.commentLines) == 0) {
t.Fatalf("parseFromString failed to collect comment lines: %v", parser.CommentLines) t.Fatalf("parseFromString failed to collect comment lines: %v", parser.commentLines)
} }
if !(parser.Sections[1].Properties[3].Key.Name == "PublicKey") { if !(parser.Sections[1].Properties[3].Key.Name == "PublicKey") {
@ -198,15 +188,15 @@ PublicKey = 1234567890
func TestPartialSectionWithPropertiesWorksFine( func TestPartialSectionWithPropertiesWorksFine(
t *testing.T, t *testing.T,
) { ) {
sample := dedent(` sample := utils.Dedent(`
[Inte [Inte
PrivateKey = 1234567890 PrivateKey = 1234567890
[Peer] [Peer]
`) `)
parser := createWireguardParser() parser := CreateWireguardParser()
errors := parser.parseFromString(sample) errors := parser.ParseFromString(sample)
if len(errors) > 0 { if len(errors) > 0 {
t.Fatalf("parseFromString failed with error: %v", errors) t.Fatalf("parseFromString failed with error: %v", errors)
@ -228,12 +218,12 @@ PrivateKey = 1234567890
func TestFileWithOnlyComments( func TestFileWithOnlyComments(
t *testing.T, t *testing.T,
) { ) {
sample := dedent(` sample := utils.Dedent(`
# This is a comment # This is a comment
# Another comment # Another comment
`) `)
parser := createWireguardParser() parser := CreateWireguardParser()
errors := parser.parseFromString(sample) errors := parser.ParseFromString(sample)
if len(errors) > 0 { if len(errors) > 0 {
t.Fatalf("parseFromString failed with error: %v", errors) t.Fatalf("parseFromString failed with error: %v", errors)
@ -243,26 +233,26 @@ func TestFileWithOnlyComments(
t.Fatalf("parseFromString failed to collect sections: %v", parser.Sections) t.Fatalf("parseFromString failed to collect sections: %v", parser.Sections)
} }
if !(len(parser.CommentLines) == 2) { if !(len(parser.commentLines) == 2) {
t.Fatalf("parseFromString failed to collect comment lines: %v", parser.CommentLines) t.Fatalf("parseFromString failed to collect comment lines: %v", parser.commentLines)
} }
if !(keyExists(parser.CommentLines, 0) && keyExists(parser.CommentLines, 1)) { if !(utils.KeyExists(parser.commentLines, 0) && utils.KeyExists(parser.commentLines, 1)) {
t.Fatalf("parseFromString failed to collect comment lines: %v", parser.CommentLines) t.Fatalf("parseFromString failed to collect comment lines: %v", parser.commentLines)
} }
} }
func TestMultipleSectionsNoProperties( func TestMultipleSectionsNoProperties(
t *testing.T, t *testing.T,
) { ) {
sample := dedent(` sample := utils.Dedent(`
[Interface] [Interface]
[Peer] [Peer]
[Peer] [Peer]
`) `)
parser := createWireguardParser() parser := CreateWireguardParser()
errors := parser.parseFromString(sample) errors := parser.ParseFromString(sample)
if len(errors) > 0 { if len(errors) > 0 {
t.Fatalf("parseFromString failed with error: %v", errors) t.Fatalf("parseFromString failed with error: %v", errors)
@ -282,15 +272,14 @@ func TestMultipleSectionsNoProperties(
func TestWildTest1WorksCorrectly( func TestWildTest1WorksCorrectly(
t *testing.T, t *testing.T,
) { ) {
sample := dedent(` sample := utils.Dedent(`
[Interface] [Interface]
DNS=1.1.1.1 DNS=1.1.1.1
`) `)
parser := CreateWireguardParser()
parser := createWireguardParser() errors := parser.ParseFromString(sample)
errors := parser.parseFromString(sample)
if len(errors) > 0 { if len(errors) > 0 {
t.Fatalf("parseFromString failed with error: %v", errors) 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) t.Fatalf("parseFromString failed to collect properties of section 0: %v", parser.Sections[0].Properties)
} }
if !(len(parser.CommentLines) == 0) { if !(len(parser.commentLines) == 0) {
t.Fatalf("parseFromString failed to collect comment lines: %v", parser.CommentLines) t.Fatalf("parseFromString failed to collect comment lines: %v", parser.commentLines)
} }
if !(parser.Sections[0].StartLine == 0 && parser.Sections[0].EndLine == 1) { if !(parser.Sections[0].StartLine == 0 && parser.Sections[0].EndLine == 1) {
@ -324,12 +313,12 @@ DNS=1.1.1.1
func TestPartialKeyWorksCorrectly( func TestPartialKeyWorksCorrectly(
t *testing.T, t *testing.T,
) { ) {
sample := dedent(` sample := utils.Dedent(`
[Interface] [Interface]
DNS DNS
`) `)
parser := createWireguardParser() parser := CreateWireguardParser()
errors := parser.parseFromString(sample) errors := parser.ParseFromString(sample)
if len(errors) > 0 { if len(errors) > 0 {
t.Fatalf("parseFromString failed with error: %v", errors) t.Fatalf("parseFromString failed with error: %v", errors)
@ -347,12 +336,12 @@ DNS
func TestPartialValueWithSeparatorWorksCorrectly( func TestPartialValueWithSeparatorWorksCorrectly(
t *testing.T, t *testing.T,
) { ) {
sample := dedent(` sample := utils.Dedent(`
[Interface] [Interface]
DNS= DNS=
`) `)
parser := createWireguardParser() parser := CreateWireguardParser()
errors := parser.parseFromString(sample) errors := parser.ParseFromString(sample)
if len(errors) > 0 { if len(errors) > 0 {
t.Fatalf("parseFromString failed with error: %v", errors) t.Fatalf("parseFromString failed with error: %v", errors)

View File

@ -1,4 +1,4 @@
package wireguard package parser
import ( import (
docvalues "config-lsp/doc-values" docvalues "config-lsp/doc-values"
@ -11,27 +11,27 @@ import (
var linePattern = regexp.MustCompile(`^\s*(?P<key>.+?)\s*(?P<separator>=)\s*(?P<value>\S.*?)?\s*(?:(?:;|#).*)?\s*$`) var linePattern = regexp.MustCompile(`^\s*(?P<key>.+?)\s*(?P<separator>=)\s*(?P<value>\S.*?)?\s*(?:(?:;|#).*)?\s*$`)
type wireguardPropertyKey struct { type WireguardPropertyKey struct {
Location characterLocation Location CharacterLocation
Name string Name string
} }
type wireguardPropertyValue struct { type WireguardPropertyValue struct {
Location characterLocation Location CharacterLocation
Value string Value string
} }
type wireguardPropertySeparator struct { type WireguardPropertySeparator struct {
Location characterLocation Location CharacterLocation
} }
type wireguardProperty struct { type WireguardProperty struct {
Key wireguardPropertyKey Key WireguardPropertyKey
Separator *wireguardPropertySeparator Separator *WireguardPropertySeparator
Value *wireguardPropertyValue Value *WireguardPropertyValue
} }
func (p wireguardProperty) String() string { func (p WireguardProperty) String() string {
if p.Value == nil { if p.Value == nil {
return p.Key.Name return p.Key.Name
} }
@ -39,7 +39,7 @@ func (p wireguardProperty) String() string {
return p.Key.Name + "=" + p.Value.Value 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{ return protocol.Range{
Start: protocol.Position{ Start: protocol.Position{
Line: line, 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 [<line number>]: <property>
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, "=") { if !strings.Contains(line, "=") {
indexes := utils.GetTrimIndex(line) indexes := utils.GetTrimIndex(line)
@ -61,10 +98,10 @@ func createWireguardProperty(line string) (*wireguardProperty, error) {
return nil, &docvalues.MalformedLineError{} return nil, &docvalues.MalformedLineError{}
} }
return &wireguardProperty{ return &WireguardProperty{
Key: wireguardPropertyKey{ Key: WireguardPropertyKey{
Name: line[indexes[0]:indexes[1]], Name: line[indexes[0]:indexes[1]],
Location: characterLocation{ Location: CharacterLocation{
Start: uint32(indexes[0]), Start: uint32(indexes[0]),
End: uint32(indexes[1]), End: uint32(indexes[1]),
}, },
@ -80,8 +117,8 @@ func createWireguardProperty(line string) (*wireguardProperty, error) {
keyStart := uint32(indexes[2]) keyStart := uint32(indexes[2])
keyEnd := uint32(indexes[3]) keyEnd := uint32(indexes[3])
key := wireguardPropertyKey{ key := WireguardPropertyKey{
Location: characterLocation{ Location: CharacterLocation{
Start: keyStart, Start: keyStart,
End: keyEnd, End: keyEnd,
}, },
@ -90,22 +127,22 @@ func createWireguardProperty(line string) (*wireguardProperty, error) {
separatorStart := uint32(indexes[4]) separatorStart := uint32(indexes[4])
separatorEnd := uint32(indexes[5]) separatorEnd := uint32(indexes[5])
separator := wireguardPropertySeparator{ separator := WireguardPropertySeparator{
Location: characterLocation{ Location: CharacterLocation{
Start: separatorStart, Start: separatorStart,
End: separatorEnd, End: separatorEnd,
}, },
} }
var value *wireguardPropertyValue var value *WireguardPropertyValue
if indexes[6] != -1 && indexes[7] != -1 { if indexes[6] != -1 && indexes[7] != -1 {
// value exists // value exists
valueStart := uint32(indexes[6]) valueStart := uint32(indexes[6])
valueEnd := uint32(indexes[7]) valueEnd := uint32(indexes[7])
value = &wireguardPropertyValue{ value = &WireguardPropertyValue{
Location: characterLocation{ Location: CharacterLocation{
Start: valueStart, Start: valueStart,
End: valueEnd, End: valueEnd,
}, },
@ -113,24 +150,9 @@ func createWireguardProperty(line string) (*wireguardProperty, error) {
} }
} }
return &wireguardProperty{ return &WireguardProperty{
Key: key, Key: key,
Separator: &separator, Separator: &separator,
Value: value, Value: value,
}, nil }, nil
} }
// [<line number>]: <property>
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
}

View File

@ -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 = "<nil>"
} 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<header>.+?)\]\s*$`)
// Create a new create section
// Return (<name>, <new section>)
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,
}
}

View File

@ -1,7 +0,0 @@
package wireguard
import (
protocol "github.com/tliron/glsp/protocol_3_16"
)
var documentParserMap = map[protocol.DocumentUri]*wireguardParser{}

View File

@ -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")
}

View File

@ -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 = "<nil>"
} 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<header>.+?)\]\s*$`)
// Create a new create section
// Return (<name>, <new section>)
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,
}
}

View File

@ -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
}

View File

@ -1,8 +1,7 @@
package roothandler package roothandler
import ( import (
"config-lsp/handlers/wireguard" wireguard "config-lsp/handlers/wireguard/lsp"
"github.com/tliron/glsp" "github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16" protocol "github.com/tliron/glsp/protocol_3_16"
) )

View File

@ -2,7 +2,7 @@ package roothandler
import ( import (
"config-lsp/handlers/fstab" "config-lsp/handlers/fstab"
"config-lsp/handlers/wireguard" wireguard "config-lsp/handlers/wireguard/lsp"
"github.com/tliron/glsp" "github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16" protocol "github.com/tliron/glsp/protocol_3_16"

View File

@ -2,7 +2,7 @@ package roothandler
import ( import (
"config-lsp/handlers/fstab" "config-lsp/handlers/fstab"
"config-lsp/handlers/wireguard" wireguard "config-lsp/handlers/wireguard/lsp"
"github.com/tliron/glsp" "github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16" protocol "github.com/tliron/glsp/protocol_3_16"

View File

@ -1,7 +1,7 @@
package roothandler package roothandler
import ( import (
"config-lsp/handlers/wireguard" wireguard "config-lsp/handlers/wireguard/lsp"
"github.com/tliron/glsp" "github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16" protocol "github.com/tliron/glsp/protocol_3_16"

View File

@ -3,7 +3,7 @@ package roothandler
import ( import (
"config-lsp/common" "config-lsp/common"
fstab "config-lsp/handlers/fstab" fstab "config-lsp/handlers/fstab"
"config-lsp/handlers/wireguard" wireguard "config-lsp/handlers/wireguard/lsp"
"fmt" "fmt"
"github.com/tliron/glsp" "github.com/tliron/glsp"

View File

@ -2,7 +2,7 @@ package roothandler
import ( import (
"config-lsp/handlers/fstab" "config-lsp/handlers/fstab"
"config-lsp/handlers/wireguard" wireguard "config-lsp/handlers/wireguard/lsp"
"github.com/tliron/glsp" "github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16" protocol "github.com/tliron/glsp/protocol_3_16"

View File

@ -1,7 +1,7 @@
package roothandler package roothandler
import ( import (
"config-lsp/handlers/wireguard" wireguard "config-lsp/handlers/wireguard/lsp"
"strings" "strings"
"github.com/tliron/glsp" "github.com/tliron/glsp"

13
utils/tests.go Normal file
View File

@ -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
}