mirror of
https://github.com/Myzel394/config-lsp.git
synced 2025-06-18 15:05:28 +02:00
feat: Add KeyValueAssignmentValue
This commit is contained in:
parent
014d9ebe94
commit
d5cb9c53e9
@ -266,6 +266,42 @@ func (v PathValue) CheckIsValid(value string) error {
|
|||||||
return PathInvalidError{}
|
return PathInvalidError{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type KeyValueAssignmentValue struct {
|
||||||
|
Key Value
|
||||||
|
Value Value
|
||||||
|
Separator string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v KeyValueAssignmentValue) GetTypeDescription() []string {
|
||||||
|
return []string{
|
||||||
|
fmt.Sprintf("Key-Value pair in form of 'key%svalue'", v.Separator),
|
||||||
|
fmt.Sprintf("#### Key\n%s", strings.Join(v.Key.GetTypeDescription(), "\n")),
|
||||||
|
fmt.Sprintf("#### Value:\n%s", strings.Join(v.Value.GetTypeDescription(), "\n")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (v KeyValueAssignmentValue) CheckIsValid(value string) error {
|
||||||
|
parts := strings.Split(value, v.Separator)
|
||||||
|
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return KeyValueAssignmentError{}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := v.Key.CheckIsValid(parts[0])
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = v.Value.CheckIsValid(parts[1])
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
type Option struct {
|
type Option struct {
|
||||||
Documentation string
|
Documentation string
|
||||||
Value Value
|
Value Value
|
||||||
|
@ -136,6 +136,12 @@ func (e PathDoesNotExistError) Error() string {
|
|||||||
return "This path does not exist"
|
return "This path does not exist"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type KeyValueAssignmentError struct{}
|
||||||
|
|
||||||
|
func (e KeyValueAssignmentError) Error() string {
|
||||||
|
return "This is not valid key-value assignment"
|
||||||
|
}
|
||||||
|
|
||||||
type PathInvalidError struct{}
|
type PathInvalidError struct{}
|
||||||
|
|
||||||
func (e PathInvalidError) Error() string {
|
func (e PathInvalidError) Error() string {
|
||||||
|
@ -100,3 +100,21 @@ func IsPathFile(path string) bool {
|
|||||||
|
|
||||||
return err == nil && !info.IsDir()
|
return err == nil && !info.IsDir()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func OffsetLineAtLeft(offset uint32, line string, cursor uint32) (string, uint32) {
|
||||||
|
if offset >= uint32(len(line)) {
|
||||||
|
return line, cursor
|
||||||
|
}
|
||||||
|
|
||||||
|
return line[offset:], cursor - offset
|
||||||
|
}
|
||||||
|
|
||||||
|
func FindPreviousCharacter(line string, character string) (uint32, bool) {
|
||||||
|
for index := len(line) - 1; index >= 0; index-- {
|
||||||
|
if string(line[index]) == character {
|
||||||
|
return uint32(index), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
@ -205,21 +205,41 @@ See PATTERNS in ssh_config(5) for more information on patterns. This keyword may
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
// "ChannelTimeout": `Specifies whether and how quickly sshd(8) should close inactive channels. Timeouts are specified as one or more “type=interval” pairs separated by whitespace, where the “type” must be the special keyword “global” or a channel type name from the list below, optionally containing wildcard characters.
|
"ChannelTimeout": common.NewOption(`Specifies whether and how quickly sshd(8) should close inactive channels. Timeouts are specified as one or more “type=interval” pairs separated by whitespace, where the “type” must be the special keyword “global” or a channel type name from the list below, optionally containing wildcard characters.
|
||||||
// The timeout value “interval” is specified in seconds or may use any of the units documented in the “TIME FORMATS” section. For example, “session=5m” would cause interactive sessions to terminate after five minutes of inactivity. Specifying a zero value disables the inactivity timeout.
|
The timeout value “interval” is specified in seconds or may use any of the units documented in the “TIME FORMATS” section. For example, “session=5m” would cause interactive sessions to terminate after five minutes of inactivity. Specifying a zero value disables the inactivity timeout.
|
||||||
// The special timeout “global” applies to all active channels, taken together. Traffic on any active channel will reset the timeout, but when the timeout expires then all open channels will be closed. Note that this global timeout is not matched by wildcards and must be specified explicitly.
|
The special timeout “global” applies to all active channels, taken together. Traffic on any active channel will reset the timeout, but when the timeout expires then all open channels will be closed. Note that this global timeout is not matched by wildcards and must be specified explicitly.
|
||||||
// The available channel type names include:
|
The available channel type names include:
|
||||||
// agent-connection Open connections to ssh-agent(1).
|
|
||||||
// direct-tcpip, direct-streamlocal@openssh.com Open TCP or Unix socket (respectively) connections that have been established from a ssh(1) local forwarding, i.e. LocalForward or DynamicForward.
|
agent-connection Open connections to ssh-agent(1).
|
||||||
// forwarded-tcpip, forwarded-streamlocal@openssh.com Open TCP or Unix socket (respectively) connections that have been established to a sshd(8) listening on behalf of a ssh(1) remote forwarding, i.e. RemoteForward.
|
direct-tcpip, direct-streamlocal@openssh.com Open TCP or Unix socket (respectively) connections that have been established from a ssh(1) local forwarding, i.e. LocalForward or DynamicForward.
|
||||||
//
|
forwarded-tcpip, forwarded-streamlocal@openssh.com Open TCP or Unix socket (respectively) connections that have been established to a sshd(8) listening on behalf of a ssh(1) remote forwarding, i.e. RemoteForward.
|
||||||
// `,
|
session The interactive main session, including shell session, command execution, scp(1), sftp(1), etc.
|
||||||
// "session": `The interactive main session, including shell session, command execution, scp(1), sftp(1), etc.
|
tun-connection Open TunnelForward connections.
|
||||||
// tun-connection Open TunnelForward connections.
|
x11-connection Open X11 forwarding sessions.
|
||||||
// x11-connection Open X11 forwarding sessions.
|
|
||||||
// Note that in all the above cases, terminating an inactive session does not guarantee to remove all resources associated with the session, e.g. shell processes or X11 clients relating to the session may continue to execute.
|
Note that in all the above cases, terminating an inactive session does not guarantee to remove all resources associated with the session, e.g. shell processes or X11 clients relating to the session may continue to execute.
|
||||||
// Moreover, terminating an inactive channel or session does not necessarily close the SSH connection, nor does it prevent a client from requesting another channel of the same type. In particular, expiring an inactive forwarding session does not prevent another identical forwarding from being subsequently created.
|
Moreover, terminating an inactive channel or session does not necessarily close the SSH connection, nor does it prevent a client from requesting another channel of the same type. In particular, expiring an inactive forwarding session does not prevent another identical forwarding from being subsequently created.
|
||||||
// The default is not to expire channels of any type for inactivity.`,
|
The default is not to expire channels of any type for inactivity.`,
|
||||||
|
common.ArrayValue{
|
||||||
|
Separator: " ",
|
||||||
|
AllowDuplicates: false,
|
||||||
|
SubValue: common.KeyValueAssignmentValue{
|
||||||
|
Separator: "=",
|
||||||
|
Key: common.EnumValue{
|
||||||
|
Values: []string{
|
||||||
|
"global",
|
||||||
|
"agent-connection",
|
||||||
|
"direct-tcpip", "direct-streamlocal@openssh.com",
|
||||||
|
"forwarded-tcpip", "forwarded-streamlocal@openssh.com",
|
||||||
|
"session",
|
||||||
|
"tun-connection",
|
||||||
|
"x11-connection",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Value: common.StringValue{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
"ChrootDirectory": common.NewOption(`Specifies the pathname of a directory to chroot(2) to after authentication. At session startup sshd(8) checks that all components of the pathname are root-owned directories which are not writable by group or others. After the chroot, sshd(8) changes the working directory to the user's home directory. Arguments to ChrootDirectory accept the tokens described in the “TOKENS” section.
|
"ChrootDirectory": common.NewOption(`Specifies the pathname of a directory to chroot(2) to after authentication. At session startup sshd(8) checks that all components of the pathname are root-owned directories which are not writable by group or others. After the chroot, sshd(8) changes the working directory to the user's home directory. Arguments to ChrootDirectory accept the tokens described in the “TOKENS” section.
|
||||||
The ChrootDirectory must contain the necessary files and directories to support the user's session. For an interactive session this requires at least a shell, typically sh(1), and basic /dev nodes such as null(4), zero(4), stdin(4), stdout(4), stderr(4), and tty(4) devices. For file transfer sessions using SFTP no additional configuration of the environment is necessary if the in-process sftp-server is used, though sessions which use logging may require /dev/log inside the chroot directory on some operating systems (see sftp-server(8) for details).
|
The ChrootDirectory must contain the necessary files and directories to support the user's session. For an interactive session this requires at least a shell, typically sh(1), and basic /dev nodes such as null(4), zero(4), stdin(4), stdout(4), stderr(4), and tty(4) devices. For file transfer sessions using SFTP no additional configuration of the environment is necessary if the in-process sftp-server is used, though sessions which use logging may require /dev/log inside the chroot directory on some operating systems (see sftp-server(8) for details).
|
||||||
For safety, it is very important that the directory hierarchy be prevented from modification by other processes on the system (especially those outside the jail). Misconfiguration can lead to unsafe environments which sshd(8) cannot detect.
|
For safety, it is very important that the directory hierarchy be prevented from modification by other processes on the system (especially those outside the jail). Misconfiguration can lead to unsafe environments which sshd(8) cannot detect.
|
||||||
|
@ -3,6 +3,7 @@ package openssh
|
|||||||
import (
|
import (
|
||||||
"config-lsp/common"
|
"config-lsp/common"
|
||||||
"errors"
|
"errors"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/tliron/glsp"
|
"github.com/tliron/glsp"
|
||||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
@ -12,13 +13,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionParams) (interface{}, error) {
|
func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionParams) (interface{}, error) {
|
||||||
option, line, err := Parser.FindByLineNumber(uint32(params.Position.Line))
|
optionName, line, err := Parser.FindByLineNumber(uint32(params.Position.Line))
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if line.IsCursorAtRootOption(int(params.Position.Character)) {
|
if line.IsCursorAtRootOption(int(params.Position.Character)) {
|
||||||
return getRootCompletions(), nil
|
return getRootCompletions(), nil
|
||||||
} else {
|
} else {
|
||||||
return getOptionCompletions(option), nil
|
rawLine, cursor := common.OffsetLineAtLeft(
|
||||||
|
uint32(utf8.RuneCountInString(optionName + " ")),
|
||||||
|
line.Value,
|
||||||
|
params.Position.Character,
|
||||||
|
)
|
||||||
|
|
||||||
|
return getOptionCompletions(optionName, rawLine, cursor), nil
|
||||||
}
|
}
|
||||||
} else if errors.Is(err, common.LineNotFoundError{}) {
|
} else if errors.Is(err, common.LineNotFoundError{}) {
|
||||||
return getRootCompletions(), nil
|
return getRootCompletions(), nil
|
||||||
@ -52,11 +59,11 @@ func getRootCompletions() []protocol.CompletionItem {
|
|||||||
return completions
|
return completions
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCompletionsFromValue(value common.Value) []protocol.CompletionItem {
|
func getCompletionsFromValue(requiredValue common.Value, line string, cursor uint32) []protocol.CompletionItem {
|
||||||
switch value.(type) {
|
switch requiredValue.(type) {
|
||||||
case common.EnumValue:
|
case common.EnumValue:
|
||||||
enumValue := value.(common.EnumValue)
|
enumValue := requiredValue.(common.EnumValue)
|
||||||
completions := make([]protocol.CompletionItem, len(value.(common.EnumValue).Values))
|
completions := make([]protocol.CompletionItem, len(requiredValue.(common.EnumValue).Values))
|
||||||
|
|
||||||
for index, value := range enumValue.Values {
|
for index, value := range enumValue.Values {
|
||||||
textFormat := protocol.InsertTextFormatPlainText
|
textFormat := protocol.InsertTextFormatPlainText
|
||||||
@ -71,37 +78,54 @@ func getCompletionsFromValue(value common.Value) []protocol.CompletionItem {
|
|||||||
|
|
||||||
return completions
|
return completions
|
||||||
case common.CustomValue:
|
case common.CustomValue:
|
||||||
customValue := value.(common.CustomValue)
|
customValue := requiredValue.(common.CustomValue)
|
||||||
val := customValue.FetchValue()
|
val := customValue.FetchValue()
|
||||||
|
|
||||||
return getCompletionsFromValue(val)
|
return getCompletionsFromValue(val, line, cursor)
|
||||||
case common.ArrayValue:
|
case common.ArrayValue:
|
||||||
arrayValue := value.(common.ArrayValue)
|
arrayValue := requiredValue.(common.ArrayValue)
|
||||||
|
relativePosition, found := common.FindPreviousCharacter(line, arrayValue.Separator)
|
||||||
|
|
||||||
return getCompletionsFromValue(arrayValue.SubValue)
|
if found {
|
||||||
|
line, cursor = common.OffsetLineAtLeft(relativePosition, line, cursor)
|
||||||
|
}
|
||||||
|
|
||||||
|
return getCompletionsFromValue(arrayValue.SubValue, line, cursor)
|
||||||
case common.OrValue:
|
case common.OrValue:
|
||||||
orValue := value.(common.OrValue)
|
orValue := requiredValue.(common.OrValue)
|
||||||
|
|
||||||
completions := make([]protocol.CompletionItem, 0)
|
completions := make([]protocol.CompletionItem, 0)
|
||||||
|
|
||||||
for _, subValue := range orValue.Values {
|
for _, subValue := range orValue.Values {
|
||||||
completions = append(completions, getCompletionsFromValue(subValue)...)
|
completions = append(completions, getCompletionsFromValue(subValue, line, cursor)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return completions
|
return completions
|
||||||
case common.PrefixWithMeaningValue:
|
case common.PrefixWithMeaningValue:
|
||||||
prefixWithMeaningValue := value.(common.PrefixWithMeaningValue)
|
prefixWithMeaningValue := requiredValue.(common.PrefixWithMeaningValue)
|
||||||
|
|
||||||
return getCompletionsFromValue(prefixWithMeaningValue.SubValue)
|
return getCompletionsFromValue(prefixWithMeaningValue.SubValue, line, cursor)
|
||||||
|
case common.KeyValueAssignmentValue:
|
||||||
|
keyValueAssignmentValue := requiredValue.(common.KeyValueAssignmentValue)
|
||||||
|
|
||||||
|
relativePosition, found := common.FindPreviousCharacter(line, keyValueAssignmentValue.Separator)
|
||||||
|
|
||||||
|
if found {
|
||||||
|
line, cursor = common.OffsetLineAtLeft(relativePosition, line, cursor)
|
||||||
|
|
||||||
|
return getCompletionsFromValue(keyValueAssignmentValue.Value, line, cursor)
|
||||||
|
} else {
|
||||||
|
return getCompletionsFromValue(keyValueAssignmentValue.Key, line, cursor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return []protocol.CompletionItem{}
|
return []protocol.CompletionItem{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getOptionCompletions(optionName string) []protocol.CompletionItem {
|
func getOptionCompletions(optionName string, line string, cursor uint32) []protocol.CompletionItem {
|
||||||
option := Options[optionName]
|
option := Options[optionName]
|
||||||
|
|
||||||
completions := getCompletionsFromValue(option.Value)
|
completions := getCompletionsFromValue(option.Value, line, cursor)
|
||||||
|
|
||||||
return completions
|
return completions
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user