From 2768f99d2f730e9b327eaaa294a697dac4fe27e2 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sun, 8 Sep 2024 16:59:01 +0200 Subject: [PATCH] feat(aliases): Add signature help support; Improvements --- handlers/aliases/fields/fields.go | 50 ++- handlers/aliases/handlers/hover.go | 25 +- handlers/aliases/handlers/signature_help.go | 309 ++++++++++++++++++ .../lsp/text-document-signature-help.go | 52 +++ root-handler/handler.go | 36 +- root-handler/text-document-prepare-rename.go | 3 +- root-handler/text-document-signature-help.go | 37 +++ utils/strings.go | 6 + 8 files changed, 484 insertions(+), 34 deletions(-) create mode 100644 handlers/aliases/handlers/signature_help.go create mode 100644 handlers/aliases/lsp/text-document-signature-help.go create mode 100644 root-handler/text-document-signature-help.go diff --git a/handlers/aliases/fields/fields.go b/handlers/aliases/fields/fields.go index 940ce3e..f29804e 100644 --- a/handlers/aliases/fields/fields.go +++ b/handlers/aliases/fields/fields.go @@ -5,14 +5,50 @@ import ( docvalues "config-lsp/doc-values" ) -var UserField = docvalues.UserValue("", false) - -var PathField = docvalues.PathValue{ - RequiredType: docvalues.PathTypeFile, +var UserField = docvalues.DocumentationValue{ + Documentation: "A user on the host machine. The user must have a valid entry in the passwd(5) database file.", + Value: docvalues.UserValue("", false), } -var CommandField = docvalues.StringValue{} +var UserDeclaration = "`user`" -var EmailField = docvalues.RegexValue{ - Regex: *commondocumentation.EmailRegex, +var PathField = docvalues.DocumentationValue{ + Documentation: "Append messages to file, specified by its absolute pathname", + Value: docvalues.PathValue{ + RequiredType: docvalues.PathTypeFile, + }, } + +var PathDeclaration = "`/path/to/file`" + +var CommandField = docvalues.DocumentationValue{ + Documentation: "Pipe the message to command on its standard input. The command is run under the privileges of the daemon's unprivileged account.", + Value: docvalues.StringValue{}, +} + +var CommandDeclaration = "`|command`" + +var EmailField = docvalues.DocumentationValue{ + Documentation: "An email address in RFC 5322 format. If an address extension is appended to the user-part, it is first compared for an exact match. It is then stripped so that an address such as user+ext@example.com will only use the part that precedes ‘+’ as a key.", + Value: docvalues.RegexValue{ + Regex: *commondocumentation.EmailRegex, + }, +} + +var EmailDeclaration = "`user-part@domain-part`" + +var IncludeField = docvalues.DocumentationValue{ + Documentation: "Include any definitions in file as alias entries. The format of the file is identical to this one.", + Value: docvalues.PathValue{ + RequiredType: docvalues.PathTypeFile, + }, +} + +var IncludeDeclaration = "`include:/path/to/file`" + +var ErrorMessageField = docvalues.DocumentationValue{ + Documentation: "A status code and message to return. The code must be 3 digits, starting 4XX (TempFail) or 5XX (PermFail). The message must be present and can be freely chosen.", + Value: docvalues.StringValue{}, +} + +var ErrorDeclaration = "`error:code message`" diff --git a/handlers/aliases/handlers/hover.go b/handlers/aliases/handlers/hover.go index 84206e7..8cd5a8e 100644 --- a/handlers/aliases/handlers/hover.go +++ b/handlers/aliases/handlers/hover.go @@ -2,6 +2,7 @@ package handlers import ( "config-lsp/handlers/aliases/ast" + "config-lsp/handlers/aliases/fields" "config-lsp/handlers/aliases/indexes" "config-lsp/utils" "fmt" @@ -100,44 +101,44 @@ func GetAliasValueTypeInfo( case ast.AliasValueUser: return []string{ "### User", - "`user`", + fields.UserDeclaration, "", - "A user on the host machine. The user must have a valid entry in the passwd(5) database file.", + fields.UserField.Documentation, } case ast.AliasValueEmail: return []string{ "### Email", - "`user-part@domain-part`", + fields.EmailDeclaration, "", - "An email address in RFC 5322 format. If an address extension is appended to the user-part, it is first compared for an exact match. It is then stripped so that an address such as user+ext@example.com will only use the part that precedes ‘+’ as a key.", + fields.EmailField.Documentation, } case ast.AliasValueInclude: return []string{ "### Include", - "`include:/path/to/file`", + fields.IncludeDeclaration, "", - "Include any definitions in file as alias entries. The format of the file is identical to this one.", + fields.IncludeField.Documentation, } case ast.AliasValueFile: return []string{ "### File", - "`/path/to/file`", + fields.PathDeclaration, "", - "Append messages to file, specified by its absolute pathname.", + fields.PathField.Documentation, } case ast.AliasValueCommand: return []string{ "### Command", - "`|command`", + fields.CommandDeclaration, "", - "Pipe the message to command on its standard input. The command is run under the privileges of the daemon's unprivileged account.", + fields.CommandField.Documentation, } case ast.AliasValueError: return []string{ "### Error", - "`error:code message`", + fields.ErrorDeclaration, "", - "A status code and message to return. The code must be 3 digits, starting 4XX (TempFail) or 5XX (PermFail). The message must be present and can be freely chosen.", + fields.ErrorMessageField.Documentation, } } diff --git a/handlers/aliases/handlers/signature_help.go b/handlers/aliases/handlers/signature_help.go new file mode 100644 index 0000000..7da7f06 --- /dev/null +++ b/handlers/aliases/handlers/signature_help.go @@ -0,0 +1,309 @@ +package handlers + +import ( + "config-lsp/handlers/aliases/ast" + "strings" + + protocol "github.com/tliron/glsp/protocol_3_16" +) + +func GetRootSignatureHelp( + activeParameter uint32, +) *protocol.SignatureHelp { + index := uint32(0) + return &protocol.SignatureHelp{ + ActiveSignature: &index, + Signatures: []protocol.SignatureInformation{ + { + Label: ": , , ...", + ActiveParameter: &activeParameter, + Parameters: []protocol.ParameterInformation{ + { + Label: []uint32{ + 0, + uint32(len("") + 1), + }, + Documentation: "The alias to define", + }, + { + Label: []uint32{ + uint32(len(":")), + uint32(len(":") + len("")), + }, + Documentation: "A value to associate with the alias", + }, + }, + }, + }, + } +} + +func GetAllValuesSignatureHelp() *protocol.SignatureHelp { + index := uint32(0) + return &protocol.SignatureHelp{ + Signatures: []protocol.SignatureInformation{ + { + Label: "", + ActiveParameter: &index, + Parameters: []protocol.ParameterInformation{ + { + Label: []uint32{ + 0, + uint32(len("")), + }, + }, + }, + }, + { + Label: "@", + ActiveParameter: &index, + Parameters: []protocol.ParameterInformation{ + { + Label: []uint32{ + 0, + uint32(len("")), + }, + }, + { + Label: []uint32{ + uint32(len("@")), + uint32(len("@")), + }, + }, + }, + }, + { + Label: "", + ActiveParameter: &index, + Parameters: []protocol.ParameterInformation{ + { + Label: []uint32{ + 0, + uint32(len("")), + }, + }, + }, + }, + { + Label: ":include:", + ActiveParameter: &index, + Parameters: []protocol.ParameterInformation{ + { + Label: []uint32{ + 0, + uint32(len(":include:")), + }, + }, + }, + }, + { + Label: "|", + ActiveParameter: &index, + Parameters: []protocol.ParameterInformation{ + { + Label: []uint32{ + 0, + 1, + }, + }, + }, + }, + { + Label: "error: ", + ActiveParameter: &index, + Parameters: []protocol.ParameterInformation{ + { + Label: []uint32{ + 0, + uint32(len("error")), + }, + }, + }, + }, + }, + } +} + +func GetValueSignatureHelp( + value ast.AliasValueInterface, + cursor uint32, +) *protocol.SignatureHelp { + switch value.(type) { + case ast.AliasValueUser: + index := uint32(0) + return &protocol.SignatureHelp{ + Signatures: []protocol.SignatureInformation{ + { + Label: "", + ActiveParameter: &index, + Parameters: []protocol.ParameterInformation{ + { + Label: []uint32{ + 0, + uint32(len("")), + }, + }, + }, + }, + { + Label: "@", + ActiveParameter: &index, + Parameters: []protocol.ParameterInformation{ + { + Label: []uint32{ + 0, + uint32(len("")), + }, + }, + { + Label: []uint32{ + uint32(len("@")), + uint32(len("@") + len("")), + }, + }, + }, + }, + }, + } + case ast.AliasValueEmail: + isBeforeAtSymbol := cursor <= uint32(strings.Index(value.GetAliasValue().Value, "@")) + + var index uint32 + + if isBeforeAtSymbol { + index = 0 + } else { + index = 1 + } + + return &protocol.SignatureHelp{ + Signatures: []protocol.SignatureInformation{ + { + Label: "@", + ActiveParameter: &index, + Parameters: []protocol.ParameterInformation{ + { + Label: []uint32{ + 0, + uint32(len("")), + }, + }, + { + Label: []uint32{ + uint32(len("@")), + uint32(len("@") + len("")), + }, + }, + }, + }, + }, + } + case ast.AliasValueFile: + index := uint32(0) + return &protocol.SignatureHelp{ + Signatures: []protocol.SignatureInformation{ + { + Label: "", + ActiveParameter: &index, + Parameters: []protocol.ParameterInformation{ + { + Label: []uint32{ + 0, + uint32(len("")), + }, + }, + }, + }, + }, + } + case ast.AliasValueInclude: + index := uint32(0) + return &protocol.SignatureHelp{ + Signatures: []protocol.SignatureInformation{ + { + Label: "include:", + ActiveParameter: &index, + Parameters: []protocol.ParameterInformation{ + { + Label: []uint32{ + uint32(len("include:")), + uint32(len("include:")), + }, + }, + }, + }, + }, + } + case ast.AliasValueCommand: + var index uint32 + + if cursor == 0 { + index = 0 + } else { + index = 1 + } + + return &protocol.SignatureHelp{ + Signatures: []protocol.SignatureInformation{ + { + Label: "|", + ActiveParameter: &index, + Parameters: []protocol.ParameterInformation{ + { + Label: []uint32{ + 0, + 1, + }, + }, + { + Label: []uint32{ + 1, + uint32(1 + len("")), + }, + }, + }, + }, + }, + } + case ast.AliasValueError: + errorValue := value.(ast.AliasValueError) + var index uint32 + + if errorValue.Code == nil || cursor <= errorValue.Code.Location.End.Character { + index = 1 + } else { + index = 2 + } + + return &protocol.SignatureHelp{ + Signatures: []protocol.SignatureInformation{ + { + Label: "error: ", + ActiveParameter: &index, + Parameters: []protocol.ParameterInformation{ + { + Label: []uint32{ + 0, + uint32(len("error:")), + }, + }, + { + Label: []uint32{ + uint32(len("error:")), + uint32(len("error:")), + }, + }, + { + Label: []uint32{ + uint32(len("error: ")), + uint32(len("error: ")), + }, + }, + }, + }, + }, + } + } + + return nil +} diff --git a/handlers/aliases/lsp/text-document-signature-help.go b/handlers/aliases/lsp/text-document-signature-help.go new file mode 100644 index 0000000..ce3a9b0 --- /dev/null +++ b/handlers/aliases/lsp/text-document-signature-help.go @@ -0,0 +1,52 @@ +package lsp + +import ( + "config-lsp/handlers/aliases" + "config-lsp/handlers/aliases/ast" + "config-lsp/handlers/aliases/handlers" + + "github.com/tliron/glsp" + protocol "github.com/tliron/glsp/protocol_3_16" +) + +func TextDocumentSignatureHelp(context *glsp.Context, params *protocol.SignatureHelpParams) (*protocol.SignatureHelp, error) { + document := aliases.DocumentParserMap[params.TextDocument.URI] + + line := params.Position.Line + character := params.Position.Character + + if _, found := document.Parser.CommentLines[line]; found { + // Comment + return nil, nil + } + + rawEntry, found := document.Parser.Aliases.Get(line) + + if !found { + return handlers.GetRootSignatureHelp(0), nil + } + + entry := rawEntry.(*ast.AliasEntry) + + if entry.Key != nil && character >= entry.Key.Location.Start.Character && character <= entry.Key.Location.End.Character { + return handlers.GetRootSignatureHelp(0), nil + } + + if entry.Values != nil && character >= entry.Values.Location.Start.Character && character <= entry.Values.Location.End.Character { + value := handlers.GetValueAtCursor(character, entry) + + if value == nil { + // For some reason, this does not really work, + // When we return all, and then a user value is entered + // and the `GetValueSignatureHelp` is called, still the old + // signatures with all signature are shown + // return handlers.GetAllValuesSignatureHelp(), nil + + return nil, nil + } + + return handlers.GetValueSignatureHelp(*value, character), nil + } + + return nil, nil +} diff --git a/root-handler/handler.go b/root-handler/handler.go index 9dff13c..4a7cd3c 100644 --- a/root-handler/handler.go +++ b/root-handler/handler.go @@ -17,20 +17,21 @@ 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, - TextDocumentCodeAction: TextDocumentCodeAction, - TextDocumentDefinition: TextDocumentDefinition, - WorkspaceExecuteCommand: WorkspaceExecuteCommand, - TextDocumentRename: TextDocumentRename, + Initialize: initialize, + Initialized: initialized, + Shutdown: shutdown, + SetTrace: setTrace, + TextDocumentDidOpen: TextDocumentDidOpen, + TextDocumentDidChange: TextDocumentDidChange, + TextDocumentCompletion: TextDocumentCompletion, + TextDocumentHover: TextDocumentHover, + TextDocumentDidClose: TextDocumentDidClose, + TextDocumentCodeAction: TextDocumentCodeAction, + TextDocumentDefinition: TextDocumentDefinition, + WorkspaceExecuteCommand: WorkspaceExecuteCommand, + TextDocumentRename: TextDocumentRename, TextDocumentPrepareRename: TextDocumentPrepareRename, + TextDocumentSignatureHelp: TextDocumentSignatureHelp, } server := server.NewServer(&lspHandler, lsName, false) @@ -41,6 +42,15 @@ func SetUpRootHandler() { func initialize(context *glsp.Context, params *protocol.InitializeParams) (any, error) { capabilities := lspHandler.CreateServerCapabilities() capabilities.TextDocumentSync = protocol.TextDocumentSyncKindFull + capabilities.SignatureHelpProvider = &protocol.SignatureHelpOptions{ + TriggerCharacters: []string{ + "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", + "_", "-", ".", "/", ":", "@", "#", "!", "$", "%", "^", "&", "*", "(", ")", "+", "=", "[", "]", "{", "}", "<", ">", "?", ";", ",", "|", + " ", + }, + } if (*params.Capabilities.TextDocument.Rename.PrepareSupport) == true { // Client supports rename preparation diff --git a/root-handler/text-document-prepare-rename.go b/root-handler/text-document-prepare-rename.go index fc4489b..6dbc75a 100644 --- a/root-handler/text-document-prepare-rename.go +++ b/root-handler/text-document-prepare-rename.go @@ -1,8 +1,8 @@ package roothandler import ( - "github.com/tliron/glsp" aliases "config-lsp/handlers/aliases/lsp" + "github.com/tliron/glsp" protocol "github.com/tliron/glsp/protocol_3_16" ) @@ -35,4 +35,3 @@ func TextDocumentPrepareRename(context *glsp.Context, params *protocol.PrepareRe panic("root-handler/TextDocumentPrepareRename: unexpected language" + *language) } - diff --git a/root-handler/text-document-signature-help.go b/root-handler/text-document-signature-help.go new file mode 100644 index 0000000..a3a7396 --- /dev/null +++ b/root-handler/text-document-signature-help.go @@ -0,0 +1,37 @@ +package roothandler + +import ( + aliases "config-lsp/handlers/aliases/lsp" + + "github.com/tliron/glsp" + protocol "github.com/tliron/glsp/protocol_3_16" +) + +func TextDocumentSignatureHelp(context *glsp.Context, params *protocol.SignatureHelpParams) (*protocol.SignatureHelp, error) { + language := rootHandler.GetLanguageForDocument(params.TextDocument.URI) + + if language == nil { + showParseError( + context, + params.TextDocument.URI, + undetectableError, + ) + + return nil, undetectableError.Err + } + + switch *language { + case LanguageHosts: + return nil, nil + case LanguageSSHDConfig: + return nil, nil + case LanguageFstab: + return nil, nil + case LanguageWireguard: + return nil, nil + case LanguageAliases: + return aliases.TextDocumentSignatureHelp(context, params) + } + + panic("root-handler/TextDocumentSignatureHelp: unexpected language" + *language) +} diff --git a/utils/strings.go b/utils/strings.go index 1e4f429..bc32f88 100644 --- a/utils/strings.go +++ b/utils/strings.go @@ -51,3 +51,9 @@ func CountCharacterOccurrences(line string, character rune) int { return count } + +var emptyRegex = regexp.MustCompile(`^\s*$`) + +func IsEmpty(s string) bool { + return emptyRegex.MatchString(s) +}