mirror of
https://github.com/Myzel394/config-lsp.git
synced 2025-06-18 23:15:26 +02:00
feat(ssh_config): Add code actiont o add option to unknown option
This commit is contained in:
parent
bf54705e93
commit
cd23ffcb7f
@ -7,6 +7,12 @@ import (
|
|||||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var DefaultFormattingOptions = protocol.FormattingOptions{
|
||||||
|
"tabSize": float64(4),
|
||||||
|
"insertSpaces": false,
|
||||||
|
"trimTrailingWhitespace": true,
|
||||||
|
}
|
||||||
|
|
||||||
type FormatTemplate string
|
type FormatTemplate string
|
||||||
|
|
||||||
func (f FormatTemplate) Format(
|
func (f FormatTemplate) Format(
|
||||||
@ -84,7 +90,10 @@ func surroundWithQuotes(s string) string {
|
|||||||
|
|
||||||
innerValue := value[startPosition:endPosition]
|
innerValue := value[startPosition:endPosition]
|
||||||
|
|
||||||
if strings.Contains(innerValue, " ") {
|
if innerValue[0] == '"' && innerValue[len(innerValue)-1] == '"' && (len(innerValue) >= 2 || innerValue[len(innerValue)-2] != '\\') {
|
||||||
|
// Already surrounded
|
||||||
|
value = value[:startPosition-3] + innerValue + value[endPosition+3:]
|
||||||
|
} else if strings.Contains(innerValue, " ") {
|
||||||
value = value[:startPosition-3] + "\"" + innerValue + "\"" + value[endPosition+3:]
|
value = value[:startPosition-3] + "\"" + innerValue + "\"" + value[endPosition+3:]
|
||||||
} else {
|
} else {
|
||||||
value = value[:startPosition-3] + innerValue + value[endPosition+3:]
|
value = value[:startPosition-3] + innerValue + value[endPosition+3:]
|
||||||
|
@ -116,3 +116,22 @@ func TestSurroundWithQuotesButNoSpaceExample(
|
|||||||
t.Errorf("Expected %q but got %q", expected, result)
|
t.Errorf("Expected %q but got %q", expected, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSurroundWithQuotesButAlreadySurrounded(
|
||||||
|
t *testing.T,
|
||||||
|
) {
|
||||||
|
template := FormatTemplate("%s /!'%s/!'")
|
||||||
|
|
||||||
|
options := protocol.FormattingOptions{
|
||||||
|
"tabSize": float64(4),
|
||||||
|
"insertSpaces": false,
|
||||||
|
"trimTrailingWhitespace": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
result := template.Format(options, "PermitRootLogin", `"Hello World"`)
|
||||||
|
expected := `PermitRootLogin "Hello World"`
|
||||||
|
|
||||||
|
if result != expected {
|
||||||
|
t.Errorf("Expected %q but got %q", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -32,7 +32,7 @@ func analyzeValuesAreValid(
|
|||||||
Severity: &common.SeverityError,
|
Severity: &common.SeverityError,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
ctx.document.Indexes.UnknownOptions = append(ctx.document.Indexes.UnknownOptions, info)
|
ctx.document.Indexes.UnknownOptions[info.Option.Start.Line] = info
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
184
handlers/ssh_config/handlers/code-actions.go
Normal file
184
handlers/ssh_config/handlers/code-actions.go
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"config-lsp/common/formatting"
|
||||||
|
sshconfig "config-lsp/handlers/ssh_config"
|
||||||
|
"config-lsp/handlers/ssh_config/ast"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
|
)
|
||||||
|
|
||||||
|
type codeActionName string
|
||||||
|
|
||||||
|
const (
|
||||||
|
CodeActionAddToUnknown codeActionName = "addToUnknown"
|
||||||
|
)
|
||||||
|
|
||||||
|
type codeAction interface {
|
||||||
|
RunCommand(*sshconfig.SSHDocument) (*protocol.ApplyWorkspaceEditParams, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type codeActionArgs interface{}
|
||||||
|
|
||||||
|
type codeActionAddToUnknownArgs struct {
|
||||||
|
URI protocol.DocumentUri
|
||||||
|
// Where the option is defined
|
||||||
|
OptionLine uint32
|
||||||
|
// Where the block is defined, if nil, option is globally defined
|
||||||
|
BlockLine *uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func CodeActionAddToUnknownArgsFromArguments(arguments map[string]interface{}) codeActionAddToUnknownArgs {
|
||||||
|
var blockLine *uint32
|
||||||
|
|
||||||
|
if arguments["BlockLine"] != nil {
|
||||||
|
blockLineValue := uint32(arguments["BlockLine"].(float64))
|
||||||
|
blockLine = &blockLineValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return codeActionAddToUnknownArgs{
|
||||||
|
URI: arguments["URI"].(protocol.DocumentUri),
|
||||||
|
OptionLine: uint32(arguments["OptionLine"].(float64)),
|
||||||
|
BlockLine: blockLine,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var addToUnknownOptionTemplate = formatting.FormatTemplate("/!'%s/!'")
|
||||||
|
|
||||||
|
func (args codeActionAddToUnknownArgs) RunCommand(d *sshconfig.SSHDocument) (*protocol.ApplyWorkspaceEditParams, error) {
|
||||||
|
var option *ast.SSHOption
|
||||||
|
var block ast.SSHBlock
|
||||||
|
|
||||||
|
// Either this or `insertionLine` must be set
|
||||||
|
// `ignoreUnknownOption` is used if an `IgnoreUnknown` option is set already
|
||||||
|
// `insertionLine` is used if no `IgnoreUnknown` option is set
|
||||||
|
var ignoreUnknownOption *ast.SSHOption
|
||||||
|
var insertionLine uint32
|
||||||
|
|
||||||
|
if args.BlockLine == nil {
|
||||||
|
// Global
|
||||||
|
rawOption, found := d.Config.Options.Get(args.OptionLine)
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return nil, fmt.Errorf("No option found at line %d", args.OptionLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
option = rawOption.(*ast.SSHOption)
|
||||||
|
|
||||||
|
if ignoreOption, found := d.Indexes.IgnoredOptions[nil]; found {
|
||||||
|
ignoreUnknownOption = ignoreOption.OptionValue
|
||||||
|
} else {
|
||||||
|
insertionLine = 0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Block
|
||||||
|
rawBlock, found := d.Config.Options.Get(*args.BlockLine)
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return nil, fmt.Errorf("No block found at line %d", *args.BlockLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
block = rawBlock.(ast.SSHBlock)
|
||||||
|
|
||||||
|
rawOption, found := block.GetOptions().Get(args.OptionLine)
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return nil, fmt.Errorf("No option found at line %d", args.OptionLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
option = rawOption.(*ast.SSHOption)
|
||||||
|
|
||||||
|
if ignoreOption, found := d.Indexes.IgnoredOptions[block]; found {
|
||||||
|
ignoreUnknownOption = ignoreOption.OptionValue
|
||||||
|
} else {
|
||||||
|
insertionLine = block.GetEntryOption().Start.Line + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rawOptionName := option.Key.Value.Raw
|
||||||
|
println("rawOption:")
|
||||||
|
println(rawOptionName)
|
||||||
|
optionName := addToUnknownOptionTemplate.Format(formatting.DefaultFormattingOptions, rawOptionName)
|
||||||
|
|
||||||
|
// We got everything, let's build the edit!
|
||||||
|
if ignoreUnknownOption == nil {
|
||||||
|
// Insert a completely new IgnoreUnknown option
|
||||||
|
|
||||||
|
if block == nil {
|
||||||
|
// Global
|
||||||
|
label := fmt.Sprintf("Add %s to unknown options", option.Key.Key)
|
||||||
|
return &protocol.ApplyWorkspaceEditParams{
|
||||||
|
Label: &label,
|
||||||
|
Edit: protocol.WorkspaceEdit{
|
||||||
|
Changes: map[protocol.DocumentUri][]protocol.TextEdit{
|
||||||
|
args.URI: {
|
||||||
|
{
|
||||||
|
Range: protocol.Range{
|
||||||
|
Start: protocol.Position{
|
||||||
|
Line: insertionLine,
|
||||||
|
Character: 0,
|
||||||
|
},
|
||||||
|
End: protocol.Position{
|
||||||
|
Line: insertionLine,
|
||||||
|
Character: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NewText: fmt.Sprintf("IgnoreUnknown %s\n", optionName),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
} else {
|
||||||
|
label := fmt.Sprintf("Add %s to unknown options", option.Key.Key)
|
||||||
|
return &protocol.ApplyWorkspaceEditParams{
|
||||||
|
Label: &label,
|
||||||
|
Edit: protocol.WorkspaceEdit{
|
||||||
|
Changes: map[protocol.DocumentUri][]protocol.TextEdit{
|
||||||
|
args.URI: {
|
||||||
|
{
|
||||||
|
Range: protocol.Range{
|
||||||
|
Start: protocol.Position{
|
||||||
|
Line: insertionLine,
|
||||||
|
Character: 0,
|
||||||
|
},
|
||||||
|
End: protocol.Position{
|
||||||
|
Line: insertionLine,
|
||||||
|
Character: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NewText: fmt.Sprintf(" IgnoreUnknown %s\n", optionName),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Append to the existing IgnoreUnknown option
|
||||||
|
label := fmt.Sprintf("Add %s to unknown options", option.Key.Key)
|
||||||
|
return &protocol.ApplyWorkspaceEditParams{
|
||||||
|
Label: &label,
|
||||||
|
Edit: protocol.WorkspaceEdit{
|
||||||
|
Changes: map[protocol.DocumentUri][]protocol.TextEdit{
|
||||||
|
args.URI: {
|
||||||
|
{
|
||||||
|
Range: protocol.Range{
|
||||||
|
Start: protocol.Position{
|
||||||
|
Line: ignoreUnknownOption.Start.Line,
|
||||||
|
Character: ignoreUnknownOption.End.Character,
|
||||||
|
},
|
||||||
|
End: protocol.Position{
|
||||||
|
Line: ignoreUnknownOption.Start.Line,
|
||||||
|
Character: ignoreUnknownOption.End.Character,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NewText: fmt.Sprintf(" %s", optionName),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
47
handlers/ssh_config/handlers/fetch-code-actions.go
Normal file
47
handlers/ssh_config/handlers/fetch-code-actions.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
sshconfig "config-lsp/handlers/ssh_config"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
|
)
|
||||||
|
|
||||||
|
func FetchCodeActions(
|
||||||
|
d *sshconfig.SSHDocument,
|
||||||
|
params *protocol.CodeActionParams,
|
||||||
|
) []protocol.CodeAction {
|
||||||
|
line := params.Range.Start.Line
|
||||||
|
|
||||||
|
if unknownOption, found := d.Indexes.UnknownOptions[line]; found {
|
||||||
|
var blockLine *uint32
|
||||||
|
|
||||||
|
if unknownOption.Block != nil {
|
||||||
|
blockLineValue := uint32(unknownOption.Block.GetLocation().Start.Line)
|
||||||
|
blockLine = &blockLineValue
|
||||||
|
}
|
||||||
|
|
||||||
|
commandID := "sshconfig." + CodeActionAddToUnknown
|
||||||
|
command := protocol.Command{
|
||||||
|
Title: fmt.Sprintf("Add %s to unknown options", unknownOption.Option.Key.Key),
|
||||||
|
Command: string(commandID),
|
||||||
|
Arguments: []any{
|
||||||
|
codeActionAddToUnknownArgs{
|
||||||
|
URI: params.TextDocument.URI,
|
||||||
|
OptionLine: unknownOption.Option.Start.Line,
|
||||||
|
BlockLine: blockLine,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
codeAction := &protocol.CodeAction{
|
||||||
|
Title: fmt.Sprintf("Add %s to unknown options", unknownOption.Option.Key.Key),
|
||||||
|
Command: &command,
|
||||||
|
}
|
||||||
|
|
||||||
|
return []protocol.CodeAction{
|
||||||
|
*codeAction,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -44,5 +44,6 @@ type SSHIndexes struct {
|
|||||||
// This is used for code actions.
|
// This is used for code actions.
|
||||||
// This stores a list of unknown option, so that we can provide
|
// This stores a list of unknown option, so that we can provide
|
||||||
// a code action to add them to a "IgnoreUnknown" option
|
// a code action to add them to a "IgnoreUnknown" option
|
||||||
UnknownOptions []ast.AllOptionInfo
|
// This is a map of <line> to <option>
|
||||||
|
UnknownOptions map[uint32]ast.AllOptionInfo
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ func NewSSHIndexes() *SSHIndexes {
|
|||||||
AllOptionsPerName: make(map[fields.NormalizedOptionName](map[ast.SSHBlock]([]*ast.SSHOption)), 0),
|
AllOptionsPerName: make(map[fields.NormalizedOptionName](map[ast.SSHBlock]([]*ast.SSHOption)), 0),
|
||||||
Includes: make([]*SSHIndexIncludeLine, 0),
|
Includes: make([]*SSHIndexIncludeLine, 0),
|
||||||
IgnoredOptions: make(map[ast.SSHBlock]SSHIndexIgnoredUnknowns),
|
IgnoredOptions: make(map[ast.SSHBlock]SSHIndexIgnoredUnknowns),
|
||||||
UnknownOptions: make([]ast.AllOptionInfo, 0),
|
UnknownOptions: make(map[uint32]ast.AllOptionInfo, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
# /sshd_config/lsp
|
|
||||||
|
|
||||||
This folder is the glue between our language server and the LSP
|
|
||||||
clients.
|
|
||||||
This folder only contains LSP commands.
|
|
||||||
It only handles very little actual logic, and instead calls
|
|
||||||
the handlers from `../handlers`.
|
|
17
handlers/ssh_config/lsp/text-document-code-action.go
Normal file
17
handlers/ssh_config/lsp/text-document-code-action.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package lsp
|
||||||
|
|
||||||
|
import (
|
||||||
|
sshconfig "config-lsp/handlers/ssh_config"
|
||||||
|
"config-lsp/handlers/ssh_config/handlers"
|
||||||
|
|
||||||
|
"github.com/tliron/glsp"
|
||||||
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
return actions, nil
|
||||||
|
}
|
25
handlers/ssh_config/lsp/workspace-execute-command.go
Normal file
25
handlers/ssh_config/lsp/workspace-execute-command.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package lsp
|
||||||
|
|
||||||
|
import (
|
||||||
|
sshconfig "config-lsp/handlers/ssh_config"
|
||||||
|
"config-lsp/handlers/ssh_config/handlers"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/tliron/glsp"
|
||||||
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
|
)
|
||||||
|
|
||||||
|
func WorkspaceExecuteCommand(context *glsp.Context, params *protocol.ExecuteCommandParams) (*protocol.ApplyWorkspaceEditParams, error) {
|
||||||
|
_, command, _ := strings.Cut(params.Command, ".")
|
||||||
|
|
||||||
|
switch command {
|
||||||
|
case string(handlers.CodeActionAddToUnknown):
|
||||||
|
args := handlers.CodeActionAddToUnknownArgsFromArguments(params.Arguments[0].(map[string]any))
|
||||||
|
|
||||||
|
d := sshconfig.DocumentParserMap[args.URI]
|
||||||
|
|
||||||
|
return args.RunCommand(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
@ -3,6 +3,7 @@ package roothandler
|
|||||||
import (
|
import (
|
||||||
aliases "config-lsp/handlers/aliases/lsp"
|
aliases "config-lsp/handlers/aliases/lsp"
|
||||||
hosts "config-lsp/handlers/hosts/lsp"
|
hosts "config-lsp/handlers/hosts/lsp"
|
||||||
|
sshconfig "config-lsp/handlers/ssh_config/lsp"
|
||||||
wireguard "config-lsp/handlers/wireguard/lsp"
|
wireguard "config-lsp/handlers/wireguard/lsp"
|
||||||
|
|
||||||
"github.com/tliron/glsp"
|
"github.com/tliron/glsp"
|
||||||
@ -30,7 +31,7 @@ func TextDocumentCodeAction(context *glsp.Context, params *protocol.CodeActionPa
|
|||||||
case LanguageSSHDConfig:
|
case LanguageSSHDConfig:
|
||||||
return nil, nil
|
return nil, nil
|
||||||
case LanguageSSHConfig:
|
case LanguageSSHConfig:
|
||||||
return nil, nil
|
return sshconfig.TextDocumentCodeAction(context, params)
|
||||||
case LanguageWireguard:
|
case LanguageWireguard:
|
||||||
return wireguard.TextDocumentCodeAction(context, params)
|
return wireguard.TextDocumentCodeAction(context, params)
|
||||||
case LanguageAliases:
|
case LanguageAliases:
|
||||||
|
@ -3,6 +3,7 @@ package roothandler
|
|||||||
import (
|
import (
|
||||||
aliases "config-lsp/handlers/aliases/lsp"
|
aliases "config-lsp/handlers/aliases/lsp"
|
||||||
hosts "config-lsp/handlers/hosts/lsp"
|
hosts "config-lsp/handlers/hosts/lsp"
|
||||||
|
sshconfig "config-lsp/handlers/ssh_config/lsp"
|
||||||
wireguard "config-lsp/handlers/wireguard/lsp"
|
wireguard "config-lsp/handlers/wireguard/lsp"
|
||||||
|
|
||||||
"strings"
|
"strings"
|
||||||
@ -24,6 +25,8 @@ func WorkspaceExecuteCommand(context *glsp.Context, params *protocol.ExecuteComm
|
|||||||
edit, err = hosts.WorkspaceExecuteCommand(context, params)
|
edit, err = hosts.WorkspaceExecuteCommand(context, params)
|
||||||
case "aliases":
|
case "aliases":
|
||||||
edit, err = aliases.WorkspaceExecuteCommand(context, params)
|
edit, err = aliases.WorkspaceExecuteCommand(context, params)
|
||||||
|
case "sshconfig":
|
||||||
|
edit, err = sshconfig.WorkspaceExecuteCommand(context, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user