diff --git a/config-lsp b/config-lsp new file mode 100755 index 0000000..69fc8f6 Binary files /dev/null and b/config-lsp differ diff --git a/handlers/wireguard/code-actions.go b/handlers/wireguard/code-actions.go new file mode 100644 index 0000000..1894130 --- /dev/null +++ b/handlers/wireguard/code-actions.go @@ -0,0 +1,73 @@ +package wireguard + +import ( + protocol "github.com/tliron/glsp/protocol_3_16" +) + +type codeActionName string + +const ( + codeActionGeneratePrivateKey codeActionName = "generatePrivateKey" +) + +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 (p wireguardProperty) getInsertRange(line uint32) protocol.Range { + insertPosition := p.Separator.Location.End + var length uint32 = 0 + + if p.Value != nil { + // Length of the value + length = p.Value.Location.End - p.Value.Location.Start + } + + 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 +} diff --git a/handlers/wireguard/text-document-code-action.go b/handlers/wireguard/text-document-code-action.go new file mode 100644 index 0000000..892f2bf --- /dev/null +++ b/handlers/wireguard/text-document-code-action.go @@ -0,0 +1,45 @@ +package wireguard + +import ( + "github.com/tliron/glsp" + protocol "github.com/tliron/glsp/protocol_3_16" +) + +func TextDocumentCodeAction(context *glsp.Context, params *protocol.CodeActionParams) ([]protocol.CodeAction, error) { + line := params.Range.Start.Line + parser := documentParserMap[params.TextDocument.URI] + + section, property := parser.getPropertyByLine(line) + + if section == nil || property == nil || property.Separator == nil { + return nil, nil + } + + switch property.Key.Name { + case "PrivateKey": + if !areWireguardToolsAvailable() { + return nil, nil + } + + commandID := "wireguard." + codeActionGeneratePrivateKey + command := protocol.Command{ + Title: "Generate Private Key", + Command: string(commandID), + Arguments: []any{ + codeActionGeneratePrivateKeyArgs{ + URI: params.TextDocument.URI, + Line: line, + }, + }, + } + + return []protocol.CodeAction{ + { + Title: "Generate Private Key", + Command: &command, + }, + }, nil + } + + return nil, nil +} diff --git a/handlers/wireguard/text-document-hover.go b/handlers/wireguard/text-document-hover.go index 82d92d9..f4a3965 100644 --- a/handlers/wireguard/text-document-hover.go +++ b/handlers/wireguard/text-document-hover.go @@ -11,8 +11,6 @@ func TextDocumentHover( context *glsp.Context, params *protocol.HoverParams, ) (*protocol.Hover, error) { - // cursor := params.Position.Character - parser := documentParserMap[params.TextDocument.URI] switch parser.getTypeByLine(params.Position.Line) { diff --git a/handlers/wireguard/wg-commands.go b/handlers/wireguard/wg-commands.go new file mode 100644 index 0000000..7a345cf --- /dev/null +++ b/handlers/wireguard/wg-commands.go @@ -0,0 +1,37 @@ +package wireguard + +import ( + "os/exec" + "strings" +) + +func areWireguardToolsAvailable() bool { + _, err := exec.LookPath("wg") + + return err == nil +} + +func createNewPrivateKey() (string, error) { + cmd := exec.Command("wg", "genkey") + + bytes, err := cmd.Output() + + if err != nil { + return "", err + } + + return string(bytes), nil +} + +func createPublicKey(privateKey string) (string, error) { + cmd := exec.Command("wg", "pubkey") + cmd.Stdin = strings.NewReader(privateKey) + + bytes, err := cmd.Output() + + if err != nil { + return "", err + } + + return strings.ReplaceAll(string(bytes), "\n", ""), nil +} diff --git a/handlers/wireguard/wg-parser.go b/handlers/wireguard/wg-parser.go index 9c60ba4..65f08e0 100644 --- a/handlers/wireguard/wg-parser.go +++ b/handlers/wireguard/wg-parser.go @@ -327,3 +327,19 @@ func (p *wireguardParser) getBelongingSectionByLine(line uint32) *wireguardSecti return nil } + +func (p *wireguardParser) getPropertyByLine(line uint32) (*wireguardSection, *wireguardProperty) { + section := p.getSectionByLine(line) + + if section.Name == nil { + return nil, nil + } + + property, _ := section.findProperty(line) + + if property == nil { + return nil, nil + } + + return section, property +} diff --git a/handlers/wireguard/wg_commands_test.go b/handlers/wireguard/wg_commands_test.go new file mode 100644 index 0000000..6cb2de6 --- /dev/null +++ b/handlers/wireguard/wg_commands_test.go @@ -0,0 +1,40 @@ +package wireguard + +import "testing" + +func TestWireguardAvailable( + t *testing.T, +) { + if !areWireguardToolsAvailable() { + t.Skip("Wireguard tools not available") + } +} + +func TestWireguardPrivateKey( + t *testing.T, +) { + privateKey, err := createNewPrivateKey() + + if err != nil { + t.Fatal(err) + } + + t.Log(privateKey) +} + +func TestWireguardPublicKey( + t *testing.T, +) { + privateKey := "UPBKR0kLF2C/+Ei5fwN5KHsAcon9xfBX+RWhebYFGWg=" + publicKey, err := createPublicKey(privateKey) + + if err != nil { + t.Fatal(err) + } + + if publicKey != "3IPUqUKXUkkU7tNp/G/KgcBqUh3N0WWJpfQf79lGdl0=" { + t.Fatalf("Public key does not match, it's: %v", publicKey) + } + + t.Log(publicKey) +} diff --git a/handlers/wireguard/workspace-execute-command.go b/handlers/wireguard/workspace-execute-command.go new file mode 100644 index 0000000..39a1a60 --- /dev/null +++ b/handlers/wireguard/workspace-execute-command.go @@ -0,0 +1,23 @@ +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) + } + + return nil, nil +} diff --git a/root-handler/handler.go b/root-handler/handler.go index 96ec971..565413e 100644 --- a/root-handler/handler.go +++ b/root-handler/handler.go @@ -17,15 +17,17 @@ var lspHandler protocol.Handler func SetUpRootHandler() { rootHandler = NewRootHandler() lspHandler = protocol.Handler{ - Initialize: initialize, - Initialized: initialized, - Shutdown: shutdown, - SetTrace: setTrace, - TextDocumentDidOpen: TextDocumentDidOpen, - TextDocumentDidChange: TextDocumentDidChange, - TextDocumentCompletion: TextDocumentCompletion, - TextDocumentHover: TextDocumentHover, - TextDocumentDidClose: TextDocumentDidClose, + Initialize: initialize, + Initialized: initialized, + Shutdown: shutdown, + SetTrace: setTrace, + TextDocumentDidOpen: TextDocumentDidOpen, + TextDocumentDidChange: TextDocumentDidChange, + TextDocumentCompletion: TextDocumentCompletion, + TextDocumentHover: TextDocumentHover, + TextDocumentDidClose: TextDocumentDidClose, + TextDocumentCodeAction: TextDocumentCodeAction, + WorkspaceExecuteCommand: WorkspaceExecuteCommand, } server := server.NewServer(&lspHandler, lsName, false) diff --git a/root-handler/text-document-code-action.go b/root-handler/text-document-code-action.go new file mode 100644 index 0000000..a2f98e5 --- /dev/null +++ b/root-handler/text-document-code-action.go @@ -0,0 +1,23 @@ +package roothandler + +import ( + "config-lsp/handlers/wireguard" + + "github.com/tliron/glsp" + protocol "github.com/tliron/glsp/protocol_3_16" +) + +func TextDocumentCodeAction(context *glsp.Context, params *protocol.CodeActionParams) (any, error) { + language := rootHandler.GetLanguageForDocument(params.TextDocument.URI) + + switch language { + case LanguageFstab: + return nil, nil + case LanguageSSHDConfig: + return nil, nil + case LanguageWireguard: + return wireguard.TextDocumentCodeAction(context, params) + } + + panic("root-handler/TextDocumentCompletion: unexpected language" + language) +} diff --git a/root-handler/workspace-execute-command.go b/root-handler/workspace-execute-command.go new file mode 100644 index 0000000..90b2fcb --- /dev/null +++ b/root-handler/workspace-execute-command.go @@ -0,0 +1,32 @@ +package roothandler + +import ( + "config-lsp/handlers/wireguard" + "strings" + + "github.com/tliron/glsp" + protocol "github.com/tliron/glsp/protocol_3_16" +) + +func WorkspaceExecuteCommand(context *glsp.Context, params *protocol.ExecuteCommandParams) (any, error) { + commandSection, _, _ := strings.Cut(params.Command, ".") + + var edit *protocol.ApplyWorkspaceEditParams + var err error + + switch commandSection { + case "wireguard": + edit, err = wireguard.WorkspaceExecuteCommand(context, params) + } + + if err != nil { + return nil, err + } + + context.Notify( + "workspace/applyEdit", + edit, + ) + + return nil, nil +}