From 3afcf4c27b6d62d7e610d29a3ea8b68874a21eba Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sun, 29 Sep 2024 17:44:33 +0200 Subject: [PATCH] feat(ssh_config): Add completions for match option --- handlers/ssh_config/ast/parser_test.go | 66 +++++++++++++++++++ handlers/ssh_config/ast/ssh_config_fields.go | 4 ++ handlers/ssh_config/fields/match.go | 27 ++++++++ handlers/ssh_config/handlers/completions.go | 12 ++-- .../ssh_config/handlers/completions_match.go | 27 ++++---- 5 files changed, 117 insertions(+), 19 deletions(-) diff --git a/handlers/ssh_config/ast/parser_test.go b/handlers/ssh_config/ast/parser_test.go index 1462fa7..6bda496 100644 --- a/handlers/ssh_config/ast/parser_test.go +++ b/handlers/ssh_config/ast/parser_test.go @@ -257,6 +257,72 @@ Match originalhost laptop exec "[[ $(/usr/bin/dig +short laptop.lan) == '' ]]" } } +func TestIncompleteExample( + t *testing.T, +) { + input := utils.Dedent(` +User +`) + p := NewSSHConfig() + + errors := p.Parse(input) + + if len(errors) > 0 { + t.Fatalf("Expected no errors, got %v", errors) + } + + if !(p.Options.Size() == 1) { + t.Errorf("Expected 1 option, but got: %v", p.Options.Size()) + } + + if !(len(utils.KeysOfMap(p.CommentLines)) == 0) { + t.Errorf("Expected no comment lines, but got: %v", len(p.CommentLines)) + } + + rawFirstEntry, _ := p.Options.Get(uint32(0)) + firstEntry := rawFirstEntry.(*SSHOption) + if !(firstEntry.Value.Raw == "User " && firstEntry.Key.Value.Raw == "User") { + t.Errorf("Expected first entry to be User, but got: %v", firstEntry) + } + + if !(firstEntry.OptionValue != nil && firstEntry.OptionValue.Value.Raw == "") { + t.Errorf("Expected first entry to have an empty value, but got: %v", firstEntry) + } +} + +func TestIncompleteMatch( + t *testing.T, +) { + input := utils.Dedent(` +Match +`) + p := NewSSHConfig() + + errors := p.Parse(input) + + if len(errors) > 0 { + t.Fatalf("Expected no errors, got %v", errors) + } + + if !(p.Options.Size() == 1) { + t.Errorf("Expected 1 option, but got: %v", p.Options.Size()) + } + + if !(len(utils.KeysOfMap(p.CommentLines)) == 0) { + t.Errorf("Expected no comment lines, but got: %v", len(p.CommentLines)) + } + + rawFirstEntry, _ := p.Options.Get(uint32(0)) + firstEntry := rawFirstEntry.(*SSHMatchBlock) + if !(firstEntry.MatchOption.Key.Value.Raw == "Match") { + t.Errorf("Expected first entry to be User, but got: %v", firstEntry) + } + + if !(firstEntry.MatchOption.OptionValue != nil && firstEntry.MatchOption.OptionValue.Value.Raw == "") { + t.Errorf("Expected first entry to have an empty value, but got: %v", firstEntry) + } +} + func TestComplexBigExample( t *testing.T, ) { diff --git a/handlers/ssh_config/ast/ssh_config_fields.go b/handlers/ssh_config/ast/ssh_config_fields.go index dd5c5bc..b5544ae 100644 --- a/handlers/ssh_config/ast/ssh_config_fields.go +++ b/handlers/ssh_config/ast/ssh_config_fields.go @@ -147,6 +147,10 @@ func (c SSHConfig) FindOption(line uint32) (*SSHOption, SSHBlock) { option = rawOption.(*SSHOption) } } else { + if line == block.GetLocation().Start.Line { + return block.GetEntryOption(), block + } + if rawOption, found := block.GetOptions().Get(line); found { option = rawOption.(*SSHOption) } diff --git a/handlers/ssh_config/fields/match.go b/handlers/ssh_config/fields/match.go index 95f5f5f..abcb7e2 100644 --- a/handlers/ssh_config/fields/match.go +++ b/handlers/ssh_config/fields/match.go @@ -1 +1,28 @@ package fields + +import ( + docvalues "config-lsp/doc-values" + matchparser "config-lsp/handlers/ssh_config/match-parser" +) + +var MatchExecField = docvalues.StringValue{} +var MatchLocalNetworkField = docvalues.IPAddressValue{ + AllowIPv4: true, + AllowIPv6: true, + AllowRange: false, +} +var MatchHostField = docvalues.StringValue{} +var MatchOriginalHostField = docvalues.StringValue{} +var MatchTypeTaggedField = docvalues.StringValue{} +var MatchUserField = docvalues.UserValue("", false) +var MatchTypeLocalUserField = docvalues.UserValue("", false) + +var MatchValueFieldMap = map[matchparser.MatchCriteriaType]docvalues.DeprecatedValue{ + matchparser.MatchCriteriaTypeExec: MatchExecField, + matchparser.MatchCriteriaTypeLocalNetwork: MatchLocalNetworkField, + matchparser.MatchCriteriaTypeHost: MatchHostField, + matchparser.MatchCriteriaTypeOriginalHost: MatchOriginalHostField, + matchparser.MatchCriteriaTypeTagged: MatchTypeTaggedField, + matchparser.MatchCriteriaTypeUser: MatchUserField, + matchparser.MatchCriteriaTypeLocalUser: MatchTypeLocalUserField, +} diff --git a/handlers/ssh_config/handlers/completions.go b/handlers/ssh_config/handlers/completions.go index bcf9e7e..a068bd9 100644 --- a/handlers/ssh_config/handlers/completions.go +++ b/handlers/ssh_config/handlers/completions.go @@ -72,12 +72,12 @@ func GetOptionCompletions( } if entry.Key.Key == "Match" { - return nil, nil - // return getMatchCompletions( - // d, - // cursor, - // matchBlock.MatchValue, - // ) + matchBlock := block.(*ast.SSHMatchBlock) + return getMatchCompletions( + d, + cursor, + matchBlock.MatchValue, + ) } if entry.OptionValue == nil { diff --git a/handlers/ssh_config/handlers/completions_match.go b/handlers/ssh_config/handlers/completions_match.go index b4f701f..372f38c 100644 --- a/handlers/ssh_config/handlers/completions_match.go +++ b/handlers/ssh_config/handlers/completions_match.go @@ -3,8 +3,9 @@ package handlers import ( "config-lsp/common" sshconfig "config-lsp/handlers/ssh_config" + "config-lsp/handlers/ssh_config/fields" matchparser "config-lsp/handlers/ssh_config/match-parser" - "config-lsp/handlers/sshd_config/fields" + protocol "github.com/tliron/glsp/protocol_3_16" ) @@ -103,20 +104,20 @@ func getMatchValueCompletions( } switch entry.Criteria.Type { - case matchparser.MatchCriteriaTypeUser: - return fields.MatchUserField.DeprecatedFetchCompletions(line, relativeCursor) - case matchparser.MatchCriteriaTypeGroup: - return fields.MatchGroupField.DeprecatedFetchCompletions(line, relativeCursor) + case matchparser.MatchCriteriaTypeExec: + return fields.MatchExecField.DeprecatedFetchCompletions(line, relativeCursor) + case matchparser.MatchCriteriaTypeLocalNetwork: + return fields.MatchLocalNetworkField.DeprecatedFetchCompletions(line, relativeCursor) case matchparser.MatchCriteriaTypeHost: return fields.MatchHostField.DeprecatedFetchCompletions(line, relativeCursor) - case matchparser.MatchCriteriaTypeAddress: - return fields.MatchAddressField.DeprecatedFetchCompletions(line, relativeCursor) - case matchparser.MatchCriteriaTypeLocalAddress: - return fields.MatchLocalAddressField.DeprecatedFetchCompletions(line, relativeCursor) - case matchparser.MatchCriteriaTypeLocalPort: - return fields.MatchLocalPortField.DeprecatedFetchCompletions(line, relativeCursor) - case matchparser.MatchCriteriaTypeRDomain: - return fields.MatchRDomainField.DeprecatedFetchCompletions(line, relativeCursor) + case matchparser.MatchCriteriaTypeOriginalHost: + return fields.MatchOriginalHostField.DeprecatedFetchCompletions(line, relativeCursor) + case matchparser.MatchCriteriaTypeTagged: + return fields.MatchTypeTaggedField.DeprecatedFetchCompletions(line, relativeCursor) + case matchparser.MatchCriteriaTypeUser: + return fields.MatchUserField.DeprecatedFetchCompletions(line, relativeCursor) + case matchparser.MatchCriteriaTypeLocalUser: + return fields.MatchTypeLocalUserField.DeprecatedFetchCompletions(line, relativeCursor) } return nil