feat(ssh_config): Add completions support for tokens

This commit is contained in:
Myzel394 2024-10-14 12:51:19 +02:00
parent 3caaf6aec4
commit b4aaa2cc3a
No known key found for this signature in database
GPG Key ID: ED20A1D1D423AF3F
6 changed files with 69 additions and 37 deletions

View File

@ -28,7 +28,7 @@ func analyzeTokens(
tokens = []string{} tokens = []string{}
} }
disallowedTokens := utils.Without(fields.AvailableTokens, tokens) disallowedTokens := utils.Without(utils.KeysOfMap(fields.AvailableTokens), tokens)
for _, token := range disallowedTokens { for _, token := range disallowedTokens {
if strings.Contains(text, token) { if strings.Contains(text, token) {

View File

@ -1,25 +1,27 @@
package fields package fields
var AvailableTokens = []string{ import "config-lsp/utils"
"%%",
"%C", var AvailableTokens = map[string]string{
"%d", "%%": "A literal %.",
"%f", "%C": "Hash of %l%h%p%r%j.",
"%H", "%d": "Local user's home directory.",
"%h", "%f": "The fingerprint of the server's host key.",
"%l", "%H": "The known_hosts hostname or address that is being searched for.",
"%i", "%h": "The remote hostname.",
"%j", "%I": "A string describing the reason for a KnownHostsCommand execution: either ADDRESS when looking up a host by address (only when CheckHostIP is enabled), HOSTNAME when searching by hostname, or ORDER when preparing the host key algorithm preference list to use for the destination host.",
"%K", "%i": "The local user ID.",
"%k", "%j": "The contents of the ProxyJump option, or the empty string if this option is unset.",
"%L", "%K": "The base64 encoded host key.",
"%l", "%k": "The host key alias if specified, otherwise the original remote hostname given on the command line.",
"%n", "%L": "The local hostname.",
"%p", "%l": "The local hostname, including the domain name.",
"%r", "%n": "The original remote hostname, as given on the command line.",
"%T", "%p": "The remote port.",
"%t", "%r": "The remote username.",
"%u", "%T": "The local tun(4) or tap(4) network interface assigned if tunnel forwarding was requested, or \"NONE\" otherwise.",
"%t": "The type of the server host key, e.g. ssh-ed25519.",
"%u": "The local username.",
} }
// A map of <option name> to <list of supported tokens> // A map of <option name> to <list of supported tokens>
@ -47,7 +49,7 @@ var OptionsTokensMap = map[NormalizedOptionName][]string{
"%h", "%h",
}, },
"localcommand": AvailableTokens, "localcommand": utils.KeysOfMap(AvailableTokens),
"proxycommand": { "proxycommand": {
"%%", "%h", "%n", "%p", "%r", "%%", "%h", "%n", "%p", "%r",

View File

@ -15,7 +15,7 @@ func GetRootCompletions(
d *sshconfig.SSHDocument, d *sshconfig.SSHDocument,
parentBlock ast.SSHBlock, parentBlock ast.SSHBlock,
suggestValue bool, suggestValue bool,
) ([]protocol.CompletionItem, error) { ) []protocol.CompletionItem {
kind := protocol.CompletionItemKindField kind := protocol.CompletionItemKindField
availableOptions := make(map[fields.NormalizedOptionName]docvalues.DocumentationValue, 0) availableOptions := make(map[fields.NormalizedOptionName]docvalues.DocumentationValue, 0)
@ -58,7 +58,7 @@ func GetRootCompletions(
return *completion return *completion
}, },
), nil )
} }
func GetOptionCompletions( func GetOptionCompletions(
@ -67,11 +67,11 @@ func GetOptionCompletions(
block ast.SSHBlock, block ast.SSHBlock,
line uint32, line uint32,
cursor common.CursorPosition, cursor common.CursorPosition,
) ([]protocol.CompletionItem, error) { ) []protocol.CompletionItem {
option, found := fields.Options[entry.Key.Key] option, found := fields.Options[entry.Key.Key]
if !found { if !found {
return nil, nil return nil
} }
if entry.Key.Key == matchOption { if entry.Key.Key == matchOption {
@ -92,18 +92,48 @@ func GetOptionCompletions(
} }
if entry.OptionValue == nil { if entry.OptionValue == nil {
return option.DeprecatedFetchCompletions("", 0), nil return option.DeprecatedFetchCompletions("", 0)
} }
// token completions
completions := getTokenCompletions(entry, cursor)
// Hello wo|rld // Hello wo|rld
lineValue := entry.OptionValue.Value.Raw lineValue := entry.OptionValue.Value.Raw
// NEW: docvalues index // NEW: docvalues index
return option.DeprecatedFetchCompletions( completions = append(completions, option.DeprecatedFetchCompletions(
lineValue, lineValue,
common.DeprecatedImprovedCursorToIndex( common.DeprecatedImprovedCursorToIndex(
cursor, cursor,
lineValue, lineValue,
entry.OptionValue.Start.Character, entry.OptionValue.Start.Character,
), ),
), nil )...)
return completions
}
func getTokenCompletions(
entry *ast.SSHOption,
cursor common.CursorPosition,
) []protocol.CompletionItem {
completions := make([]protocol.CompletionItem, 0)
index := common.CursorToCharacterIndex(uint32(cursor))
if entry.Value.Raw[index] == '%' {
if tokens, found := fields.OptionsTokensMap[entry.Key.Key]; found {
for _, token := range tokens {
description := fields.AvailableTokens[token]
kind := protocol.CompletionItemKindConstant
completions = append(completions, protocol.CompletionItem{
Label: token,
Kind: &kind,
Documentation: description,
})
}
}
}
return completions
} }

View File

@ -14,12 +14,12 @@ func getMatchCompletions(
d *sshconfig.SSHDocument, d *sshconfig.SSHDocument,
cursor common.CursorPosition, cursor common.CursorPosition,
match *matchparser.Match, match *matchparser.Match,
) ([]protocol.CompletionItem, error) { ) []protocol.CompletionItem {
if match == nil || len(match.Entries) == 0 { if match == nil || len(match.Entries) == 0 {
completions := getMatchCriteriaCompletions() completions := getMatchCriteriaCompletions()
completions = append(completions, getMatchAllKeywordCompletion()) completions = append(completions, getMatchAllKeywordCompletion())
return completions, nil return completions
} }
entry := match.GetEntryAtPosition(cursor) entry := match.GetEntryAtPosition(cursor)
@ -39,10 +39,10 @@ func getMatchCompletions(
completions = append(completions, getMatchAllKeywordCompletion()) completions = append(completions, getMatchAllKeywordCompletion())
} }
return completions, nil return completions
} }
return getMatchValueCompletions(entry, cursor), nil return getMatchValueCompletions(entry, cursor)
} }
func getMatchCriteriaCompletions() []protocol.CompletionItem { func getMatchCriteriaCompletions() []protocol.CompletionItem {

View File

@ -15,7 +15,7 @@ func getTagCompletions(
line uint32, line uint32,
cursor common.CursorPosition, cursor common.CursorPosition,
entry *ast.SSHOption, entry *ast.SSHOption,
) ([]protocol.CompletionItem, error) { ) []protocol.CompletionItem {
completions := make([]protocol.CompletionItem, 0) completions := make([]protocol.CompletionItem, 0)
for name, info := range d.Indexes.Tags { for name, info := range d.Indexes.Tags {
@ -35,7 +35,7 @@ func getTagCompletions(
}) })
} }
return completions, nil return completions
} }
func renderMatchBlock( func renderMatchBlock(

View File

@ -34,7 +34,7 @@ func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionPa
block, block,
// Empty line, or currently typing a new key // Empty line, or currently typing a new key
option == nil || isEmptyPattern.Match([]byte(option.Value.Raw[cursor:])), option == nil || isEmptyPattern.Match([]byte(option.Value.Raw[cursor:])),
) ), nil
} }
if option.Separator != nil && option.OptionValue.IsPositionAfterStart(cursor) { if option.Separator != nil && option.OptionValue.IsPositionAfterStart(cursor) {
@ -44,7 +44,7 @@ func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionPa
block, block,
line, line,
cursor, cursor,
) ), nil
} }
return nil, nil return nil, nil