feat(ssh_config): Add rename support; Improvements

This commit is contained in:
Myzel394 2024-10-03 16:28:19 +02:00
parent 413d919719
commit 15b732e136
No known key found for this signature in database
GPG Key ID: DEC4AAB876F73185
11 changed files with 218 additions and 17 deletions

View File

@ -5,6 +5,7 @@ import (
"config-lsp/common/formatting"
sshconfig "config-lsp/handlers/ssh_config"
"config-lsp/handlers/ssh_config/ast"
"config-lsp/handlers/ssh_config/indexes"
"config-lsp/utils"
"fmt"
@ -18,9 +19,9 @@ func getTagCompletions(
) ([]protocol.CompletionItem, error) {
return utils.MapMapToSlice(
d.Indexes.Tags,
func(name string, block *ast.SSHMatchBlock) protocol.CompletionItem {
func(name string, info indexes.SSHIndexTagInfo) protocol.CompletionItem {
kind := protocol.CompletionItemKindModule
text := renderMatchBlock(block)
text := renderMatchBlock(info.Block)
return protocol.CompletionItem{
Label: name,
Kind: &kind,

View File

@ -0,0 +1,42 @@
package handlers
import (
sshconfig "config-lsp/handlers/ssh_config"
"errors"
protocol "github.com/tliron/glsp/protocol_3_16"
)
func RenameTag(
params *protocol.RenameParams,
d *sshconfig.SSHDocument,
oldName string,
newName string,
) (*protocol.WorkspaceEdit, error) {
changes := make([]protocol.TextEdit, 0)
// tag rename
info, found := d.Indexes.Tags[oldName]
if !found {
return nil, errors.New("Tag could not be found")
}
changes = append(changes, protocol.TextEdit{
Range: info.EntryValue.ToLSPRange(),
NewText: newName,
})
// Rename all occurrences
for _, option := range d.Indexes.TagImports[oldName] {
changes = append(changes, protocol.TextEdit{
Range: option.OptionValue.ToLSPRange(),
NewText: newName,
})
}
return &protocol.WorkspaceEdit{
Changes: map[protocol.DocumentUri][]protocol.TextEdit{
params.TextDocument.URI: changes,
},
}, nil
}

View File

@ -4,6 +4,7 @@ import (
"config-lsp/common"
"config-lsp/handlers/ssh_config/ast"
"config-lsp/handlers/ssh_config/fields"
matchparser "config-lsp/handlers/ssh_config/match-parser"
)
type ValidPath string
@ -35,6 +36,11 @@ type SSHIndexIgnoredUnknowns struct {
IgnoredOptions map[fields.NormalizedOptionName]SSHIndexIgnoredUnknownInfo
}
type SSHIndexTagInfo struct {
EntryValue *matchparser.MatchValue
Block *ast.SSHMatchBlock
}
type SSHIndexes struct {
AllOptionsPerName map[fields.NormalizedOptionName](map[ast.SSHBlock]([]*ast.SSHOption))
@ -51,5 +57,6 @@ type SSHIndexes struct {
// This is a map of <line> to <option>
UnknownOptions map[uint32]ast.AllOptionInfo
Tags map[string]*ast.SSHMatchBlock
Tags map[string]SSHIndexTagInfo
TagImports map[string](map[ast.SSHBlock]*ast.SSHOption)
}

View File

@ -18,12 +18,14 @@ func NewSSHIndexes() *SSHIndexes {
Includes: make([]*SSHIndexIncludeLine, 0),
IgnoredOptions: make(map[ast.SSHBlock]SSHIndexIgnoredUnknowns),
UnknownOptions: make(map[uint32]ast.AllOptionInfo),
Tags: make(map[string]*ast.SSHMatchBlock),
Tags: make(map[string]SSHIndexTagInfo),
TagImports: make(map[string](map[ast.SSHBlock]*ast.SSHOption)),
}
}
var includeOption = fields.CreateNormalizedName("Include")
var matchOption = fields.CreateNormalizedName("Match")
var tagOption = fields.CreateNormalizedName("Tag")
func CreateIndexes(config ast.SSHConfig) (*SSHIndexes, []common.LSPError) {
errs := make([]common.LSPError, 0)
@ -106,14 +108,45 @@ func CreateIndexes(config ast.SSHConfig) (*SSHIndexes, []common.LSPError) {
// Tag already exists
errs = append(errs, common.LSPError{
Range: entry.LocationRange,
Err: fmt.Errorf("Tag %s has already been defined on line %d", name, existingBlock.Start.Line+1),
Err: fmt.Errorf("Tag %s has already been defined on line %d", name, existingBlock.Block.Start.Line+1),
})
continue
}
// Add tag
indexes.Tags[name] = block
indexes.Tags[name] = SSHIndexTagInfo{
EntryValue: value,
Block: block,
}
}
}
}
// Add Tag imports
for block, options := range indexes.AllOptionsPerName[tagOption] {
for _, option := range options {
if option.OptionValue == nil || option.OptionValue.Value.Value == "" {
continue
}
tagName := option.OptionValue.Value.Value
if tagImport, found := indexes.TagImports[tagName]; found {
if definedOption, found := tagImport[block]; found {
errs = append(errs, common.LSPError{
Range: option.OptionValue.LocationRange,
Err: fmt.Errorf("Tag %s has already been imported on line %d", tagName, definedOption.Start.Line+1),
})
continue
} else {
tagImport[block] = option
}
} else {
indexes.TagImports[tagName] = map[ast.SSHBlock]*ast.SSHOption{
block: option,
}
}
}
}

View File

@ -3,7 +3,6 @@ package indexes
import (
"config-lsp/handlers/ssh_config/ast"
"config-lsp/utils"
"fmt"
"testing"
)
@ -128,6 +127,7 @@ Match tagged good_ip
Match tagged myuser
User root
Tag good_ip
`)
config := ast.NewSSHConfig()
@ -150,14 +150,26 @@ Match tagged myuser
rawFirstMatch, _ := config.Options.Get(uint32(0))
firstMatch := rawFirstMatch.(*ast.SSHMatchBlock)
println(fmt.Sprintf("%v", indexes.Tags["good_ip"]))
if !(indexes.Tags["good_ip"].Start.Line == firstMatch.Start.Line) {
if !(indexes.Tags["good_ip"].Block.Start.Line == firstMatch.Start.Line) {
t.Errorf("Expected first tag to be 'good_ip', but got %v", indexes.Tags)
}
rawSecondMatch, _ := config.Options.Get(uint32(3))
secondMatch := rawSecondMatch.(*ast.SSHMatchBlock)
if !(indexes.Tags["myuser"].Start.Line == secondMatch.Start.Line) {
_, secondBlock := config.FindOption(uint32(3))
secondMatch := secondBlock.(*ast.SSHMatchBlock)
if !(indexes.Tags["myuser"].Block.Start.Line == secondMatch.Start.Line) {
t.Errorf("Expected second tag to be 'myuser', but got %v", indexes.Tags)
}
if !(len(indexes.TagImports) == 1) {
t.Errorf("Expected 1 tag import, but got %v", indexes.TagImports)
}
if !(len(indexes.TagImports["good_ip"]) == 1) {
t.Errorf("Expected 1 tag import for 'good_ip', but got %v", indexes.TagImports["good_ip"])
}
tagOption, _ := config.FindOption(uint32(5))
if !(indexes.TagImports["good_ip"][secondBlock].Start.Line == tagOption.Start.Line) {
t.Errorf("Expected first tag import to be 'good_ip', but got %v", indexes.TagImports)
}
}

View File

@ -9,7 +9,6 @@ import (
)
func TextDocumentCodeAction(context *glsp.Context, params *protocol.CodeActionParams) ([]protocol.CodeAction, error) {
println("TextDocumentCodeAction sshconfig")
d := sshconfig.DocumentParserMap[params.TextDocument.URI]
actions := handlers.FetchCodeActions(d, params)

View File

@ -18,11 +18,11 @@ func TextDocumentDefinition(context *glsp.Context, params *protocol.DefinitionPa
option, _ := d.Config.FindOption(line)
if option != nil && option.Key.Key == tagOption && option.OptionValue != nil {
if block, found := d.Indexes.Tags[option.OptionValue.Value.Value]; found {
if info, found := d.Indexes.Tags[option.OptionValue.Value.Value]; found {
return []protocol.Location{
{
URI: params.TextDocument.URI,
Range: block.ToLSPRange(),
Range: info.Block.ToLSPRange(),
},
}, nil
}

View File

@ -0,0 +1,45 @@
package lsp
import (
"config-lsp/common"
sshconfig "config-lsp/handlers/ssh_config"
"config-lsp/handlers/ssh_config/ast"
"github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16"
)
func TextDocumentPrepareRename(context *glsp.Context, params *protocol.PrepareRenameParams) (any, error) {
d := sshconfig.DocumentParserMap[params.TextDocument.URI]
line := params.Position.Line
option, block := d.Config.FindOption(line)
if option == nil || option.Key == nil {
// Empty line
return nil, nil
}
if option.Key.Key == tagOption && option.OptionValue != nil {
return option.OptionValue.ToLSPRange(), nil
}
if option.Key.Key == matchOption {
matchBlock := block.(*ast.SSHMatchBlock)
entry := matchBlock.MatchValue.GetEntryAtPosition(common.LSPCharacterAsIndexPosition(params.Position.Character))
if entry == nil {
return nil, nil
}
value := entry.GetValueAtPosition(common.LSPCharacterAsIndexPosition(params.Position.Character))
if value == nil {
return nil, nil
}
return value.ToLSPRange(), nil
}
return nil, nil
}

View File

@ -0,0 +1,58 @@
package lsp
import (
"config-lsp/common"
"config-lsp/common/formatting"
sshconfig "config-lsp/handlers/ssh_config"
"config-lsp/handlers/ssh_config/ast"
"config-lsp/handlers/ssh_config/fields"
"config-lsp/handlers/ssh_config/handlers"
"github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16"
)
var nameTemplate = formatting.FormatTemplate("/!'%s/!'")
var matchOption = fields.CreateNormalizedName("Match")
func TextDocumentRename(context *glsp.Context, params *protocol.RenameParams) (*protocol.WorkspaceEdit, error) {
d := sshconfig.DocumentParserMap[params.TextDocument.URI]
index := common.LSPCharacterAsIndexPosition(params.Position.Character)
line := params.Position.Line
option, block := d.Config.FindOption(line)
if option != nil && option.OptionValue != nil && option.OptionValue.Value.Value != "" {
newName := nameTemplate.Format(formatting.DefaultFormattingOptions, params.NewName)
if option.Key.Key == tagOption {
return handlers.RenameTag(
params,
d,
option.OptionValue.Value.Value,
newName,
)
}
if option.Key.Key == matchOption {
matchBlock := block.(*ast.SSHMatchBlock)
entry := matchBlock.MatchValue.GetEntryAtPosition(index)
if entry != nil {
value := entry.GetValueAtPosition(index)
if value != nil {
return handlers.RenameTag(
params,
d,
value.Value.Value,
newName,
)
}
}
}
}
return nil, nil
}

View File

@ -2,6 +2,8 @@ package roothandler
import (
aliases "config-lsp/handlers/aliases/lsp"
sshconfig "config-lsp/handlers/ssh_config/lsp"
"github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16"
@ -26,7 +28,7 @@ func TextDocumentPrepareRename(context *glsp.Context, params *protocol.PrepareRe
case LanguageSSHDConfig:
return nil, nil
case LanguageSSHConfig:
return nil, nil
return sshconfig.TextDocumentPrepareRename(context, params)
case LanguageFstab:
return nil, nil
case LanguageWireguard:

View File

@ -2,6 +2,8 @@ package roothandler
import (
aliases "config-lsp/handlers/aliases/lsp"
sshconfig "config-lsp/handlers/ssh_config/lsp"
"github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16"
)
@ -25,7 +27,7 @@ func TextDocumentRename(context *glsp.Context, params *protocol.RenameParams) (*
case LanguageSSHDConfig:
return nil, nil
case LanguageSSHConfig:
return nil, nil
return sshconfig.TextDocumentRename(context, params)
case LanguageFstab:
return nil, nil
case LanguageWireguard: