mirror of
https://github.com/Myzel394/config-lsp.git
synced 2025-06-18 23:15:26 +02:00
feat(ssh_config): Overall improvements
This commit is contained in:
parent
6608375b9e
commit
92b2f12e3f
@ -152,6 +152,7 @@ func (s *sshParserListener) ExitEntry(ctx *parser.EntryContext) {
|
||||
|
||||
s.sshContext.currentKeyIsBlockOf = nil
|
||||
s.sshContext.currentBlock = matchBlock
|
||||
|
||||
case SSHBlockTypeHost:
|
||||
var host *hostparser.Host
|
||||
|
||||
|
54
handlers/ssh_config/ast/ssh_cofig_fields_test.go
Normal file
54
handlers/ssh_config/ast/ssh_cofig_fields_test.go
Normal file
@ -0,0 +1,54 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"config-lsp/utils"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestComplexExampleRetrievesCorrectly(
|
||||
t *testing.T,
|
||||
) {
|
||||
input := utils.Dedent(`
|
||||
Port 22
|
||||
|
||||
Host laptop
|
||||
HostName laptop.lan
|
||||
|
||||
Match originalhost laptop exec "[[ $(/usr/bin/dig +short laptop.lan) == '' ]]"
|
||||
HostName laptop.sdn
|
||||
`)
|
||||
|
||||
p := NewSSHConfig()
|
||||
errors := p.Parse(input)
|
||||
|
||||
if len(errors) != 0 {
|
||||
t.Fatalf("Expected no errors, got %v", errors)
|
||||
}
|
||||
|
||||
firstOption, firstBlock := p.FindOption(0)
|
||||
if !(firstOption.Value.Raw == "Port 22") {
|
||||
t.Errorf("Expected Port 22, got %v", firstOption.Value.Raw)
|
||||
}
|
||||
|
||||
if !(firstBlock == nil) {
|
||||
t.Errorf("Expected no block, got %v", firstBlock)
|
||||
}
|
||||
|
||||
secondOption, secondBlock := p.FindOption(3)
|
||||
if !(secondOption.Value.Raw == " HostName laptop.lan") {
|
||||
t.Errorf("Expected HostName laptop.lan, got %v", secondOption.Value.Raw)
|
||||
}
|
||||
|
||||
if !(secondBlock.GetLocation().Start.Line == 2) {
|
||||
t.Errorf("Expected line 2, got %v", secondBlock.GetLocation().Start.Line)
|
||||
}
|
||||
|
||||
thirdOption, thirdBlock := p.FindOption(6)
|
||||
if !(thirdOption.Value.Raw == " HostName laptop.sdn") {
|
||||
t.Errorf("Expected HostName laptop.sdn, got %v", thirdOption.Value.Raw)
|
||||
}
|
||||
|
||||
if !(thirdBlock.GetLocation().Start.Line == 5) {
|
||||
t.Errorf("Expected line 3, got %v", thirdBlock.GetLocation().Start.Line)
|
||||
}
|
||||
}
|
@ -18,6 +18,8 @@ type SSHBlock interface {
|
||||
AddOption(option *SSHOption)
|
||||
SetEnd(common.Location)
|
||||
GetOptions() *treemap.Map
|
||||
GetEntryOption() *SSHOption
|
||||
GetLocation() common.LocationRange
|
||||
}
|
||||
|
||||
func (b *SSHMatchBlock) GetBlockType() SSHBlockType {
|
||||
@ -36,6 +38,14 @@ func (b *SSHMatchBlock) GetOptions() *treemap.Map {
|
||||
return b.Options
|
||||
}
|
||||
|
||||
func (b *SSHMatchBlock) GetEntryOption() *SSHOption {
|
||||
return b.MatchOption
|
||||
}
|
||||
|
||||
func (b *SSHMatchBlock) GetLocation() common.LocationRange {
|
||||
return b.LocationRange
|
||||
}
|
||||
|
||||
func (b *SSHHostBlock) GetBlockType() SSHBlockType {
|
||||
return SSHBlockTypeHost
|
||||
}
|
||||
@ -52,6 +62,14 @@ func (b *SSHHostBlock) GetOptions() *treemap.Map {
|
||||
return b.Options
|
||||
}
|
||||
|
||||
func (b *SSHHostBlock) GetEntryOption() *SSHOption {
|
||||
return b.HostOption
|
||||
}
|
||||
|
||||
func (b *SSHHostBlock) GetLocation() common.LocationRange {
|
||||
return b.LocationRange
|
||||
}
|
||||
|
||||
type SSHType uint8
|
||||
|
||||
const (
|
||||
@ -88,3 +106,94 @@ func (b *SSHHostBlock) GetType() SSHType {
|
||||
func (b *SSHHostBlock) GetOption() *SSHOption {
|
||||
return b.HostOption
|
||||
}
|
||||
|
||||
func (c SSHConfig) FindBlock(line uint32) SSHBlock {
|
||||
it := c.Options.Iterator()
|
||||
for it.Next() {
|
||||
entry := it.Value().(SSHEntry)
|
||||
|
||||
if entry.GetType() == SSHTypeOption {
|
||||
continue
|
||||
}
|
||||
|
||||
block := entry.(SSHBlock)
|
||||
|
||||
if block.GetLocation().Start.Line <= line && block.GetLocation().End.Line >= line {
|
||||
return block
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c SSHConfig) FindOption(line uint32) (*SSHOption, SSHBlock) {
|
||||
block := c.FindBlock(line)
|
||||
|
||||
var option *SSHOption
|
||||
|
||||
if block == nil {
|
||||
if rawOption, found := c.Options.Get(line); found {
|
||||
option = rawOption.(*SSHOption)
|
||||
}
|
||||
} else {
|
||||
if rawOption, found := block.GetOptions().Get(line); found {
|
||||
option = rawOption.(*SSHOption)
|
||||
}
|
||||
}
|
||||
|
||||
return option, block
|
||||
}
|
||||
|
||||
type AllOptionInfo struct {
|
||||
Block SSHBlock
|
||||
Option *SSHOption
|
||||
}
|
||||
|
||||
func (c SSHConfig) GetAllOptions() []AllOptionInfo {
|
||||
options := make([]AllOptionInfo, 0, 50)
|
||||
|
||||
for _, rawEntry := range c.Options.Values() {
|
||||
switch rawEntry.(type) {
|
||||
case *SSHOption:
|
||||
option := rawEntry.(*SSHOption)
|
||||
options = append(options, AllOptionInfo{
|
||||
Block: nil,
|
||||
Option: option,
|
||||
})
|
||||
case *SSHMatchBlock:
|
||||
block := rawEntry.(SSHBlock)
|
||||
|
||||
options = append(options, AllOptionInfo{
|
||||
Block: block,
|
||||
Option: block.GetEntryOption(),
|
||||
})
|
||||
|
||||
for _, rawOption := range block.GetOptions().Values() {
|
||||
option := rawOption.(*SSHOption)
|
||||
options = append(options, AllOptionInfo{
|
||||
Block: nil,
|
||||
Option: option,
|
||||
})
|
||||
}
|
||||
case *SSHHostBlock:
|
||||
block := rawEntry.(SSHBlock)
|
||||
|
||||
options = append(options, AllOptionInfo{
|
||||
Block: block,
|
||||
Option: block.GetEntryOption(),
|
||||
})
|
||||
|
||||
for _, rawOption := range block.GetOptions().Values() {
|
||||
option := rawOption.(*SSHOption)
|
||||
options = append(options, AllOptionInfo{
|
||||
Block: nil,
|
||||
Option: option,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
|
31
handlers/ssh_config/document_fields.go
Normal file
31
handlers/ssh_config/document_fields.go
Normal file
@ -0,0 +1,31 @@
|
||||
package sshconfig
|
||||
|
||||
import "config-lsp/handlers/ssh_config/ast"
|
||||
|
||||
func (d SSHDocument) FindOptionByNameAndBlock(
|
||||
name string,
|
||||
block ast.SSHBlock,
|
||||
) *ast.AllOptionInfo {
|
||||
for _, info := range d.FindOptionsByName(name) {
|
||||
if info.Block == block {
|
||||
return &info
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d SSHDocument) FindOptionsByName(
|
||||
name string,
|
||||
) []ast.AllOptionInfo {
|
||||
options := make([]ast.AllOptionInfo, 0, 5)
|
||||
|
||||
for _, info := range d.Config.GetAllOptions() {
|
||||
if info.Option.Key.Key == name {
|
||||
options = append(options, info)
|
||||
}
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
59
handlers/ssh_config/handlers/completions.go
Normal file
59
handlers/ssh_config/handlers/completions.go
Normal file
@ -0,0 +1,59 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
docvalues "config-lsp/doc-values"
|
||||
sshconfig "config-lsp/handlers/ssh_config"
|
||||
"config-lsp/handlers/ssh_config/ast"
|
||||
"config-lsp/handlers/ssh_config/fields"
|
||||
"config-lsp/utils"
|
||||
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
func GetRootCompletions(
|
||||
d *sshconfig.SSHDocument,
|
||||
parentBlock ast.SSHBlock,
|
||||
suggestValue bool,
|
||||
) ([]protocol.CompletionItem, error) {
|
||||
kind := protocol.CompletionItemKindField
|
||||
|
||||
availableOptions := make(map[string]docvalues.DocumentationValue, 0)
|
||||
|
||||
for key, option := range fields.Options {
|
||||
alreadyExists := d.FindOptionByNameAndBlock(key, parentBlock) != nil
|
||||
|
||||
if !alreadyExists || utils.KeyExists(fields.AllowedDuplicateOptions, key) {
|
||||
availableOptions[key] = option
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all fields that are already present and are not allowed to be duplicated
|
||||
for _, info := range d.Config.GetAllOptions() {
|
||||
if _, found := fields.AllowedDuplicateOptions[info.Option.Key.Key]; found {
|
||||
continue
|
||||
}
|
||||
|
||||
delete(availableOptions, info.Option.Key.Key)
|
||||
}
|
||||
|
||||
return utils.MapMapToSlice(
|
||||
availableOptions,
|
||||
func(name string, doc docvalues.DocumentationValue) protocol.CompletionItem {
|
||||
completion := &protocol.CompletionItem{
|
||||
Label: name,
|
||||
Kind: &kind,
|
||||
Documentation: doc.Documentation,
|
||||
}
|
||||
|
||||
if suggestValue {
|
||||
format := protocol.InsertTextFormatSnippet
|
||||
insertText := name + " " + "${1:value}"
|
||||
|
||||
completion.InsertTextFormat = &format
|
||||
completion.InsertText = &insertText
|
||||
}
|
||||
|
||||
return *completion
|
||||
},
|
||||
), nil
|
||||
}
|
@ -23,12 +23,14 @@ type SSHIndexIncludeValue struct {
|
||||
type SSHIndexIncludeLine struct {
|
||||
Values []*SSHIndexIncludeValue
|
||||
Option *ast.SSHOption
|
||||
Block *ast.SSHBlock
|
||||
Block ast.SSHBlock
|
||||
}
|
||||
|
||||
type SSHIndexes struct {
|
||||
AllOptionsPerName map[string](map[*ast.SSHBlock]([]*ast.SSHOption))
|
||||
AllOptionsPerName map[string](map[ast.SSHBlock]([]*ast.SSHOption))
|
||||
|
||||
Includes []*SSHIndexIncludeLine
|
||||
|
||||
BlockRanges map[uint32]ast.SSHBlock
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@ var whitespacePattern = regexp.MustCompile(`\S+`)
|
||||
|
||||
func NewSSHIndexes() *SSHIndexes {
|
||||
return &SSHIndexes{
|
||||
AllOptionsPerName: make(map[string](map[*ast.SSHBlock]([]*ast.SSHOption)), 0),
|
||||
AllOptionsPerName: make(map[string](map[ast.SSHBlock]([]*ast.SSHOption)), 0),
|
||||
Includes: make([]*SSHIndexIncludeLine, 0),
|
||||
}
|
||||
}
|
||||
@ -34,13 +34,13 @@ func CreateIndexes(config ast.SSHConfig) (*SSHIndexes, []common.LSPError) {
|
||||
case ast.SSHTypeHost:
|
||||
block := entry.(ast.SSHBlock)
|
||||
|
||||
errs = append(errs, addOption(indexes, entry.GetOption(), &block)...)
|
||||
errs = append(errs, addOption(indexes, entry.GetOption(), block)...)
|
||||
|
||||
it := block.GetOptions().Iterator()
|
||||
for it.Next() {
|
||||
option := it.Value().(*ast.SSHOption)
|
||||
|
||||
errs = append(errs, addOption(indexes, option, &block)...)
|
||||
errs = append(errs, addOption(indexes, option, block)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -90,7 +90,7 @@ func CreateIndexes(config ast.SSHConfig) (*SSHIndexes, []common.LSPError) {
|
||||
func addOption(
|
||||
i *SSHIndexes,
|
||||
option *ast.SSHOption,
|
||||
block *ast.SSHBlock,
|
||||
block ast.SSHBlock,
|
||||
) []common.LSPError {
|
||||
var errs []common.LSPError
|
||||
|
||||
@ -118,7 +118,7 @@ func addOption(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
i.AllOptionsPerName[option.Key.Key] = map[*ast.SSHBlock]([]*ast.SSHOption){
|
||||
i.AllOptionsPerName[option.Key.Key] = map[ast.SSHBlock]([]*ast.SSHOption){
|
||||
block: {
|
||||
option,
|
||||
},
|
@ -1,10 +1,50 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"config-lsp/common"
|
||||
sshconfig "config-lsp/handlers/ssh_config"
|
||||
"config-lsp/handlers/ssh_config/handlers"
|
||||
"regexp"
|
||||
|
||||
"github.com/tliron/glsp"
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
|
||||
var isEmptyPattern = regexp.MustCompile(`^\s*$`)
|
||||
|
||||
func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionParams) (any, error) {
|
||||
line := params.Position.Line
|
||||
cursor := common.LSPCharacterAsCursorPosition(params.Position.Character)
|
||||
|
||||
d := sshconfig.DocumentParserMap[params.TextDocument.URI]
|
||||
|
||||
if _, found := d.Config.CommentLines[line]; found {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
option, block := d.Config.FindOption(line)
|
||||
|
||||
if option == nil ||
|
||||
option.Separator == nil ||
|
||||
option.Key == nil ||
|
||||
option.Key.IsPositionBeforeEnd(cursor) {
|
||||
|
||||
return handlers.GetRootCompletions(
|
||||
d,
|
||||
block,
|
||||
// Empty line, or currently typing a new key
|
||||
option == nil || isEmptyPattern.Match([]byte(option.Value.Raw[cursor:])),
|
||||
)
|
||||
}
|
||||
|
||||
// if option.Separator != nil && option.OptionValue.IsPositionAfterStart(cursor) {
|
||||
// return handlers.GetOptionCompletions(
|
||||
// d,
|
||||
// entry,
|
||||
// matchBlock,
|
||||
// cursor,
|
||||
// )
|
||||
// }
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
package sshconfig
|
||||
|
||||
import (
|
||||
"config-lsp/handlers/ssh_config/ast"
|
||||
"config-lsp/handlers/ssh_config/indexes"
|
||||
"config-lsp/handlers/ssh_config/ast"
|
||||
|
||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||
)
|
||||
@ -13,3 +13,4 @@ type SSHDocument struct {
|
||||
}
|
||||
|
||||
var DocumentParserMap = map[protocol.DocumentUri]*SSHDocument{}
|
||||
|
||||
|
@ -62,7 +62,6 @@ func (c SSHDConfig) FindOption(line uint32) (*SSHDOption, *SSHDMatchBlock) {
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
|
||||
}
|
||||
|
||||
func (c SSHDConfig) GetAllOptions() []*SSHDOption {
|
||||
|
@ -20,23 +20,17 @@ func GetRootCompletions(
|
||||
|
||||
availableOptions := make(map[string]docvalues.DocumentationValue, 0)
|
||||
|
||||
if parentMatchBlock == nil {
|
||||
for key, option := range fields.Options {
|
||||
if d.Indexes != nil && utils.KeyExists(d.Indexes.AllOptionsPerName, key) && !utils.KeyExists(fields.AllowedDuplicateOptions, key) {
|
||||
continue
|
||||
}
|
||||
for key, option := range fields.Options {
|
||||
var exists = false
|
||||
|
||||
availableOptions[key] = option
|
||||
if optionsMap, found := d.Indexes.AllOptionsPerName[key]; found {
|
||||
if _, found := optionsMap[parentMatchBlock]; found {
|
||||
exists = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for key := range fields.MatchAllowedOptions {
|
||||
if option, found := fields.Options[key]; found {
|
||||
if d.Indexes != nil && utils.KeyExists(d.Indexes.AllOptionsPerName, key) && !utils.KeyExists(fields.AllowedDuplicateOptions, key) {
|
||||
continue
|
||||
}
|
||||
|
||||
availableOptions[key] = option
|
||||
}
|
||||
if !exists || utils.KeyExists(fields.AllowedDuplicateOptions, key) {
|
||||
availableOptions[key] = option
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user