From 3795dc2af4b5798936208dd5736808329280b745 Mon Sep 17 00:00:00 2001 From: Myzel394 Date: Sun, 3 Aug 2025 19:10:29 +0200 Subject: [PATCH 01/20] feat: Add ls-lint --- .ls-lint.yml | 21 +++++++++++++++++++++ flake.nix | 35 ++++++++++++++++------------------- justfile | 5 ++--- 3 files changed, 39 insertions(+), 22 deletions(-) create mode 100644 .ls-lint.yml diff --git a/.ls-lint.yml b/.ls-lint.yml new file mode 100644 index 0000000..b56bfeb --- /dev/null +++ b/.ls-lint.yml @@ -0,0 +1,21 @@ +ls: + server: + .go: regex:[a-z0-9-]+(_[a-z0-9-]+){0,2} + + server/handlers/*/analyzer: + .go: snake_case + + server/handlers/*/lsp: + .go: kebab-case + + # Generated by antlr + server/handlers/*/ast/parser: + .go: snake_case + +ignore: + - .git + - node_modules + - .next + - dist + - result + - out diff --git a/flake.nix b/flake.nix index 043a83f..e3d54d5 100644 --- a/flake.nix +++ b/flake.nix @@ -34,8 +34,10 @@ gomod2nix.overlays.default ]; }; - inputs = [ - pkgs.go_1_24 + inputs = with pkgs; [ + go_1_24 + ls-lint + just ]; serverUncompressed = (pkgs: pkgs.buildGoModule { CGO_ENABLED = 0; @@ -138,25 +140,21 @@ "vs-code-extension" = vsCodeExtension pkgs; }; - devShells.default = let - ourGopls = pkgs.gopls; - in - pkgs.mkShell { - buildInputs = inputs ++ (with pkgs; [ - mailutils - wireguard-tools - antlr - just - ourGopls - python3 - ]) ++ (if pkgs.stdenv.isLinux then with pkgs; [ - postfix - ] else []); - }; + devShells.default = pkgs.mkShell { + buildInputs = inputs ++ (with pkgs; [ + mailutils + wireguard-tools + antlr + gopls + python3 + ls-lint + ]) ++ (if pkgs.stdenv.isLinux then with pkgs; [ + postfix + ] else []); + }; devShells."vs-code-extension" = pkgs.mkShell { buildInputs = with pkgs; [ - just nodejs vsce yarn @@ -166,7 +164,6 @@ devShells.website = pkgs.mkShell { buildInputs = with pkgs; [ - just nodejs_22 yarn oxlint diff --git a/justfile b/justfile index aad6316..b028872 100644 --- a/justfile +++ b/justfile @@ -8,10 +8,9 @@ default: @just --list # Lint whole project -[working-directory: "./server"] lint: - gofmt -s -w . - # cd vs-code-extension && yarn run lint + ls_lint && \ + cd server && gofmt -s -w . # Build config-lsp and test it in nvim (config-lsp will be loaded automatically) [working-directory: "./server"] From 68b523d5096ef8e9d76e0a12c2683fcf3f75b3d9 Mon Sep 17 00:00:00 2001 From: Myzel394 Date: Sun, 3 Aug 2025 19:10:45 +0200 Subject: [PATCH 02/20] fix(server): Apply ls lint --- .../aliases/handlers/{go_to_definition.go => go-to-definition.go} | 0 .../{go_to_definition_test.go => go-to-definition_test.go} | 0 .../aliases/handlers/{signature_help.go => signature-help.go} | 0 .../fields/{available_rpc_calls.go => available-rpc-calls.go} | 0 server/handlers/ssh_config/ast/{ssh_config.go => ssh-config.go} | 0 .../ssh_config/ast/{ssh_config_fields.go => ssh-config_fields.go} | 0 .../ast/{ssh_cofig_fields_test.go => ssh-config_fields_test.go} | 0 ...ons_add_to_unknown.go => fetch-code-actions_add-to-unknown.go} | 0 .../handlers/sshd_config/ast/{sshd_config.go => sshd-config.go} | 0 .../ast/{sshd_config_ast_utils.go => sshd-config_ast-utils.go} | 0 .../ast/{sshd_config_fields.go => sshd-config_fields.go} | 0 11 files changed, 0 insertions(+), 0 deletions(-) rename server/handlers/aliases/handlers/{go_to_definition.go => go-to-definition.go} (100%) rename server/handlers/aliases/handlers/{go_to_definition_test.go => go-to-definition_test.go} (100%) rename server/handlers/aliases/handlers/{signature_help.go => signature-help.go} (100%) rename server/handlers/bitcoin_conf/fields/{available_rpc_calls.go => available-rpc-calls.go} (100%) rename server/handlers/ssh_config/ast/{ssh_config.go => ssh-config.go} (100%) rename server/handlers/ssh_config/ast/{ssh_config_fields.go => ssh-config_fields.go} (100%) rename server/handlers/ssh_config/ast/{ssh_cofig_fields_test.go => ssh-config_fields_test.go} (100%) rename server/handlers/ssh_config/handlers/{fetch-code-actions_add_to_unknown.go => fetch-code-actions_add-to-unknown.go} (100%) rename server/handlers/sshd_config/ast/{sshd_config.go => sshd-config.go} (100%) rename server/handlers/sshd_config/ast/{sshd_config_ast_utils.go => sshd-config_ast-utils.go} (100%) rename server/handlers/sshd_config/ast/{sshd_config_fields.go => sshd-config_fields.go} (100%) diff --git a/server/handlers/aliases/handlers/go_to_definition.go b/server/handlers/aliases/handlers/go-to-definition.go similarity index 100% rename from server/handlers/aliases/handlers/go_to_definition.go rename to server/handlers/aliases/handlers/go-to-definition.go diff --git a/server/handlers/aliases/handlers/go_to_definition_test.go b/server/handlers/aliases/handlers/go-to-definition_test.go similarity index 100% rename from server/handlers/aliases/handlers/go_to_definition_test.go rename to server/handlers/aliases/handlers/go-to-definition_test.go diff --git a/server/handlers/aliases/handlers/signature_help.go b/server/handlers/aliases/handlers/signature-help.go similarity index 100% rename from server/handlers/aliases/handlers/signature_help.go rename to server/handlers/aliases/handlers/signature-help.go diff --git a/server/handlers/bitcoin_conf/fields/available_rpc_calls.go b/server/handlers/bitcoin_conf/fields/available-rpc-calls.go similarity index 100% rename from server/handlers/bitcoin_conf/fields/available_rpc_calls.go rename to server/handlers/bitcoin_conf/fields/available-rpc-calls.go diff --git a/server/handlers/ssh_config/ast/ssh_config.go b/server/handlers/ssh_config/ast/ssh-config.go similarity index 100% rename from server/handlers/ssh_config/ast/ssh_config.go rename to server/handlers/ssh_config/ast/ssh-config.go diff --git a/server/handlers/ssh_config/ast/ssh_config_fields.go b/server/handlers/ssh_config/ast/ssh-config_fields.go similarity index 100% rename from server/handlers/ssh_config/ast/ssh_config_fields.go rename to server/handlers/ssh_config/ast/ssh-config_fields.go diff --git a/server/handlers/ssh_config/ast/ssh_cofig_fields_test.go b/server/handlers/ssh_config/ast/ssh-config_fields_test.go similarity index 100% rename from server/handlers/ssh_config/ast/ssh_cofig_fields_test.go rename to server/handlers/ssh_config/ast/ssh-config_fields_test.go diff --git a/server/handlers/ssh_config/handlers/fetch-code-actions_add_to_unknown.go b/server/handlers/ssh_config/handlers/fetch-code-actions_add-to-unknown.go similarity index 100% rename from server/handlers/ssh_config/handlers/fetch-code-actions_add_to_unknown.go rename to server/handlers/ssh_config/handlers/fetch-code-actions_add-to-unknown.go diff --git a/server/handlers/sshd_config/ast/sshd_config.go b/server/handlers/sshd_config/ast/sshd-config.go similarity index 100% rename from server/handlers/sshd_config/ast/sshd_config.go rename to server/handlers/sshd_config/ast/sshd-config.go diff --git a/server/handlers/sshd_config/ast/sshd_config_ast_utils.go b/server/handlers/sshd_config/ast/sshd-config_ast-utils.go similarity index 100% rename from server/handlers/sshd_config/ast/sshd_config_ast_utils.go rename to server/handlers/sshd_config/ast/sshd-config_ast-utils.go diff --git a/server/handlers/sshd_config/ast/sshd_config_fields.go b/server/handlers/sshd_config/ast/sshd-config_fields.go similarity index 100% rename from server/handlers/sshd_config/ast/sshd_config_fields.go rename to server/handlers/sshd_config/ast/sshd-config_fields.go From 70b026bd29c0cd9db674a0a283b059b9fede198d Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Fri, 15 Aug 2025 23:30:39 +0200 Subject: [PATCH 03/20] fix(server/parser/ini): Add Raw value; Allow quotes in values --- server/parsers/ini/ast.go | 1 + server/parsers/ini/parser.go | 18 ++++++++++-- server/parsers/ini/parser_test.go | 48 +++++++++++++++++++++++++++++++ server/utils/quotes.go | 12 ++++++++ server/utils/quotes_test.go | 13 +++++++++ 5 files changed, 89 insertions(+), 3 deletions(-) diff --git a/server/parsers/ini/ast.go b/server/parsers/ini/ast.go index d7b4a31..f2168fb 100644 --- a/server/parsers/ini/ast.go +++ b/server/parsers/ini/ast.go @@ -15,6 +15,7 @@ type PropertyKey struct { type PropertyValue struct { common.LocationRange Value string + Raw string } // PropertySeparator represents the separator in an INI property diff --git a/server/parsers/ini/parser.go b/server/parsers/ini/parser.go index 20ab441..d7a8b4e 100644 --- a/server/parsers/ini/parser.go +++ b/server/parsers/ini/parser.go @@ -236,20 +236,32 @@ func (c *Config) Parse(input string) []common.LSPError { // value exists valueStart := uint32(indexes[6]) valueEnd := uint32(indexes[7]) + + rawStart := valueStart + rawEnd := valueEnd + + if (line[valueStart] == '"' && line[valueEnd-1] == '"') || + (line[valueStart] == '\'' && line[valueEnd-1] == '\'') { + // Value is quoted + valueStart++ + valueEnd-- + } + propertyEnd = valueEnd value = &PropertyValue{ LocationRange: common.LocationRange{ Start: common.Location{ Line: lineNumber, - Character: valueStart, + Character: rawStart, }, End: common.Location{ Line: lineNumber, - Character: valueEnd, + Character: rawEnd, }, }, - Value: line[valueStart:valueEnd], + Value: utils.NormalizeEscapedQuotes(line[valueStart:valueEnd]), + Raw: line[rawStart:rawEnd], } } diff --git a/server/parsers/ini/parser_test.go b/server/parsers/ini/parser_test.go index e9f6d5c..6647eb7 100644 --- a/server/parsers/ini/parser_test.go +++ b/server/parsers/ini/parser_test.go @@ -344,3 +344,51 @@ func TestSectionHeaderEmpty(t *testing.T) { t.Fatalf("Parse: Expected one section with no header and no properties, but got %d sections", len(config.Sections)) } } + +func TestPropertyValueInQuotes(t *testing.T) { + sample := `hello = "world"` + + config := NewConfig() + config.XParseConfig = INIParseConfig{ + AllowRootProperties: true, + } + errors := config.Parse(sample) + + if len(errors) > 0 { + t.Fatalf("Parse: Expected no errors, but got %v", errors) + } + + if len(config.Sections) != 1 { + t.Fatalf("Parse: Expected 1 section, but got %d", len(config.Sections)) + } + + rawProperty, _ := config.Sections[0].Properties.Get(uint32(0)) + property := rawProperty.(*Property) + if !(property.Key.Name == "hello" && property.Value.Value == "world" && property.Value.Raw == `"world"`) { + t.Errorf("Parse: Expected property to be 'hello = \"world\"', but got '%s = %s'; RAW: %s", property.Key.Name, property.Value.Value, property.Value.Raw) + } +} + +func TestPropertyValueInQuotesWithEscapedQuotes(t *testing.T) { + sample := `hello = "world \"escaped\""` + + config := NewConfig() + config.XParseConfig = INIParseConfig{ + AllowRootProperties: true, + } + errors := config.Parse(sample) + + if len(errors) > 0 { + t.Fatalf("Parse: Expected no errors, but got %v", errors) + } + + if len(config.Sections) != 1 { + t.Fatalf("Parse: Expected 1 section, but got %d", len(config.Sections)) + } + + rawProperty, _ := config.Sections[0].Properties.Get(uint32(0)) + property := rawProperty.(*Property) + if !(property.Key.Name == "hello" && property.Value.Value == `world "escaped"` && property.Value.Raw == `"world \"escaped\""` && property.Separator != nil) { + t.Errorf("Parse: Expected property to be correct, got %s = %s", property.Key.Name, property.Value.Raw) + } +} diff --git a/server/utils/quotes.go b/server/utils/quotes.go index 2615e78..870bfa9 100644 --- a/server/utils/quotes.go +++ b/server/utils/quotes.go @@ -97,3 +97,15 @@ func GetQuoteRanges(s string) quoteRanges { return quoteRanges } + +func NormalizeEscapedQuotes(value string) string { + // Search for \" and remove the backslash + for i := 0; i < len(value)-1; i++ { + if value[i] == '\\' && ((value[i+1] == '"') || (value[i+1] == '\'')) { + value = value[:i] + value[i+1:] + i-- // Adjust index after removal + } + } + + return value +} diff --git a/server/utils/quotes_test.go b/server/utils/quotes_test.go index 86fe71a..b24018a 100644 --- a/server/utils/quotes_test.go +++ b/server/utils/quotes_test.go @@ -93,3 +93,16 @@ func TestInvertedQuotesFirstThenRemaining( t.Fatalf("Unexpected inverted quote ranges: %v", inverted) } } + +func TestNormalizeValidQuotes( + t *testing.T, +) { + // Test with valid quotes + input := `hello \"world"` + expected := `hello "world"` + result := NormalizeEscapedQuotes(input) + + if result != expected { + t.Errorf("Expected %q, got %q", expected, result) + } +} From 3335887a45a9061aab03490098e45b2a9ca15264 Mon Sep 17 00:00:00 2001 From: Myzel394 Date: Sat, 16 Aug 2025 15:42:57 +0200 Subject: [PATCH 04/20] feat(server): Add wireguard formatting --- .../handlers/wireguard/handlers/formatting.go | 32 ++++ .../wireguard/handlers/formatting_test.go | 164 ++++++++++++++++++ .../lsp/text-document-range-formatting.go | 22 +++ server/parsers/ini/formatting.go | 43 +++++ .../lsp/text-document-range-formatting.go | 3 +- 5 files changed, 263 insertions(+), 1 deletion(-) create mode 100644 server/handlers/wireguard/handlers/formatting.go create mode 100644 server/handlers/wireguard/handlers/formatting_test.go create mode 100644 server/handlers/wireguard/lsp/text-document-range-formatting.go create mode 100644 server/parsers/ini/formatting.go diff --git a/server/handlers/wireguard/handlers/formatting.go b/server/handlers/wireguard/handlers/formatting.go new file mode 100644 index 0000000..9e899ab --- /dev/null +++ b/server/handlers/wireguard/handlers/formatting.go @@ -0,0 +1,32 @@ +package handlers + +import ( + "config-lsp/handlers/wireguard" + "config-lsp/parsers/ini" + + protocol "github.com/tliron/glsp/protocol_3_16" +) + +func FormatDocument( + d *wireguard.WGDocument, + textRange protocol.Range, + options protocol.FormattingOptions, +) ([]protocol.TextEdit, error) { + edits := make([]protocol.TextEdit, 0) + + for _, section := range d.Config.Sections { + it := section.Properties.Iterator() + + for it.Next() { + property := it.Value().(*ini.Property) + + newEdits, err := ini.FormatProperty(property, options) + + if err == nil { + edits = append(edits, newEdits...) + } + } + } + + return edits, nil +} diff --git a/server/handlers/wireguard/handlers/formatting_test.go b/server/handlers/wireguard/handlers/formatting_test.go new file mode 100644 index 0000000..2f2eec4 --- /dev/null +++ b/server/handlers/wireguard/handlers/formatting_test.go @@ -0,0 +1,164 @@ +package handlers + +import ( + "config-lsp/handlers/wireguard" + "config-lsp/handlers/wireguard/ast" + "config-lsp/handlers/wireguard/indexes" + "config-lsp/utils" + "testing" + + protocol "github.com/tliron/glsp/protocol_3_16" +) + +func TestSimpleFormattingExample(t *testing.T) { + input := utils.Dedent(` +[Interface] +DNS = 9.9.9.9, 1.1.1.1 +`) + config := ast.NewWGConfig() + errors := config.Parse(input) + + if len(errors) > 0 { + t.Fatalf("Failed to parse WireGuard config: %v", errors) + } + + i, errors := indexes.CreateIndexes(config) + + if len(errors) > 0 { + t.Fatalf("Failed to create indexes: %v", errors) + } + + d := wireguard.WGDocument{ + Config: config, + Indexes: i, + } + + options := protocol.FormattingOptions{} + options["insertSpaces"] = true + options["tabSize"] = float64(4) + + edits, err := FormatDocument( + &d, + protocol.Range{ + Start: protocol.Position{ + Line: 0, + Character: protocol.UInteger(0), + }, + End: protocol.Position{ + Line: 2, + Character: protocol.UInteger(0), + }, + }, + options, + ) + + if err != nil { + t.Errorf("Failed to format document: %v", err) + } + + if !(len(edits) == 1 && edits[0].NewText == `DNS = "9.9.9.9, 1.1.1.1"` && edits[0].Range.Start.Line == 1 && edits[0].Range.Start.Character == 0 && edits[0].Range.End.Line == 1 && edits[0].Range.End.Character == 22) { + t.Errorf("Unexpected edits: %v", edits) + } +} + +func TestFormattingAlreadySurroundedQuotesExample(t *testing.T) { + input := utils.Dedent(` +[Interface] +DNS = "9.9.9.9, 1.1.1.1" +`) + config := ast.NewWGConfig() + errors := config.Parse(input) + + if len(errors) > 0 { + t.Fatalf("Failed to parse WireGuard config: %v", errors) + } + + i, errors := indexes.CreateIndexes(config) + + if len(errors) > 0 { + t.Fatalf("Failed to create indexes: %v", errors) + } + + d := wireguard.WGDocument{ + Config: config, + Indexes: i, + } + + options := protocol.FormattingOptions{} + options["insertSpaces"] = true + options["tabSize"] = float64(4) + + edits, err := FormatDocument( + &d, + protocol.Range{ + Start: protocol.Position{ + Line: 0, + Character: protocol.UInteger(0), + }, + End: protocol.Position{ + Line: 2, + Character: protocol.UInteger(0), + }, + }, + options, + ) + + if err != nil { + t.Errorf("Failed to format document: %v", err) + } + + if !(len(edits) == 1 && edits[0].NewText == `DNS = "9.9.9.9, 1.1.1.1"` && edits[0].Range.Start.Line == 1 && edits[0].Range.Start.Character == 0 && edits[0].Range.End.Line == 1 && edits[0].Range.End.Character == 23) { + t.Errorf("Unexpected edits: %v", edits) + } +} + +func TestWithinQuotesExample(t *testing.T) { + input := utils.Dedent(` +[Interface] +DNS = 9.9.9.9, "1234:5678::1", 1.1.1.1 +`) + config := ast.NewWGConfig() + errors := config.Parse(input) + + if len(errors) > 0 { + t.Fatalf("Failed to parse WireGuard config: %v", errors) + } + + i, errors := indexes.CreateIndexes(config) + + if len(errors) > 0 { + t.Fatalf("Failed to create indexes: %v", errors) + } + + d := wireguard.WGDocument{ + Config: config, + Indexes: i, + } + + options := protocol.FormattingOptions{} + options["insertSpaces"] = true + options["tabSize"] = float64(4) + + edits, err := FormatDocument( + &d, + protocol.Range{ + Start: protocol.Position{ + Line: 0, + Character: protocol.UInteger(0), + }, + End: protocol.Position{ + Line: 2, + Character: protocol.UInteger(0), + }, + }, + options, + ) + + if err != nil { + t.Errorf("Failed to format document: %v", err) + } + + if !(len(edits) == 1 && edits[0].NewText == `DNS = "9.9.9.9, \"1234:5678::1\", 1.1.1.1"` && edits[0].Range.Start.Line == 1 && edits[0].Range.Start.Character == 0 && edits[0].Range.End.Line == 1 && edits[0].Range.End.Character == 38) { + t.Errorf("Unexpected edits: %v", edits) + } +} diff --git a/server/handlers/wireguard/lsp/text-document-range-formatting.go b/server/handlers/wireguard/lsp/text-document-range-formatting.go new file mode 100644 index 0000000..11e6490 --- /dev/null +++ b/server/handlers/wireguard/lsp/text-document-range-formatting.go @@ -0,0 +1,22 @@ +package lsp + +import ( + wireguard "config-lsp/handlers/wireguard" + "config-lsp/handlers/wireguard/handlers" + + "github.com/tliron/glsp" + protocol "github.com/tliron/glsp/protocol_3_16" +) + +func TextDocumentRangeFormatting( + context *glsp.Context, + params *protocol.DocumentRangeFormattingParams, +) ([]protocol.TextEdit, error) { + d := wireguard.DocumentParserMap[params.TextDocument.URI] + + return handlers.FormatDocument( + d, + params.Range, + params.Options, + ) +} diff --git a/server/parsers/ini/formatting.go b/server/parsers/ini/formatting.go new file mode 100644 index 0000000..9b58f5c --- /dev/null +++ b/server/parsers/ini/formatting.go @@ -0,0 +1,43 @@ +package ini + +import ( + "config-lsp/common/formatting" + "strings" + + protocol "github.com/tliron/glsp/protocol_3_16" +) + +var propertyTemplate = formatting.FormatTemplate( + "/!'%s/!'", +) +func FormatProperty( + property *Property, + options protocol.FormattingOptions, +) ([]protocol.TextEdit, error) { + edits := make([]protocol.TextEdit, 0) + + if property.Key.Name == "" || property.Value == nil { + return edits, nil + } + + keyText := property.Key.Name + valueText := strings.ReplaceAll( + strings.ReplaceAll( + property.Value.Value, + "\"", + "\\\"", + ), + "'", + "\\'", + ) + + newValueText := propertyTemplate.Format(options, valueText) + newText := keyText + " = " + newValueText + + edits = append(edits, protocol.TextEdit{ + Range: property.ToLSPRange(), + NewText: newText, + }) + + return edits, nil +} diff --git a/server/root-handler/lsp/text-document-range-formatting.go b/server/root-handler/lsp/text-document-range-formatting.go index c308e0e..fb5c837 100644 --- a/server/root-handler/lsp/text-document-range-formatting.go +++ b/server/root-handler/lsp/text-document-range-formatting.go @@ -3,6 +3,7 @@ package lsp import ( "config-lsp/common" bitcoinconf "config-lsp/handlers/bitcoin_conf/lsp" + wireguard "config-lsp/handlers/wireguard/lsp" sshconfig "config-lsp/handlers/ssh_config/lsp" sshdconfig "config-lsp/handlers/sshd_config/lsp" "config-lsp/root-handler/shared" @@ -36,7 +37,7 @@ func TextDocumentRangeFormattingFunc( case shared.LanguageFstab: return nil, nil case shared.LanguageWireguard: - return nil, nil + return wireguard.TextDocumentRangeFormatting(context, params) case shared.LanguageAliases: return nil, nil case shared.LanguageBitcoinConf: From 800eb697186eb5a06094240e3ded9839a4562ba4 Mon Sep 17 00:00:00 2001 From: Myzel394 Date: Sat, 16 Aug 2025 16:56:11 +0200 Subject: [PATCH 05/20] feat(server): Improve wireguard pre/post handler to show fix individually --- .../handlers/wireguard/analyzer/properties.go | 14 +++++ .../handlers/wireguard/analyzer/property.go | 42 ++++++------- .../wireguard/handlers/code-actions.go | 2 +- ...tdown.go => code-actions_generate-down.go} | 59 +++++++++---------- ...go => fetch-code-actions_generate-down.go} | 37 +++++++----- server/handlers/wireguard/indexes/indexes.go | 9 ++- .../wireguard/indexes/indexes_handlers.go | 34 +++++++---- .../wireguard/indexes/indexes_test.go | 16 +---- .../lsp/workspace-execute-command.go | 4 +- server/parsers/ini/fields.go | 14 +++++ server/parsers/ini/formatting.go | 3 +- server/root-handler/handler.go | 2 +- .../lsp/text-document-range-formatting.go | 2 +- 13 files changed, 139 insertions(+), 99 deletions(-) rename server/handlers/wireguard/handlers/{code-actions_generate-postdown.go => code-actions_generate-down.go} (51%) rename server/handlers/wireguard/handlers/{fetch-code-actions_generate-postdown.go => fetch-code-actions_generate-down.go} (53%) diff --git a/server/handlers/wireguard/analyzer/properties.go b/server/handlers/wireguard/analyzer/properties.go index c2910b0..99c48f8 100644 --- a/server/handlers/wireguard/analyzer/properties.go +++ b/server/handlers/wireguard/analyzer/properties.go @@ -52,6 +52,20 @@ func analyzeProperties( // Duplicate check } else if existingProperty, found := existingProperties[normalizedPropertyName]; found { + var allowedDuplicated map[fields.NormalizedName]struct{} + + switch section.Header.Name { + case "Interface": + allowedDuplicated = fields.InterfaceAllowedDuplicateFields + case "Peer": + allowedDuplicated = fields.PeerAllowedDuplicateFields + } + + if _, ok := allowedDuplicated[normalizedPropertyName]; ok { + // If the property is allowed to be duplicated, we skip the error + continue + } + ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ Message: fmt.Sprintf("Property '%s' has already been defined on line %d", property.Key.Name, existingProperty.Start.Line+1), Severity: &common.SeverityError, diff --git a/server/handlers/wireguard/analyzer/property.go b/server/handlers/wireguard/analyzer/property.go index 89c33b2..f145d6a 100644 --- a/server/handlers/wireguard/analyzer/property.go +++ b/server/handlers/wireguard/analyzer/property.go @@ -60,37 +60,37 @@ func analyzeKeepAlivePropertyIsSet( func analyzeSymmetricPropertiesSet( ctx *analyzerContext, ) { - for _, section := range ctx.document.Indexes.SectionsByName["Interface"] { - _, preUpProperty := section.FindFirstPropertyByName("PreUp") - _, preDownProperty := section.FindFirstPropertyByName("PreDown") + for section, info := range ctx.document.Indexes.AsymmetricRules { + if info.PreMissing { + properties := section.FindPropertiesByName("PreUp") - _, postUpProperty := section.FindFirstPropertyByName("PostUp") - _, postDownProperty := section.FindFirstPropertyByName("PostDown") + if len(properties) == 0 { + // TODO: Fix later + continue + } + + lastProperty := properties[len(properties)-1] - if preUpProperty != nil && preDownProperty == nil { ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ Message: "PreUp is set, but PreDown is not. It is recommended to set both properties symmetrically", - Range: preUpProperty.ToLSPRange(), - Severity: &common.SeverityHint, - }) - } else if preUpProperty == nil && preDownProperty != nil { - ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ - Message: "PreDown is set, but PreUp is not. It is recommended to set both properties symmetrically", - Range: preDownProperty.ToLSPRange(), + Range: lastProperty.Key.ToLSPRange(), Severity: &common.SeverityHint, }) } - if postUpProperty != nil && postDownProperty == nil { + if info.PostMissing { + properties := section.FindPropertiesByName("PostUp") + + if len(properties) == 0 { + // TODO: Fix later + continue + } + + lastProperty := properties[len(properties)-1] + ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ Message: "PostUp is set, but PostDown is not. It is recommended to set both properties symmetrically", - Range: postUpProperty.ToLSPRange(), - Severity: &common.SeverityHint, - }) - } else if postUpProperty == nil && postDownProperty != nil { - ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ - Message: "PostDown is set, but PostUp is not. It is recommended to set both properties symmetrically", - Range: postDownProperty.ToLSPRange(), + Range: lastProperty.Key.ToLSPRange(), Severity: &common.SeverityHint, }) } diff --git a/server/handlers/wireguard/handlers/code-actions.go b/server/handlers/wireguard/handlers/code-actions.go index 3331eb2..5edeef5 100644 --- a/server/handlers/wireguard/handlers/code-actions.go +++ b/server/handlers/wireguard/handlers/code-actions.go @@ -12,7 +12,7 @@ const ( CodeActionGeneratePrivateKey CodeActionName = "generatePrivateKey" CodeActionGeneratePresharedKey CodeActionName = "generatePresharedKey" CodeActionCreatePeer CodeActionName = "createPeer" - CodeActionGeneratePostDown CodeActionName = "generatePostDown" + CodeActionGenerateDownRule CodeActionName = "generatePostDown" ) type CodeAction interface { diff --git a/server/handlers/wireguard/handlers/code-actions_generate-postdown.go b/server/handlers/wireguard/handlers/code-actions_generate-down.go similarity index 51% rename from server/handlers/wireguard/handlers/code-actions_generate-postdown.go rename to server/handlers/wireguard/handlers/code-actions_generate-down.go index 01ba17b..afc8424 100644 --- a/server/handlers/wireguard/handlers/code-actions_generate-postdown.go +++ b/server/handlers/wireguard/handlers/code-actions_generate-down.go @@ -4,42 +4,56 @@ import ( "config-lsp/handlers/wireguard" "config-lsp/utils" "errors" + "fmt" "strings" protocol "github.com/tliron/glsp/protocol_3_16" ) -type CodeActionGeneratePostdownKeyArgs struct { - URI protocol.DocumentUri +type CodeActionGenerateDownRuleArgs struct { + URI protocol.DocumentUri + Line uint32 } -func CodeActionGeneratePostdownArgsFromArguments(arguments map[string]any) CodeActionGeneratePostdownKeyArgs { - return CodeActionGeneratePostdownKeyArgs{ - URI: arguments["URI"].(protocol.DocumentUri), +func CodeActionGenerateDownRuleArgsFromArguments(arguments map[string]any) CodeActionGenerateDownRuleArgs { + return CodeActionGenerateDownRuleArgs{ + URI: arguments["URI"].(protocol.DocumentUri), + Line: uint32(arguments["Line"].(float64)), } } -func (args CodeActionGeneratePostdownKeyArgs) RunCommand(d *wireguard.WGDocument) (*protocol.ApplyWorkspaceEditParams, error) { +func (args CodeActionGenerateDownRuleArgs) RunCommand(d *wireguard.WGDocument) (*protocol.ApplyWorkspaceEditParams, error) { if utils.BlockUntilIndexesNotNil(d.Indexes) == false { return nil, errors.New("Indexes are not ready yet") } - lastUpPropertyKey := utils.FindBiggestKey(d.Indexes.UpProperties) - lastUpProperty := d.Indexes.UpProperties[lastUpPropertyKey] + section := d.Config.FindSectionByLine(args.Line) + property := d.Config.FindPropertyByLine(args.Line) - // TODO: Find out if the user specified multiple PreDown or only one (or maybe don't do this at all) + if section == nil || property == nil { + return nil, errors.New("No section or property found at the specified line") + } - rules := findAllIPTableRules(d) + rules := strings.Split(property.Value.Value, ";") invertedRules := generateInvertedRules(rules) if len(invertedRules) == 0 { return nil, errors.New("No valid iptables rules found to invert") } + var newKeyName string + + switch property.Key.Name { + case "PreUp": + newKeyName = "PreDown" + case "PostUp": + newKeyName = "PostDown" + } + newRulesString := strings.Join(invertedRules, "; ") - newPropertyString := "\nPostDown = " + newRulesString + "\n" + newPropertyString := fmt.Sprintf("\n%s = %s", newKeyName, newRulesString) - label := "Generate PostDown with inverted rules" + label := fmt.Sprintf("Generate %s with inverted rules", newKeyName) return &protocol.ApplyWorkspaceEditParams{ Label: &label, Edit: protocol.WorkspaceEdit{ @@ -47,8 +61,8 @@ func (args CodeActionGeneratePostdownKeyArgs) RunCommand(d *wireguard.WGDocument args.URI: { { Range: protocol.Range{ - Start: lastUpProperty.Property.Value.ToLSPRange().End, - End: lastUpProperty.Property.Value.ToLSPRange().End, + Start: property.Value.ToLSPRange().End, + End: property.Value.ToLSPRange().End, }, NewText: newPropertyString, }, @@ -58,23 +72,6 @@ func (args CodeActionGeneratePostdownKeyArgs) RunCommand(d *wireguard.WGDocument }, nil } -func findAllIPTableRules( - d *wireguard.WGDocument, -) []string { - upRules := make([]string, 0) - - for _, info := range d.Indexes.UpProperties { - if info.Property != nil && info.Property.Value != nil { - rulesRaw := info.Property.Value.Value - rules := strings.Split(rulesRaw, ";") - - upRules = append(upRules, rules...) - } - } - - return upRules -} - func generateInvertedRules(rules []string) []string { var postDownRules []string diff --git a/server/handlers/wireguard/handlers/fetch-code-actions_generate-postdown.go b/server/handlers/wireguard/handlers/fetch-code-actions_generate-down.go similarity index 53% rename from server/handlers/wireguard/handlers/fetch-code-actions_generate-postdown.go rename to server/handlers/wireguard/handlers/fetch-code-actions_generate-down.go index eda5edb..2a78a9d 100644 --- a/server/handlers/wireguard/handlers/fetch-code-actions_generate-postdown.go +++ b/server/handlers/wireguard/handlers/fetch-code-actions_generate-down.go @@ -9,17 +9,19 @@ import ( protocol "github.com/tliron/glsp/protocol_3_16" ) +var preUp = fields.CreateNormalizedName("PreUp") +var postUp = fields.CreateNormalizedName("PostUp") + func GetGeneratePostDownCodeActions( d *wireguard.WGDocument, params *protocol.CodeActionParams, ) []protocol.CodeAction { - postPrePropertyNames := map[fields.NormalizedName]struct{}{ - fields.CreateNormalizedName("PreUp"): {}, - fields.CreateNormalizedName("PostUp"): {}, - } - line := params.Range.Start.Line + if utils.BlockUntilIndexesNotNil(d) == false { + return nil + } + section := d.Config.FindSectionByLine(line) if section == nil { @@ -35,26 +37,33 @@ func GetGeneratePostDownCodeActions( property := rawProperty.(*ini.Property) propertyName := fields.CreateNormalizedName(property.Key.Name) - if (utils.KeyExists(postPrePropertyNames, propertyName)) && (property.Value != nil) { - // Only propose this action if no PostDown is already present - _, postDownProperty := section.FindFirstPropertyByName("PostDown") + if property.Value != nil { + var newProperty string = "" + + if propertyName == preUp && d.Indexes.AsymmetricRules[section].PreMissing { + newProperty = "PreDown" + } else if propertyName == postUp && d.Indexes.AsymmetricRules[section].PostMissing { + newProperty = "PostDown" + } - if postDownProperty == nil { - commandID := "wireguard." + CodeActionGeneratePostDown + if newProperty != "" { + title := "Generate " + newProperty + " with inverted rules" + commandID := "wireguard." + CodeActionGenerateDownRule command := protocol.Command{ - Title: "Generate PostDown with inverted rules", + Title: title, Command: string(commandID), Arguments: []any{ - CodeActionGeneratePostdownKeyArgs{ - URI: params.TextDocument.URI, + CodeActionGenerateDownRuleArgs{ + URI: params.TextDocument.URI, + Line: line, }, }, } return []protocol.CodeAction{ { - Title: "Generate PostDown with inverted rules", + Title: title, Command: &command, }, } diff --git a/server/handlers/wireguard/indexes/indexes.go b/server/handlers/wireguard/indexes/indexes.go index 4cd7708..0423a25 100644 --- a/server/handlers/wireguard/indexes/indexes.go +++ b/server/handlers/wireguard/indexes/indexes.go @@ -9,6 +9,11 @@ type WGIndexPropertyInfo struct { Property *ini.Property } +type AsymmetricRules struct { + PreMissing bool + PostMissing bool +} + type WGIndexes struct { // map of: section name -> *ini.Section SectionsByName map[string][]*ini.Section @@ -16,6 +21,6 @@ type WGIndexes struct { // map of: line number -> WGIndexPropertyInfo UnknownProperties map[uint32]WGIndexPropertyInfo - // map of: line number -> WGIndexPropertyInfo (PreUp / PostUp properties) - UpProperties map[uint32]WGIndexPropertyInfo + // Lists which properties are asymmetric (so that means they are missing a pre or post) + AsymmetricRules map[*ini.Section]AsymmetricRules } diff --git a/server/handlers/wireguard/indexes/indexes_handlers.go b/server/handlers/wireguard/indexes/indexes_handlers.go index 58d2fa2..14bff2c 100644 --- a/server/handlers/wireguard/indexes/indexes_handlers.go +++ b/server/handlers/wireguard/indexes/indexes_handlers.go @@ -7,16 +7,16 @@ import ( "config-lsp/parsers/ini" ) -var upPropertyNames = map[fields.NormalizedName]struct{}{ - fields.CreateNormalizedName("PreUp"): {}, - fields.CreateNormalizedName("PostUp"): {}, -} +var preUp = fields.CreateNormalizedName("PreUp") +var postUp = fields.CreateNormalizedName("PostUp") +var preDown = fields.CreateNormalizedName("PreDown") +var postDown = fields.CreateNormalizedName("PostDown") func CreateIndexes(config *ast.WGConfig) (*WGIndexes, []common.LSPError) { indexes := &WGIndexes{ SectionsByName: make(map[string][]*ini.Section), UnknownProperties: make(map[uint32]WGIndexPropertyInfo), - UpProperties: make(map[uint32]WGIndexPropertyInfo), + AsymmetricRules: make(map[*ini.Section]AsymmetricRules), } // Use the WGSections from the config @@ -28,19 +28,31 @@ func CreateIndexes(config *ast.WGConfig) (*WGIndexes, []common.LSPError) { section, ) + preCount := 0 + postCount := 0 + it := section.Properties.Iterator() for it.Next() { - lineNumber := it.Key().(uint32) + // lineNumber := it.Key().(uint32) property := it.Value().(*ini.Property) normalizedName := fields.CreateNormalizedName(property.Key.Name) - if _, found := upPropertyNames[normalizedName]; found { - indexes.UpProperties[lineNumber] = WGIndexPropertyInfo{ - Section: section, - Property: property, - } + switch normalizedName { + case preUp: + preCount++ + case postUp: + postCount++ + case preDown: + preCount-- + case postDown: + postCount-- } } + + indexes.AsymmetricRules[section] = AsymmetricRules{ + PreMissing: preCount != 0, + PostMissing: postCount != 0, + } } return indexes, nil diff --git a/server/handlers/wireguard/indexes/indexes_test.go b/server/handlers/wireguard/indexes/indexes_test.go index 72cd3e2..d2bade3 100644 --- a/server/handlers/wireguard/indexes/indexes_test.go +++ b/server/handlers/wireguard/indexes/indexes_test.go @@ -25,19 +25,7 @@ PreUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT t.Fatalf("Unexpected errors while creating indexes: %v", errs) } - if len(indexes.UpProperties) != 1 { - t.Errorf("Expected 1 UpProperty, got %d", len(indexes.UpProperties)) - } - - if indexes.UpProperties[3].Section.Header.Name != "Interface" { - t.Errorf("Expected UpProperty section name 'Interface', got '%s'", indexes.UpProperties[3].Section.Header.Name) - } - - if indexes.UpProperties[3].Property.Key.Name != "PreUp" { - t.Errorf("Expected UpProperty key name 'PreUp', got '%s'", indexes.UpProperties[3].Property.Key.Name) - } - - if indexes.UpProperties[3].Property.Value.Value != "iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT" { - t.Errorf("Expected UpProperty value; %v", indexes.UpProperties[3].Property.Value.Value) + if !(indexes.AsymmetricRules[config.Sections[0]].PreMissing == true && indexes.AsymmetricRules[config.Sections[0]].PostMissing == false) { + t.Errorf("Expected asymmetric rules for section '%s' to be PreMissing: true, PostMissing: false, got PreMissing: %v, PostMissing: %v", config.Sections[0].Header.Name, indexes.AsymmetricRules[config.Sections[0]].PreMissing, indexes.AsymmetricRules[config.Sections[0]].PostMissing) } } diff --git a/server/handlers/wireguard/lsp/workspace-execute-command.go b/server/handlers/wireguard/lsp/workspace-execute-command.go index bde62e7..20a70f1 100644 --- a/server/handlers/wireguard/lsp/workspace-execute-command.go +++ b/server/handlers/wireguard/lsp/workspace-execute-command.go @@ -31,8 +31,8 @@ func WorkspaceExecuteCommand(context *glsp.Context, params *protocol.ExecuteComm d := wireguard.DocumentParserMap[args.URI] return args.RunCommand(d) - case string(handlers.CodeActionGeneratePostDown): - args := handlers.CodeActionGeneratePostdownArgsFromArguments(params.Arguments[0].(map[string]any)) + case string(handlers.CodeActionGenerateDownRule): + args := handlers.CodeActionGenerateDownRuleArgsFromArguments(params.Arguments[0].(map[string]any)) d := wireguard.DocumentParserMap[args.URI] return args.RunCommand(d) diff --git a/server/parsers/ini/fields.go b/server/parsers/ini/fields.go index 9a77b44..879455e 100644 --- a/server/parsers/ini/fields.go +++ b/server/parsers/ini/fields.go @@ -83,6 +83,20 @@ func (s *Section) FindFirstPropertyByName(name string) (uint32, *Property) { return 0, nil } +func (s *Section) FindPropertiesByName(name string) []*Property { + properties := make([]*Property, 0) + + it := s.Properties.Iterator() + for it.Next() { + property := it.Value().(*Property) + if property.Key.Name == name { + properties = append(properties, property) + } + } + + return properties +} + // Find the last property in a section func (s *Section) GetLastProperty() *Property { if s.Properties.Size() == 0 { diff --git a/server/parsers/ini/formatting.go b/server/parsers/ini/formatting.go index 9b58f5c..4683e9f 100644 --- a/server/parsers/ini/formatting.go +++ b/server/parsers/ini/formatting.go @@ -10,6 +10,7 @@ import ( var propertyTemplate = formatting.FormatTemplate( "/!'%s/!'", ) + func FormatProperty( property *Property, options protocol.FormattingOptions, @@ -22,7 +23,7 @@ func FormatProperty( keyText := property.Key.Name valueText := strings.ReplaceAll( - strings.ReplaceAll( + strings.ReplaceAll( property.Value.Value, "\"", "\\\"", diff --git a/server/root-handler/handler.go b/server/root-handler/handler.go index e0b36c2..4dadedf 100644 --- a/server/root-handler/handler.go +++ b/server/root-handler/handler.go @@ -62,7 +62,7 @@ func initialize(context *glsp.Context, params *protocol.InitializeParams) (any, "wireguard." + string(wireguard_handlers.CodeActionGeneratePrivateKey), "wireguard." + string(wireguard_handlers.CodeActionGeneratePresharedKey), - "wireguard." + string(wireguard_handlers.CodeActionGeneratePostDown), + "wireguard." + string(wireguard_handlers.CodeActionGenerateDownRule), "wireguard." + string(wireguard_handlers.CodeActionCreatePeer), "bitcoinconf." + string(bitcoin_conf_handlers.CodeActionGenerateRPCAuth), diff --git a/server/root-handler/lsp/text-document-range-formatting.go b/server/root-handler/lsp/text-document-range-formatting.go index fb5c837..d8ec134 100644 --- a/server/root-handler/lsp/text-document-range-formatting.go +++ b/server/root-handler/lsp/text-document-range-formatting.go @@ -3,9 +3,9 @@ package lsp import ( "config-lsp/common" bitcoinconf "config-lsp/handlers/bitcoin_conf/lsp" - wireguard "config-lsp/handlers/wireguard/lsp" sshconfig "config-lsp/handlers/ssh_config/lsp" sshdconfig "config-lsp/handlers/sshd_config/lsp" + wireguard "config-lsp/handlers/wireguard/lsp" "config-lsp/root-handler/shared" "config-lsp/root-handler/utils" From f7983d4561b7045bd7822bba37ba159bf80d5c93 Mon Sep 17 00:00:00 2001 From: Myzel394 Date: Sat, 16 Aug 2025 17:22:07 +0200 Subject: [PATCH 06/20] fix(server/wireguard): Improvements --- server/handlers/wireguard/handlers/code-actions.go | 2 +- .../wireguard/handlers/code-actions_generate-down.go | 7 +++++-- .../wireguard/handlers/fetch-code-actions_generate-down.go | 4 ++-- server/handlers/wireguard/lsp/text-document-code-action.go | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/server/handlers/wireguard/handlers/code-actions.go b/server/handlers/wireguard/handlers/code-actions.go index 5edeef5..abd5a43 100644 --- a/server/handlers/wireguard/handlers/code-actions.go +++ b/server/handlers/wireguard/handlers/code-actions.go @@ -12,7 +12,7 @@ const ( CodeActionGeneratePrivateKey CodeActionName = "generatePrivateKey" CodeActionGeneratePresharedKey CodeActionName = "generatePresharedKey" CodeActionCreatePeer CodeActionName = "createPeer" - CodeActionGenerateDownRule CodeActionName = "generatePostDown" + CodeActionGenerateDownRule CodeActionName = "generateDownRule" ) type CodeAction interface { diff --git a/server/handlers/wireguard/handlers/code-actions_generate-down.go b/server/handlers/wireguard/handlers/code-actions_generate-down.go index afc8424..9ebd10f 100644 --- a/server/handlers/wireguard/handlers/code-actions_generate-down.go +++ b/server/handlers/wireguard/handlers/code-actions_generate-down.go @@ -48,6 +48,9 @@ func (args CodeActionGenerateDownRuleArgs) RunCommand(d *wireguard.WGDocument) ( newKeyName = "PreDown" case "PostUp": newKeyName = "PostDown" + default: + return nil, fmt.Errorf("unsupported key %q at line %d; only PreUp/PostUp are supported", property.Key.Name, args.Line) + } newRulesString := strings.Join(invertedRules, "; ") @@ -61,8 +64,8 @@ func (args CodeActionGenerateDownRuleArgs) RunCommand(d *wireguard.WGDocument) ( args.URI: { { Range: protocol.Range{ - Start: property.Value.ToLSPRange().End, - End: property.Value.ToLSPRange().End, + Start: property.ToLSPRange().End, + End: property.ToLSPRange().End, }, NewText: newPropertyString, }, diff --git a/server/handlers/wireguard/handlers/fetch-code-actions_generate-down.go b/server/handlers/wireguard/handlers/fetch-code-actions_generate-down.go index 2a78a9d..7c9c17b 100644 --- a/server/handlers/wireguard/handlers/fetch-code-actions_generate-down.go +++ b/server/handlers/wireguard/handlers/fetch-code-actions_generate-down.go @@ -12,11 +12,11 @@ import ( var preUp = fields.CreateNormalizedName("PreUp") var postUp = fields.CreateNormalizedName("PostUp") -func GetGeneratePostDownCodeActions( +func GetGenerateDownRuleCodeActions( d *wireguard.WGDocument, params *protocol.CodeActionParams, ) []protocol.CodeAction { - line := params.Range.Start.Line + line := uint32(params.Range.Start.Line) if utils.BlockUntilIndexesNotNil(d) == false { return nil diff --git a/server/handlers/wireguard/lsp/text-document-code-action.go b/server/handlers/wireguard/lsp/text-document-code-action.go index 1952f2f..8397d45 100644 --- a/server/handlers/wireguard/lsp/text-document-code-action.go +++ b/server/handlers/wireguard/lsp/text-document-code-action.go @@ -17,7 +17,7 @@ func TextDocumentCodeAction(context *glsp.Context, params *protocol.CodeActionPa actions = append(actions, handlers.GetKeepaliveCodeActions(d, params)...) actions = append(actions, handlers.GetAddPeerLikeThisCodeActions(d, params)...) actions = append(actions, handlers.GetPropertyKeywordTypoFixes(d, params)...) - actions = append(actions, handlers.GetGeneratePostDownCodeActions(d, params)...) + actions = append(actions, handlers.GetGenerateDownRuleCodeActions(d, params)...) if len(actions) > 0 { return actions, nil From a5bcfa5ccd3f8f86997948190b5eed9bb431c27d Mon Sep 17 00:00:00 2001 From: Myzel394 Date: Sat, 16 Aug 2025 17:36:49 +0200 Subject: [PATCH 07/20] fix(server/wireguard): Improvements --- .../wireguard/handlers/code-actions_generate-down.go | 5 ++++- .../wireguard/handlers/fetch-code-actions_generate-down.go | 2 +- .../handlers/wireguard/handlers/fetch-code-actions_typos.go | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/server/handlers/wireguard/handlers/code-actions_generate-down.go b/server/handlers/wireguard/handlers/code-actions_generate-down.go index 9ebd10f..d614b6f 100644 --- a/server/handlers/wireguard/handlers/code-actions_generate-down.go +++ b/server/handlers/wireguard/handlers/code-actions_generate-down.go @@ -34,6 +34,10 @@ func (args CodeActionGenerateDownRuleArgs) RunCommand(d *wireguard.WGDocument) ( return nil, errors.New("No section or property found at the specified line") } + if property.Value == nil || property.Value.Value == "" { + return nil, fmt.Errorf("property %q at line %d has no value to invert", property.Key.Name, args.Line) + } + rules := strings.Split(property.Value.Value, ";") invertedRules := generateInvertedRules(rules) @@ -50,7 +54,6 @@ func (args CodeActionGenerateDownRuleArgs) RunCommand(d *wireguard.WGDocument) ( newKeyName = "PostDown" default: return nil, fmt.Errorf("unsupported key %q at line %d; only PreUp/PostUp are supported", property.Key.Name, args.Line) - } newRulesString := strings.Join(invertedRules, "; ") diff --git a/server/handlers/wireguard/handlers/fetch-code-actions_generate-down.go b/server/handlers/wireguard/handlers/fetch-code-actions_generate-down.go index 7c9c17b..fea4049 100644 --- a/server/handlers/wireguard/handlers/fetch-code-actions_generate-down.go +++ b/server/handlers/wireguard/handlers/fetch-code-actions_generate-down.go @@ -18,7 +18,7 @@ func GetGenerateDownRuleCodeActions( ) []protocol.CodeAction { line := uint32(params.Range.Start.Line) - if utils.BlockUntilIndexesNotNil(d) == false { + if utils.BlockUntilIndexesNotNil(d.Indexes) == false { return nil } diff --git a/server/handlers/wireguard/handlers/fetch-code-actions_typos.go b/server/handlers/wireguard/handlers/fetch-code-actions_typos.go index 6082a12..e41f583 100644 --- a/server/handlers/wireguard/handlers/fetch-code-actions_typos.go +++ b/server/handlers/wireguard/handlers/fetch-code-actions_typos.go @@ -19,7 +19,7 @@ func GetPropertyKeywordTypoFixes( return nil } - if utils.BlockUntilIndexesNotNil(d) == false { + if utils.BlockUntilIndexesNotNil(d.Indexes) == false { return nil } From b6cc5a8fada5ce40b5dafe96ff46297ca9013fa7 Mon Sep 17 00:00:00 2001 From: Myzel394 Date: Tue, 19 Aug 2025 20:34:39 +0200 Subject: [PATCH 08/20] fix(server/wireguard): Show asymmetric property hint on all properties --- .../handlers/wireguard/analyzer/property.go | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/server/handlers/wireguard/analyzer/property.go b/server/handlers/wireguard/analyzer/property.go index f145d6a..bec9bd7 100644 --- a/server/handlers/wireguard/analyzer/property.go +++ b/server/handlers/wireguard/analyzer/property.go @@ -69,13 +69,13 @@ func analyzeSymmetricPropertiesSet( continue } - lastProperty := properties[len(properties)-1] - - ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ - Message: "PreUp is set, but PreDown is not. It is recommended to set both properties symmetrically", - Range: lastProperty.Key.ToLSPRange(), - Severity: &common.SeverityHint, - }) + for _, property := range properties { + ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ + Message: "PreUp is set, but PreDown is not. It is recommended to set both properties symmetrically", + Range: property.Key.ToLSPRange(), + Severity: &common.SeverityHint, + }) + } } if info.PostMissing { @@ -86,13 +86,13 @@ func analyzeSymmetricPropertiesSet( continue } - lastProperty := properties[len(properties)-1] - - ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ - Message: "PostUp is set, but PostDown is not. It is recommended to set both properties symmetrically", - Range: lastProperty.Key.ToLSPRange(), - Severity: &common.SeverityHint, - }) + for _, property := range properties { + ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ + Message: "PostUp is set, but PostDown is not. It is recommended to set both properties symmetrically", + Range: property.Key.ToLSPRange(), + Severity: &common.SeverityHint, + }) + } } } } From 459cc7189a8748e6b44f09a189426db7b91d22ed Mon Sep 17 00:00:00 2001 From: Myzel394 Date: Tue, 19 Aug 2025 20:54:22 +0200 Subject: [PATCH 09/20] fix(server/wireguard):Fix completions --- .../wireguard/handlers/completions_body.go | 73 ++++++++++++------- 1 file changed, 47 insertions(+), 26 deletions(-) diff --git a/server/handlers/wireguard/handlers/completions_body.go b/server/handlers/wireguard/handlers/completions_body.go index b87bc3a..acb0dac 100644 --- a/server/handlers/wireguard/handlers/completions_body.go +++ b/server/handlers/wireguard/handlers/completions_body.go @@ -7,6 +7,7 @@ import ( "config-lsp/handlers/wireguard/fields" "config-lsp/parsers/ini" "config-lsp/utils" + "fmt" "maps" protocol "github.com/tliron/glsp/protocol_3_16" @@ -90,18 +91,11 @@ func getPropertyCompletions( /* Value missing or started: Address = 10.| */ - - currentLine := params.Position.Line position := common.LSPCharacterAsCursorPosition(params.Position.Character) - // Special case, key defined but separator missing - if property != nil && property.Separator == nil && !property.Key.ContainsPosition(position) { - return getKeyCompletions(section, true, currentLine), nil - } - - if property == nil || property.Separator == nil || property.Key.ContainsPosition(position) { + if property == nil || property.Key.ContainsPosition(position) { // First scenario - return getKeyCompletions(section, false, currentLine), nil + return getKeyCompletions(section, property, params), nil } // Check if the cursor it outside the value @@ -116,12 +110,14 @@ func getPropertyCompletions( func getKeyCompletions( section ini.Section, - onlySuggestSeparator bool, - currentLine uint32, + property *ini.Property, + params *protocol.CompletionParams, ) []protocol.CompletionItem { options := make(map[fields.NormalizedName]docvalues.DocumentationValue) allowedDuplicatedFields := make(map[fields.NormalizedName]struct{}) + sectionStartLine := section.Start.Line + switch section.Header.Name { case "Interface": maps.Copy(options, fields.InterfaceOptions) @@ -140,7 +136,7 @@ func getKeyCompletions( continue } - if iniProperty.Key.Start.Line == currentLine { + if iniProperty.Key.Start.Line == sectionStartLine { // The user is currently typing the key, thus we should suggest it continue } @@ -148,28 +144,53 @@ func getKeyCompletions( delete(options, normalizedName) } + var start uint32 + var end uint32 + + if property == nil { + start = 0 + end = 0 + } else { + start = property.Key.Start.Character + + if property.Value != nil { + end = property.Value.Start.Character + } else if property.Separator != nil { + end = property.Separator.End.Character + } else { + end = property.Key.End.Character + } + } + kind := protocol.CompletionItemKindField + print(fmt.Sprintf("Found %d options for section", len(options))) return utils.MapMapToSlice( options, func(rawOptionName fields.NormalizedName, value docvalues.DocumentationValue) protocol.CompletionItem { optionName := fields.AllOptionsFormatted[rawOptionName] - var label string - var insertText string - - if onlySuggestSeparator { - label = optionName + " = " - insertText = "= " - } else { - label = optionName - insertText = optionName + " = " - } + + insertText := optionName + " = " + insertFormat := protocol.InsertTextFormatSnippet return protocol.CompletionItem{ - Kind: &kind, - Documentation: value.Documentation, - Label: label, - InsertText: &insertText, + Label: optionName, + Kind: &kind, + Documentation: value.Documentation, + InsertTextFormat: &insertFormat, + TextEdit: protocol.TextEdit{ + Range: protocol.Range{ + Start: protocol.Position{ + Line: params.Position.Line, + Character: start, + }, + End: protocol.Position{ + Line: params.Position.Line, + Character: end, + }, + }, + NewText: insertText, + }, } }, ) From 28eb5f5c338041cbb9742afba99c7eaa80481d6f Mon Sep 17 00:00:00 2001 From: Myzel394 Date: Tue, 19 Aug 2025 21:02:58 +0200 Subject: [PATCH 10/20] fix(server/bitcoin): Fix key completions --- .../handlers/completions_property.go | 68 ++++++++++++------- .../wireguard/handlers/completions_body.go | 2 - 2 files changed, 43 insertions(+), 27 deletions(-) diff --git a/server/handlers/bitcoin_conf/handlers/completions_property.go b/server/handlers/bitcoin_conf/handlers/completions_property.go index 5ec56f2..2f98bb9 100644 --- a/server/handlers/bitcoin_conf/handlers/completions_property.go +++ b/server/handlers/bitcoin_conf/handlers/completions_property.go @@ -20,14 +20,9 @@ func GetPropertyCompletions( ) ([]protocol.CompletionItem, error) { position := common.LSPCharacterAsCursorPosition(params.Position.Character) - // Special case, key defined but separator missing - if property != nil && property.Separator == nil && !property.Key.ContainsPosition(position) { - return getKeyCompletions(section, true) - } - - // First scenario, user adds a new property - if property == nil || property.Key.Name == "" || property.Key.ContainsPosition(position) { - return getKeyCompletions(section, false) + if property == nil || property.Key.ContainsPosition(position) { + // First scenario + return getKeyCompletions(section, property, params) } // Check if the cursor it outside the value @@ -37,12 +32,13 @@ func GetPropertyCompletions( } // Otherwise, suggest value completions - return getValueCompletions(section, property, position) + return getValueCompletions(property, position) } func getKeyCompletions( currentSection *ini.Section, - onlySuggestSeparator bool, + property *ini.Property, + params *protocol.CompletionParams, ) ([]protocol.CompletionItem, error) { options := make(map[string]docvalues.DocumentationValue) @@ -66,34 +62,56 @@ func getKeyCompletions( delete(options, property.Key.Name) } + var start uint32 + var end uint32 + + if property == nil { + start = 0 + end = 0 + } else { + start = property.Key.Start.Character + + if property.Value != nil { + end = property.Value.Start.Character + } else if property.Separator != nil { + end = property.Separator.End.Character + } else { + end = property.Key.End.Character + } + } + kind := protocol.CompletionItemKindField return utils.MapMapToSlice( options, func(optionName string, value docvalues.DocumentationValue) protocol.CompletionItem { - var label string - var insertText string - - if onlySuggestSeparator { - label = optionName + " = " - insertText = "= " - } else { - label = optionName - insertText = optionName + " = " - } + insertText := optionName + " = " + insertFormat := protocol.InsertTextFormatSnippet return protocol.CompletionItem{ - Kind: &kind, - Documentation: value.Documentation, - Label: label, - InsertText: &insertText, + Label: optionName, + Kind: &kind, + Documentation: value.Documentation, + InsertTextFormat: &insertFormat, + TextEdit: protocol.TextEdit{ + Range: protocol.Range{ + Start: protocol.Position{ + Line: params.Position.Line, + Character: start, + }, + End: protocol.Position{ + Line: params.Position.Line, + Character: end, + }, + }, + NewText: insertText, + }, } }, ), nil } func getValueCompletions( - currentSection *ini.Section, property *ini.Property, cursor common.CursorPosition, ) ([]protocol.CompletionItem, error) { diff --git a/server/handlers/wireguard/handlers/completions_body.go b/server/handlers/wireguard/handlers/completions_body.go index acb0dac..f51f97d 100644 --- a/server/handlers/wireguard/handlers/completions_body.go +++ b/server/handlers/wireguard/handlers/completions_body.go @@ -7,7 +7,6 @@ import ( "config-lsp/handlers/wireguard/fields" "config-lsp/parsers/ini" "config-lsp/utils" - "fmt" "maps" protocol "github.com/tliron/glsp/protocol_3_16" @@ -164,7 +163,6 @@ func getKeyCompletions( kind := protocol.CompletionItemKindField - print(fmt.Sprintf("Found %d options for section", len(options))) return utils.MapMapToSlice( options, func(rawOptionName fields.NormalizedName, value docvalues.DocumentationValue) protocol.CompletionItem { From 10dec9045a81789e47aa75b92f6ef635b2e2305d Mon Sep 17 00:00:00 2001 From: Myzel394 Date: Tue, 19 Aug 2025 21:30:52 +0200 Subject: [PATCH 11/20] fix(server): Allow ini parsers Key to be nil --- .../bitcoin_conf/analyzer/properties.go | 5 ++- .../code-actions_generate-rpc-auth.go | 2 +- .../handlers/completions_property.go | 4 +- .../fetch-code-actions_generate-rpc-auth.go | 2 +- .../bitcoin_conf/handlers/formatting.go | 3 ++ .../handlers/bitcoin_conf/handlers/hover.go | 4 ++ .../handlers/wireguard/analyzer/properties.go | 4 +- .../wireguard/handlers/completions_body.go | 7 +++- .../fetch-code-actions_generate-down.go | 4 +- .../fetch-code-actions_key-generation.go | 2 +- server/handlers/wireguard/handlers/hover.go | 4 ++ server/parsers/ini/ast.go | 2 +- server/parsers/ini/fields.go | 10 +++++ server/parsers/ini/formatting.go | 2 +- server/parsers/ini/parser.go | 40 +++++++++++-------- 15 files changed, 64 insertions(+), 31 deletions(-) diff --git a/server/handlers/bitcoin_conf/analyzer/properties.go b/server/handlers/bitcoin_conf/analyzer/properties.go index 40a772a..dccb64b 100644 --- a/server/handlers/bitcoin_conf/analyzer/properties.go +++ b/server/handlers/bitcoin_conf/analyzer/properties.go @@ -21,12 +21,13 @@ func analyzeProperties(ctx *analyzerContext) { for it.Next() { property := it.Value().(*ini.Property) - if property.Key.Name == "" { + if property.Key == nil { ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ Message: "This property is missing a name", - Range: property.Key.ToLSPRange(), + Range: property.ToLSPRange(), Severity: &common.SeverityError, }) + continue } name := property.Key.Name diff --git a/server/handlers/bitcoin_conf/handlers/code-actions_generate-rpc-auth.go b/server/handlers/bitcoin_conf/handlers/code-actions_generate-rpc-auth.go index 28d7310..7228d7f 100644 --- a/server/handlers/bitcoin_conf/handlers/code-actions_generate-rpc-auth.go +++ b/server/handlers/bitcoin_conf/handlers/code-actions_generate-rpc-auth.go @@ -31,7 +31,7 @@ func (args CodeActionGenerateRPCAuthArgs) RunCommand(d *bitcoinconf.BTCDocument) userProperty := d.Config.FindPropertyByLine(args.UsernameLine) passwordProperty := d.Config.FindPropertyByLine(args.PasswordLine) - if userProperty == nil || passwordProperty == nil || userProperty.Key.Name != "rpcuser" || passwordProperty.Key.Name != "rpcpassword" { + if userProperty == nil || passwordProperty == nil || userProperty.Key == nil || passwordProperty.Key == nil || userProperty.Key.Name != "rpcuser" || passwordProperty.Key.Name != "rpcpassword" { return nil, fmt.Errorf("rpcuser or rpcpassword not found at specified lines") } diff --git a/server/handlers/bitcoin_conf/handlers/completions_property.go b/server/handlers/bitcoin_conf/handlers/completions_property.go index 2f98bb9..11d91c8 100644 --- a/server/handlers/bitcoin_conf/handlers/completions_property.go +++ b/server/handlers/bitcoin_conf/handlers/completions_property.go @@ -20,7 +20,7 @@ func GetPropertyCompletions( ) ([]protocol.CompletionItem, error) { position := common.LSPCharacterAsCursorPosition(params.Position.Character) - if property == nil || property.Key.ContainsPosition(position) { + if property == nil || property.Key.ContainsPosition(position) || property.Separator.IsPositionBeforeStart(position) { // First scenario return getKeyCompletions(section, property, params) } @@ -49,7 +49,7 @@ func getKeyCompletions( for it.Next() { property := it.Value().(*ini.Property) - if property.Key.Name == "" { + if property.Key == nil { continue } diff --git a/server/handlers/bitcoin_conf/handlers/fetch-code-actions_generate-rpc-auth.go b/server/handlers/bitcoin_conf/handlers/fetch-code-actions_generate-rpc-auth.go index 32ce822..74ab73f 100644 --- a/server/handlers/bitcoin_conf/handlers/fetch-code-actions_generate-rpc-auth.go +++ b/server/handlers/bitcoin_conf/handlers/fetch-code-actions_generate-rpc-auth.go @@ -18,7 +18,7 @@ func GetGenerateRPCAuthCodeActions( section := d.Config.FindSectionByLine(line) property := d.Config.FindPropertyByLine(line) - if property != nil && (property.Key.Name == "rpcuser" || property.Key.Name == "rpcpassword") && commands.IsPythonAvailable() { + if property != nil && property.Key != nil && (property.Key.Name == "rpcuser" || property.Key.Name == "rpcpassword") && commands.IsPythonAvailable() { _, userProperty := section.FindFirstPropertyByName("rpcuser") _, passwordProperty := section.FindFirstPropertyByName("rpcpassword") diff --git a/server/handlers/bitcoin_conf/handlers/formatting.go b/server/handlers/bitcoin_conf/handlers/formatting.go index 372a3fb..6047bca 100644 --- a/server/handlers/bitcoin_conf/handlers/formatting.go +++ b/server/handlers/bitcoin_conf/handlers/formatting.go @@ -16,6 +16,9 @@ func FormatDocument( entries := d.Config.GetPropertesInRange(textRange.Start.Line, textRange.End.Line) for _, info := range entries { + if info.Property.Key == nil || info.Property.Value == nil { + continue + } edits = append(edits, formatProperty(info.Property, options)...) } diff --git a/server/handlers/bitcoin_conf/handlers/hover.go b/server/handlers/bitcoin_conf/handlers/hover.go index 738c399..4e22464 100644 --- a/server/handlers/bitcoin_conf/handlers/hover.go +++ b/server/handlers/bitcoin_conf/handlers/hover.go @@ -15,6 +15,10 @@ func GetPropertyHoverInfo( property *ini.Property, index common.IndexPosition, ) (*protocol.Hover, error) { + if property.Key == nil { + return nil, nil + } + option, found := fields.Options[property.Key.Name] if !found { diff --git a/server/handlers/wireguard/analyzer/properties.go b/server/handlers/wireguard/analyzer/properties.go index 99c48f8..068771e 100644 --- a/server/handlers/wireguard/analyzer/properties.go +++ b/server/handlers/wireguard/analyzer/properties.go @@ -25,10 +25,10 @@ func analyzeProperties( for it.Next() { property := it.Value().(*ini.Property) - if property.Key.Name == "" { + if property.Key == nil { ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ Message: "This property is missing a name", - Range: property.Key.ToLSPRange(), + Range: property.ToLSPRange(), Severity: &common.SeverityError, }) } diff --git a/server/handlers/wireguard/handlers/completions_body.go b/server/handlers/wireguard/handlers/completions_body.go index f51f97d..993b4ff 100644 --- a/server/handlers/wireguard/handlers/completions_body.go +++ b/server/handlers/wireguard/handlers/completions_body.go @@ -92,7 +92,7 @@ func getPropertyCompletions( */ position := common.LSPCharacterAsCursorPosition(params.Position.Character) - if property == nil || property.Key.ContainsPosition(position) { + if property == nil || property.Key.ContainsPosition(position) || property.Separator.IsPositionBeforeStart(position) { // First scenario return getKeyCompletions(section, property, params), nil } @@ -130,6 +130,11 @@ func getKeyCompletions( it := section.Properties.Iterator() for it.Next() { iniProperty := it.Value().(*ini.Property) + + if iniProperty.Key == nil { + continue + } + normalizedName := fields.CreateNormalizedName(iniProperty.Key.Name) if _, found := allowedDuplicatedFields[normalizedName]; found { continue diff --git a/server/handlers/wireguard/handlers/fetch-code-actions_generate-down.go b/server/handlers/wireguard/handlers/fetch-code-actions_generate-down.go index fea4049..9fe5a21 100644 --- a/server/handlers/wireguard/handlers/fetch-code-actions_generate-down.go +++ b/server/handlers/wireguard/handlers/fetch-code-actions_generate-down.go @@ -36,9 +36,9 @@ func GetGenerateDownRuleCodeActions( property := rawProperty.(*ini.Property) - propertyName := fields.CreateNormalizedName(property.Key.Name) - if property.Value != nil { + if property.Value != nil && property.Key != nil { var newProperty string = "" + propertyName := fields.CreateNormalizedName(property.Key.Name) if propertyName == preUp && d.Indexes.AsymmetricRules[section].PreMissing { newProperty = "PreDown" diff --git a/server/handlers/wireguard/handlers/fetch-code-actions_key-generation.go b/server/handlers/wireguard/handlers/fetch-code-actions_key-generation.go index 5741ed0..b7964b6 100644 --- a/server/handlers/wireguard/handlers/fetch-code-actions_key-generation.go +++ b/server/handlers/wireguard/handlers/fetch-code-actions_key-generation.go @@ -20,7 +20,7 @@ func GetKeyGenerationCodeActions( section := d.Config.FindSectionByLine(line) property := d.Config.FindPropertyByLine(line) - if section == nil || property == nil || property.Separator == nil { + if section == nil || property == nil || property.Key == nil || property.Separator == nil { return nil } diff --git a/server/handlers/wireguard/handlers/hover.go b/server/handlers/wireguard/handlers/hover.go index 45d1009..ddc4874 100644 --- a/server/handlers/wireguard/handlers/hover.go +++ b/server/handlers/wireguard/handlers/hover.go @@ -18,6 +18,10 @@ func GetPropertyHoverInfo( property ini.Property, index common.IndexPosition, ) (*protocol.Hover, error) { + if property.Key == nil { + return nil, nil + } + availableOptions, found := fields.OptionsHeaderMap[fields.CreateNormalizedName(section.Header.Name)] if !found { diff --git a/server/parsers/ini/ast.go b/server/parsers/ini/ast.go index f2168fb..a51bd6e 100644 --- a/server/parsers/ini/ast.go +++ b/server/parsers/ini/ast.go @@ -28,7 +28,7 @@ type Property struct { common.LocationRange RawValue string - Key PropertyKey + Key *PropertyKey Separator *PropertySeparator Value *PropertyValue } diff --git a/server/parsers/ini/fields.go b/server/parsers/ini/fields.go index 879455e..e911a8c 100644 --- a/server/parsers/ini/fields.go +++ b/server/parsers/ini/fields.go @@ -75,6 +75,11 @@ func (s *Section) FindFirstPropertyByName(name string) (uint32, *Property) { for it.Next() { line := it.Key().(uint32) property := it.Value().(*Property) + + if property.Key == nil { + continue + } + if property.Key.Name == name { return line, property } @@ -89,6 +94,11 @@ func (s *Section) FindPropertiesByName(name string) []*Property { it := s.Properties.Iterator() for it.Next() { property := it.Value().(*Property) + + if property.Key == nil { + continue + } + if property.Key.Name == name { properties = append(properties, property) } diff --git a/server/parsers/ini/formatting.go b/server/parsers/ini/formatting.go index 4683e9f..739a652 100644 --- a/server/parsers/ini/formatting.go +++ b/server/parsers/ini/formatting.go @@ -17,7 +17,7 @@ func FormatProperty( ) ([]protocol.TextEdit, error) { edits := make([]protocol.TextEdit, 0) - if property.Key.Name == "" || property.Value == nil { + if property.Key == nil || property.Value == nil { return edits, nil } diff --git a/server/parsers/ini/parser.go b/server/parsers/ini/parser.go index d7a8b4e..816af63 100644 --- a/server/parsers/ini/parser.go +++ b/server/parsers/ini/parser.go @@ -28,7 +28,7 @@ func (c *Config) Clear() { var commentPattern = regexp.MustCompile(`^\s*([;#])`) var emptyPattern = regexp.MustCompile(`^\s*$`) var headerPattern = regexp.MustCompile(`^\s*\[(\w+)?]?`) -var linePattern = regexp.MustCompile(`^\s*(?P.+?)\s*(?P=)\s*(?P\S.*?)?\s*(?: [;#].*)?\s*$`) +var linePattern = regexp.MustCompile(`^\s*(?P.+?)?\s*(?P=)\s*(?P\S.*?)?\s*(?: [;#].*)?\s*$`) // Parse parses an INI string and returns any errors encountered func (c *Config) Parse(input string) []common.LSPError { @@ -145,7 +145,7 @@ func (c *Config) Parse(input string) []common.LSPError { indexes := utils.GetTrimIndex(line) newProperty := &Property{ - Key: PropertyKey{ + Key: &PropertyKey{ LocationRange: common.LocationRange{ Start: common.Location{ Line: lineNumber, @@ -195,21 +195,27 @@ func (c *Config) Parse(input string) []common.LSPError { continue } - // Construct key - keyStart := uint32(indexes[2]) - keyEnd := uint32(indexes[3]) - key := PropertyKey{ - LocationRange: common.LocationRange{ - Start: common.Location{ - Line: lineNumber, - Character: keyStart, - }, - End: common.Location{ - Line: lineNumber, - Character: keyEnd, + var key *PropertyKey + propertyStart := uint32(0) + + if indexes[2] != -1 && indexes[3] != -1 { + // Construct key + keyStart := uint32(indexes[2]) + keyEnd := uint32(indexes[3]) + propertyStart = keyStart + key = &PropertyKey{ + LocationRange: common.LocationRange{ + Start: common.Location{ + Line: lineNumber, + Character: keyStart, + }, + End: common.Location{ + Line: lineNumber, + Character: keyEnd, + }, }, - }, - Name: line[keyStart:keyEnd], + Name: line[keyStart:keyEnd], + } } // Construct separator @@ -270,7 +276,7 @@ func (c *Config) Parse(input string) []common.LSPError { LocationRange: common.LocationRange{ Start: common.Location{ Line: lineNumber, - Character: keyStart, + Character: propertyStart, }, End: common.Location{ Line: lineNumber, From 98ff5a3245d0fd3a5399ecbfcb6e56440381fb7c Mon Sep 17 00:00:00 2001 From: Myzel394 Date: Tue, 19 Aug 2025 22:09:19 +0200 Subject: [PATCH 12/20] fix(server/wireguard): Improvements --- .../handlers/wireguard/analyzer/property.go | 38 +++++++++---------- .../wireguard/handlers/completions_body.go | 4 +- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/server/handlers/wireguard/analyzer/property.go b/server/handlers/wireguard/analyzer/property.go index bec9bd7..1a0f7fd 100644 --- a/server/handlers/wireguard/analyzer/property.go +++ b/server/handlers/wireguard/analyzer/property.go @@ -64,34 +64,30 @@ func analyzeSymmetricPropertiesSet( if info.PreMissing { properties := section.FindPropertiesByName("PreUp") - if len(properties) == 0 { - // TODO: Fix later - continue - } - - for _, property := range properties { - ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ - Message: "PreUp is set, but PreDown is not. It is recommended to set both properties symmetrically", - Range: property.Key.ToLSPRange(), - Severity: &common.SeverityHint, - }) + // TODO: Fix later + if len(properties) != 0 { + for _, property := range properties { + ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ + Message: "PreUp is set, but PreDown is not. It is recommended to set both properties symmetrically", + Range: property.Key.ToLSPRange(), + Severity: &common.SeverityHint, + }) + } } } if info.PostMissing { properties := section.FindPropertiesByName("PostUp") + // TODO: Fix later if len(properties) == 0 { - // TODO: Fix later - continue - } - - for _, property := range properties { - ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ - Message: "PostUp is set, but PostDown is not. It is recommended to set both properties symmetrically", - Range: property.Key.ToLSPRange(), - Severity: &common.SeverityHint, - }) + for _, property := range properties { + ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ + Message: "PostUp is set, but PostDown is not. It is recommended to set both properties symmetrically", + Range: property.Key.ToLSPRange(), + Severity: &common.SeverityHint, + }) + } } } } diff --git a/server/handlers/wireguard/handlers/completions_body.go b/server/handlers/wireguard/handlers/completions_body.go index 993b4ff..c018207 100644 --- a/server/handlers/wireguard/handlers/completions_body.go +++ b/server/handlers/wireguard/handlers/completions_body.go @@ -115,8 +115,6 @@ func getKeyCompletions( options := make(map[fields.NormalizedName]docvalues.DocumentationValue) allowedDuplicatedFields := make(map[fields.NormalizedName]struct{}) - sectionStartLine := section.Start.Line - switch section.Header.Name { case "Interface": maps.Copy(options, fields.InterfaceOptions) @@ -140,7 +138,7 @@ func getKeyCompletions( continue } - if iniProperty.Key.Start.Line == sectionStartLine { + if property != nil && iniProperty.Key.Start.Line == property.Key.Start.Line { // The user is currently typing the key, thus we should suggest it continue } From 839e46dbcf4381c64d7f006da2fcb6d19958bf74 Mon Sep 17 00:00:00 2001 From: Myzel394 Date: Wed, 20 Aug 2025 18:54:33 +0200 Subject: [PATCH 13/20] fix(server/wireguard): Improvements --- server/handlers/wireguard/analyzer/structure.go | 15 +++++++++++++++ .../handlers/wireguard/ast/wireguard_handler.go | 11 +++++++++++ server/handlers/wireguard/handlers/completions.go | 4 ++++ .../wireguard/handlers/completions_body.go | 8 ++++++-- .../wireguard/handlers/completions_header.go | 12 ++++-------- 5 files changed, 40 insertions(+), 10 deletions(-) create mode 100644 server/handlers/wireguard/ast/wireguard_handler.go diff --git a/server/handlers/wireguard/analyzer/structure.go b/server/handlers/wireguard/analyzer/structure.go index bbdecfb..3798d89 100644 --- a/server/handlers/wireguard/analyzer/structure.go +++ b/server/handlers/wireguard/analyzer/structure.go @@ -3,6 +3,7 @@ package analyzer import ( "config-lsp/common" "config-lsp/handlers/wireguard/fields" + "config-lsp/parsers/ini" "config-lsp/utils" "fmt" @@ -36,6 +37,20 @@ func analyzeStructureIsValid(ctx *analyzerContext) { protocol.DiagnosticTagUnnecessary, }, }) + } else { + it := section.Properties.Iterator() + + for it.Next() { + property := it.Value().(*ini.Property) + + if property.Key == nil { + ctx.diagnostics = append(ctx.diagnostics, protocol.Diagnostic{ + Message: "This property is missing a key", + Range: property.ToLSPRange(), + Severity: &common.SeverityError, + }) + } + } } } } diff --git a/server/handlers/wireguard/ast/wireguard_handler.go b/server/handlers/wireguard/ast/wireguard_handler.go new file mode 100644 index 0000000..9b8b45b --- /dev/null +++ b/server/handlers/wireguard/ast/wireguard_handler.go @@ -0,0 +1,11 @@ +package ast + +func (c WGConfig) IncludesHeader(headerName string) bool { + for _, section := range c.Sections { + if section.Header.Name == headerName { + return true + } + } + + return false +} diff --git a/server/handlers/wireguard/handlers/completions.go b/server/handlers/wireguard/handlers/completions.go index 783879c..55a2a02 100644 --- a/server/handlers/wireguard/handlers/completions.go +++ b/server/handlers/wireguard/handlers/completions.go @@ -2,6 +2,7 @@ package handlers import ( "config-lsp/handlers/wireguard" + "fmt" protocol "github.com/tliron/glsp/protocol_3_16" ) @@ -19,9 +20,12 @@ func SuggestCompletions( section := d.Config.FindSectionByLine(lineNumber) property := d.Config.FindPropertyByLine(lineNumber) + println(fmt.Sprintf("SuggestCompletions: section: %v, property: %v", section, property)) + if section == nil { // First, the user needs to define a section header if property == nil { + print("asdasd") return GetSectionHeaderCompletions(d, nil) } else { // However, if they start typing a property - we should not diff --git a/server/handlers/wireguard/handlers/completions_body.go b/server/handlers/wireguard/handlers/completions_body.go index c018207..60dee05 100644 --- a/server/handlers/wireguard/handlers/completions_body.go +++ b/server/handlers/wireguard/handlers/completions_body.go @@ -92,7 +92,7 @@ func getPropertyCompletions( */ position := common.LSPCharacterAsCursorPosition(params.Position.Character) - if property == nil || property.Key.ContainsPosition(position) || property.Separator.IsPositionBeforeStart(position) { + if property == nil || (property.Separator != nil && property.Separator.IsPositionBeforeEnd(position)) || (property.Key != nil && property.Key.ContainsPosition(position)) { // First scenario return getKeyCompletions(section, property, params), nil } @@ -153,7 +153,11 @@ func getKeyCompletions( start = 0 end = 0 } else { - start = property.Key.Start.Character + if property.Key == nil { + start = 0 + } else { + start = property.Key.Start.Character + } if property.Value != nil { end = property.Value.Start.Character diff --git a/server/handlers/wireguard/handlers/completions_header.go b/server/handlers/wireguard/handlers/completions_header.go index 809c98a..8e9affe 100644 --- a/server/handlers/wireguard/handlers/completions_header.go +++ b/server/handlers/wireguard/handlers/completions_header.go @@ -4,6 +4,7 @@ import ( "config-lsp/handlers/wireguard" "config-lsp/handlers/wireguard/fields" "config-lsp/parsers/ini" + "fmt" protocol "github.com/tliron/glsp/protocol_3_16" ) @@ -46,14 +47,7 @@ func GetSectionHeaderCompletions( ) ([]protocol.CompletionItem, error) { completions := make([]protocol.CompletionItem, 0) - containsInterfaceSection := false - - for _, section := range d.Config.Sections { - if section.Header.Name == "Interface" { - containsInterfaceSection = true - break - } - } + containsInterfaceSection := d.Config.IncludesHeader("Interface") if !containsInterfaceSection { completions = append(completions, getHeaderCompletion("Interface", fields.HeaderInterfaceEnum.Documentation, existingHeader)) @@ -61,5 +55,7 @@ func GetSectionHeaderCompletions( completions = append(completions, getHeaderCompletion("Peer", fields.HeaderPeerEnum.Documentation, existingHeader)) + print(fmt.Sprintf("laaaaaaaaaaaaaaaaa appended completions: %v", completions)) + return completions, nil } From f3bb73b36fc8a3b7aa74b0c362ad7c7da7525762 Mon Sep 17 00:00:00 2001 From: Myzel394 Date: Wed, 20 Aug 2025 18:54:56 +0200 Subject: [PATCH 14/20] test(server/parsers): Add more tests --- server/parsers/ini/parser_test.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/server/parsers/ini/parser_test.go b/server/parsers/ini/parser_test.go index 6647eb7..8843ca2 100644 --- a/server/parsers/ini/parser_test.go +++ b/server/parsers/ini/parser_test.go @@ -392,3 +392,23 @@ func TestPropertyValueInQuotesWithEscapedQuotes(t *testing.T) { t.Errorf("Parse: Expected property to be correct, got %s = %s", property.Key.Name, property.Value.Raw) } } + +func TestIncompleteProperty(t *testing.T) { + sample := `=world` + + config := NewConfig() + config.XParseConfig = INIParseConfig{ + AllowRootProperties: true, + } + errors := config.Parse(sample) + + if !(len(errors) == 0) { + t.Fatalf("Parse: Expected no errors, but got %v", errors) + } + + rawProperty, _ := config.Sections[0].Properties.Get(uint32(0)) + property := rawProperty.(*Property) + if !(property.Key == nil && property.Value != nil && property.Value.Value == "world" && property.Separator != nil) { + t.Errorf("Parse: Expected property to be 'hello =', but got '%s = %v'", property.Key.Name, property.Value) + } +} From 9c62ee1b4d5340bb09c6128a1434d1aa286147fa Mon Sep 17 00:00:00 2001 From: Myzel394 Date: Wed, 20 Aug 2025 19:15:30 +0200 Subject: [PATCH 15/20] fix(server/bitcoin): Improvements --- .../bitcoin_conf/handlers/completions_property.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/server/handlers/bitcoin_conf/handlers/completions_property.go b/server/handlers/bitcoin_conf/handlers/completions_property.go index 11d91c8..4cdeb38 100644 --- a/server/handlers/bitcoin_conf/handlers/completions_property.go +++ b/server/handlers/bitcoin_conf/handlers/completions_property.go @@ -20,7 +20,7 @@ func GetPropertyCompletions( ) ([]protocol.CompletionItem, error) { position := common.LSPCharacterAsCursorPosition(params.Position.Character) - if property == nil || property.Key.ContainsPosition(position) || property.Separator.IsPositionBeforeStart(position) { + if property == nil || (property.Separator != nil && property.Separator.IsPositionBeforeEnd(position)) || (property.Key != nil && property.Key.ContainsPosition(position)) { // First scenario return getKeyCompletions(section, property, params) } @@ -69,7 +69,11 @@ func getKeyCompletions( start = 0 end = 0 } else { - start = property.Key.Start.Character + if property.Key == nil { + start = 0 + } else { + start = property.Key.Start.Character + } if property.Value != nil { end = property.Value.Start.Character From 06ccd28a3081fa2b35c9afbefadbf13a5ad9949c Mon Sep 17 00:00:00 2001 From: Myzel394 Date: Wed, 20 Aug 2025 19:50:52 +0200 Subject: [PATCH 16/20] chore: Update version --- flake.nix | 2 +- server/common/common.go | 2 +- vs-code-extension/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.nix b/flake.nix index 043a83f..11b470a 100644 --- a/flake.nix +++ b/flake.nix @@ -23,7 +23,7 @@ "aarch64-windows" ] (system: let - version = "0.3.3"; # CI:CD-VERSION + version = "0.3.4"; # CI:CD-VERSION pkgs = import nixpkgs { inherit system; overlays = [ diff --git a/server/common/common.go b/server/common/common.go index dc3481f..7a74177 100644 --- a/server/common/common.go +++ b/server/common/common.go @@ -2,4 +2,4 @@ package common // The comment below at the end of the line is required for the CI:CD to work. // Do not remove it -var Version = "0.3.3" // CI:CD-VERSION +var Version = "0.3.4" // CI:CD-VERSION diff --git a/vs-code-extension/package.json b/vs-code-extension/package.json index a2e0268..b0e16bd 100644 --- a/vs-code-extension/package.json +++ b/vs-code-extension/package.json @@ -2,7 +2,7 @@ "name": "config-lsp", "description": "Language Features (completions, diagnostics, etc.) for your config files: gitconfig, fstab, aliases, hosts, wireguard, ssh_config, sshd_config, bitcoin_conf, and more to come!", "author": "Myzel394", - "version": "0.3.3", + "version": "0.3.4", "repository": { "type": "git", "url": "https://github.com/Myzel394/config-lsp" From 0896edacfb9cf7431789bdea7d2e6e738d5c867a Mon Sep 17 00:00:00 2001 From: Myzel394 Date: Wed, 20 Aug 2025 20:19:42 +0200 Subject: [PATCH 17/20] fix(server): Remove debug prints --- server/handlers/wireguard/handlers/completions.go | 3 --- server/handlers/wireguard/handlers/completions_header.go | 3 --- 2 files changed, 6 deletions(-) diff --git a/server/handlers/wireguard/handlers/completions.go b/server/handlers/wireguard/handlers/completions.go index 55a2a02..60f503f 100644 --- a/server/handlers/wireguard/handlers/completions.go +++ b/server/handlers/wireguard/handlers/completions.go @@ -20,12 +20,9 @@ func SuggestCompletions( section := d.Config.FindSectionByLine(lineNumber) property := d.Config.FindPropertyByLine(lineNumber) - println(fmt.Sprintf("SuggestCompletions: section: %v, property: %v", section, property)) - if section == nil { // First, the user needs to define a section header if property == nil { - print("asdasd") return GetSectionHeaderCompletions(d, nil) } else { // However, if they start typing a property - we should not diff --git a/server/handlers/wireguard/handlers/completions_header.go b/server/handlers/wireguard/handlers/completions_header.go index 8e9affe..06be4fe 100644 --- a/server/handlers/wireguard/handlers/completions_header.go +++ b/server/handlers/wireguard/handlers/completions_header.go @@ -4,7 +4,6 @@ import ( "config-lsp/handlers/wireguard" "config-lsp/handlers/wireguard/fields" "config-lsp/parsers/ini" - "fmt" protocol "github.com/tliron/glsp/protocol_3_16" ) @@ -55,7 +54,5 @@ func GetSectionHeaderCompletions( completions = append(completions, getHeaderCompletion("Peer", fields.HeaderPeerEnum.Documentation, existingHeader)) - print(fmt.Sprintf("laaaaaaaaaaaaaaaaa appended completions: %v", completions)) - return completions, nil } From 4b8bbea19ea5c59e50ec67f6da5899a6536e3d45 Mon Sep 17 00:00:00 2001 From: Myzel394 Date: Wed, 20 Aug 2025 20:24:42 +0200 Subject: [PATCH 18/20] fix(server): fix --- server/handlers/wireguard/handlers/completions.go | 1 - 1 file changed, 1 deletion(-) diff --git a/server/handlers/wireguard/handlers/completions.go b/server/handlers/wireguard/handlers/completions.go index 60f503f..783879c 100644 --- a/server/handlers/wireguard/handlers/completions.go +++ b/server/handlers/wireguard/handlers/completions.go @@ -2,7 +2,6 @@ package handlers import ( "config-lsp/handlers/wireguard" - "fmt" protocol "github.com/tliron/glsp/protocol_3_16" ) From 4fab4afe4020f478cba011e66285e63bd2b9e4c6 Mon Sep 17 00:00:00 2001 From: Myzel394 Date: Wed, 20 Aug 2025 20:31:58 +0200 Subject: [PATCH 19/20] fix: Fix flake --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 11b470a..eb94541 100644 --- a/flake.nix +++ b/flake.nix @@ -38,7 +38,7 @@ pkgs.go_1_24 ]; serverUncompressed = (pkgs: pkgs.buildGoModule { - CGO_ENABLED = 0; + "env.CGO_ENABLED" = 1; nativeBuildInputs = inputs; pname = "github.com/Myzel394/config-lsp"; version = version; From 1f69b65a4e61f749f766dbc026e0cf5ad176ed84 Mon Sep 17 00:00:00 2001 From: Myzel394 Date: Wed, 20 Aug 2025 20:33:31 +0200 Subject: [PATCH 20/20] fix(server/wireguard): Use pointer --- server/handlers/wireguard/ast/wireguard_handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/handlers/wireguard/ast/wireguard_handler.go b/server/handlers/wireguard/ast/wireguard_handler.go index 9b8b45b..aa51aab 100644 --- a/server/handlers/wireguard/ast/wireguard_handler.go +++ b/server/handlers/wireguard/ast/wireguard_handler.go @@ -1,6 +1,6 @@ package ast -func (c WGConfig) IncludesHeader(headerName string) bool { +func (c *WGConfig) IncludesHeader(headerName string) bool { for _, section := range c.Sections { if section.Header.Name == headerName { return true