feat(aliases): Add signature help support; Improvements

This commit is contained in:
Myzel394 2024-09-08 16:59:01 +02:00
parent e1af64f2c0
commit 2768f99d2f
No known key found for this signature in database
GPG Key ID: DEC4AAB876F73185
8 changed files with 484 additions and 34 deletions

View File

@ -5,14 +5,50 @@ import (
docvalues "config-lsp/doc-values"
)
var UserField = docvalues.UserValue("", false)
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 PathField = docvalues.PathValue{
var UserDeclaration = "`user`"
var PathField = docvalues.DocumentationValue{
Documentation: "Append messages to file, specified by its absolute pathname",
Value: docvalues.PathValue{
RequiredType: docvalues.PathTypeFile,
},
}
var CommandField = docvalues.StringValue{}
var PathDeclaration = "`/path/to/file`"
var EmailField = docvalues.RegexValue{
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`"

View File

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

View File

@ -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: "<alias>: <value1>, <value2>, ...",
ActiveParameter: &activeParameter,
Parameters: []protocol.ParameterInformation{
{
Label: []uint32{
0,
uint32(len("<alias>") + 1),
},
Documentation: "The alias to define",
},
{
Label: []uint32{
uint32(len("<alias>:")),
uint32(len("<alias>:") + len("<value1>")),
},
Documentation: "A value to associate with the alias",
},
},
},
},
}
}
func GetAllValuesSignatureHelp() *protocol.SignatureHelp {
index := uint32(0)
return &protocol.SignatureHelp{
Signatures: []protocol.SignatureInformation{
{
Label: "<user>",
ActiveParameter: &index,
Parameters: []protocol.ParameterInformation{
{
Label: []uint32{
0,
uint32(len("<user>")),
},
},
},
},
{
Label: "<user>@<host>",
ActiveParameter: &index,
Parameters: []protocol.ParameterInformation{
{
Label: []uint32{
0,
uint32(len("<user>")),
},
},
{
Label: []uint32{
uint32(len("<user>@")),
uint32(len("<user>@<host>")),
},
},
},
},
{
Label: "<file>",
ActiveParameter: &index,
Parameters: []protocol.ParameterInformation{
{
Label: []uint32{
0,
uint32(len("<file>")),
},
},
},
},
{
Label: ":include:<file>",
ActiveParameter: &index,
Parameters: []protocol.ParameterInformation{
{
Label: []uint32{
0,
uint32(len(":include:")),
},
},
},
},
{
Label: "|<command>",
ActiveParameter: &index,
Parameters: []protocol.ParameterInformation{
{
Label: []uint32{
0,
1,
},
},
},
},
{
Label: "error:<code> <message>",
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: "<user>",
ActiveParameter: &index,
Parameters: []protocol.ParameterInformation{
{
Label: []uint32{
0,
uint32(len("<user>")),
},
},
},
},
{
Label: "<user>@<host>",
ActiveParameter: &index,
Parameters: []protocol.ParameterInformation{
{
Label: []uint32{
0,
uint32(len("<user>")),
},
},
{
Label: []uint32{
uint32(len("<user>@")),
uint32(len("<user>@") + len("<host>")),
},
},
},
},
},
}
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: "<user>@<host>",
ActiveParameter: &index,
Parameters: []protocol.ParameterInformation{
{
Label: []uint32{
0,
uint32(len("<user>")),
},
},
{
Label: []uint32{
uint32(len("<user>@")),
uint32(len("<user>@") + len("<host>")),
},
},
},
},
},
}
case ast.AliasValueFile:
index := uint32(0)
return &protocol.SignatureHelp{
Signatures: []protocol.SignatureInformation{
{
Label: "<file>",
ActiveParameter: &index,
Parameters: []protocol.ParameterInformation{
{
Label: []uint32{
0,
uint32(len("<file>")),
},
},
},
},
},
}
case ast.AliasValueInclude:
index := uint32(0)
return &protocol.SignatureHelp{
Signatures: []protocol.SignatureInformation{
{
Label: "include:<file>",
ActiveParameter: &index,
Parameters: []protocol.ParameterInformation{
{
Label: []uint32{
uint32(len("include:")),
uint32(len("include:<file>")),
},
},
},
},
},
}
case ast.AliasValueCommand:
var index uint32
if cursor == 0 {
index = 0
} else {
index = 1
}
return &protocol.SignatureHelp{
Signatures: []protocol.SignatureInformation{
{
Label: "|<command>",
ActiveParameter: &index,
Parameters: []protocol.ParameterInformation{
{
Label: []uint32{
0,
1,
},
},
{
Label: []uint32{
1,
uint32(1 + len("<command>")),
},
},
},
},
},
}
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:<code> <message>",
ActiveParameter: &index,
Parameters: []protocol.ParameterInformation{
{
Label: []uint32{
0,
uint32(len("error:")),
},
},
{
Label: []uint32{
uint32(len("error:")),
uint32(len("error:<code>")),
},
},
{
Label: []uint32{
uint32(len("error:<code> ")),
uint32(len("error:<code> <message>")),
},
},
},
},
},
}
}
return nil
}

View File

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

View File

@ -31,6 +31,7 @@ func SetUpRootHandler() {
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

View File

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

View File

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

View File

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