feat(sshd_config): Add support for token completions

This commit is contained in:
Myzel394 2024-10-15 16:20:18 +02:00
parent b1498ffdc5
commit ef737e194a
No known key found for this signature in database
GPG Key ID: ED20A1D1D423AF3F
10 changed files with 123 additions and 20 deletions

View File

@ -40,7 +40,7 @@ func checkOption(
checkIsUsingDoubleQuotes(ctx, option.Key.Value, option.Key.LocationRange) checkIsUsingDoubleQuotes(ctx, option.Key.Value, option.Key.LocationRange)
checkQuotesAreClosed(ctx, option.Key.Value, option.Key.LocationRange) checkQuotesAreClosed(ctx, option.Key.Value, option.Key.LocationRange)
key := fields.CreateNormalizedName(option.Key.Key) key := option.Key.Key
docOption, found := fields.Options[key] docOption, found := fields.Options[key]
if !found { if !found {

View File

@ -4,6 +4,7 @@ import (
"config-lsp/common" "config-lsp/common"
commonparser "config-lsp/common/parser" commonparser "config-lsp/common/parser"
"config-lsp/handlers/sshd_config/ast/parser" "config-lsp/handlers/sshd_config/ast/parser"
"config-lsp/handlers/sshd_config/fields"
"config-lsp/handlers/sshd_config/match-parser" "config-lsp/handlers/sshd_config/match-parser"
"strings" "strings"
@ -76,7 +77,7 @@ func (s *sshdParserListener) EnterKey(ctx *parser.KeyContext) {
s.sshdContext.currentOption.Key = &SSHDKey{ s.sshdContext.currentOption.Key = &SSHDKey{
LocationRange: location, LocationRange: location,
Value: value, Value: value,
Key: key, Key: fields.CreateNormalizedName(key),
} }
} }

View File

@ -3,14 +3,16 @@ package ast
import ( import (
"config-lsp/common" "config-lsp/common"
commonparser "config-lsp/common/parser" commonparser "config-lsp/common/parser"
"config-lsp/handlers/sshd_config/fields"
"config-lsp/handlers/sshd_config/match-parser" "config-lsp/handlers/sshd_config/match-parser"
"github.com/emirpasic/gods/maps/treemap" "github.com/emirpasic/gods/maps/treemap"
) )
type SSHDKey struct { type SSHDKey struct {
common.LocationRange common.LocationRange
Value commonparser.ParsedString Value commonparser.ParsedString
Key string Key fields.NormalizedOptionName
} }
type SSHDValue struct { type SSHDValue struct {

View File

@ -0,0 +1,71 @@
package fields
var AvailableTokens = map[string]string{
"%%": "A literal %.",
"%C": "Identifies the connection endpoints, containing four space-separated values: client address, client port number, server address, and server port number.",
"%D": "The routing domain in which the incoming connection was received.",
"%F": "The fingerprint of the CA key.",
"%f": "The fingerprint of the key or certificate.",
"%h": "The home directory of the user.",
"%i": "The key ID in the certificate.",
"%K": "The base64-encoded CA key.",
"%k": "The base64-encoded key or certificate for authentication.",
"%s": "The serial number of the certificate.",
"%T": "The type of the CA key.",
"%t": "The key or certificate type.",
"%U": "The numeric user ID of the target user.",
"%u": "The username.",
}
// A map of <option name> to <list of supported tokens>
// This is derived from the TOKENS section of ssh_config
var OptionsTokensMap = map[NormalizedOptionName][]string{
"authorizedkeyscommand": {
"%%",
"%C",
"%D",
"%f",
"%h",
"%k",
"%t",
"%U",
"%u",
},
"authorizedkeysfile": {
"%%",
"%h",
"%U",
"%u",
},
"authorizedprincipalscommand": {
"%%",
"%C",
"%D",
"%F",
"%f",
"%h",
"%i",
"%K",
"%k",
"%s",
"%T",
"%t",
"%U",
"%u",
},
"authorizedprincipalsfile": {
"%%",
"%h",
"%U",
"%u",
},
"chrootdirectory": {
"%%",
"%h",
"%U",
"%u",
},
"routingdomain": {
"%D",
},
}

View File

@ -69,12 +69,12 @@ func GetOptionCompletions(
entry *ast.SSHDOption, entry *ast.SSHDOption,
matchBlock *ast.SSHDMatchBlock, matchBlock *ast.SSHDMatchBlock,
cursor common.CursorPosition, cursor common.CursorPosition,
) ([]protocol.CompletionItem, error) { ) []protocol.CompletionItem {
key := fields.CreateNormalizedName(entry.Key.Key) key := entry.Key.Key
option, found := fields.Options[key] option, found := fields.Options[key]
if !found { if !found {
return nil, nil return nil
} }
if entry.Key.Key == "Match" { if entry.Key.Key == "Match" {
@ -86,18 +86,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
line := entry.OptionValue.Value.Raw line := entry.OptionValue.Value.Raw
// NEW: docvalues index // NEW: docvalues index
return option.DeprecatedFetchCompletions( completions = append(completions, option.DeprecatedFetchCompletions(
line, line,
common.DeprecatedImprovedCursorToIndex( common.DeprecatedImprovedCursorToIndex(
cursor, cursor,
line, line,
entry.OptionValue.Start.Character, entry.OptionValue.Start.Character,
), ),
), nil )...)
return completions
}
func getTokenCompletions(
entry *ast.SSHDOption,
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

@ -12,21 +12,21 @@ func getMatchCompletions(
d *sshdconfig.SSHDDocument, d *sshdconfig.SSHDDocument,
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)
if entry == nil || entry.Criteria.ContainsPosition(cursor) { if entry == nil || entry.Criteria.ContainsPosition(cursor) {
return getMatchCriteriaCompletions(), nil return getMatchCriteriaCompletions()
} }
return getMatchValueCompletions(entry, cursor), nil return getMatchValueCompletions(entry, cursor)
} }
func getMatchCriteriaCompletions() []protocol.CompletionItem { func getMatchCriteriaCompletions() []protocol.CompletionItem {

View File

@ -30,11 +30,10 @@ func formatSSHDOption(
var key string var key string
if option.Key != nil { if option.Key != nil {
key = option.Key.Key if formattedName, found := fields.FieldsNameFormattedMap[option.Key.Key]; found {
normalizedKey := fields.CreateNormalizedName(key)
if formattedName, found := fields.FieldsNameFormattedMap[normalizedKey]; found {
key = formattedName key = formattedName
} else {
key = string(option.Key.Key)
} }
} else { } else {
key = "" key = ""

View File

@ -17,7 +17,7 @@ func GetHoverInfoForOption(
index common.IndexPosition, index common.IndexPosition,
) (*protocol.Hover, error) { ) (*protocol.Hover, error) {
var docValue *docvalues.DocumentationValue var docValue *docvalues.DocumentationValue
key := fields.CreateNormalizedName(option.Key.Key) key := option.Key.Key
// Either root level or in the line of a match block // Either root level or in the line of a match block
if matchBlock == nil || matchBlock.Start.Line == line { if matchBlock == nil || matchBlock.Start.Line == line {

View File

@ -89,7 +89,7 @@ func addOption(
matchBlock *ast.SSHDMatchBlock, matchBlock *ast.SSHDMatchBlock,
) []common.LSPError { ) []common.LSPError {
var errs []common.LSPError var errs []common.LSPError
key := fields.CreateNormalizedName(option.Key.Key) key := option.Key.Key
if optionsMap, found := i.AllOptionsPerName[key]; found { if optionsMap, found := i.AllOptionsPerName[key]; found {
if options, found := optionsMap[matchBlock]; found { if options, found := optionsMap[matchBlock]; found {

View File

@ -43,7 +43,7 @@ func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionPa
entry, entry,
matchBlock, matchBlock,
cursor, cursor,
) ), nil
} }
return nil, nil return nil, nil