feat(ssh_config): Add code actiont o add option to unknown option

This commit is contained in:
Myzel394 2024-10-02 09:24:11 +02:00
parent bf54705e93
commit cd23ffcb7f
No known key found for this signature in database
GPG Key ID: ED20A1D1D423AF3F
12 changed files with 311 additions and 12 deletions

View File

@ -7,6 +7,12 @@ import (
protocol "github.com/tliron/glsp/protocol_3_16"
)
var DefaultFormattingOptions = protocol.FormattingOptions{
"tabSize": float64(4),
"insertSpaces": false,
"trimTrailingWhitespace": true,
}
type FormatTemplate string
func (f FormatTemplate) Format(
@ -84,7 +90,10 @@ func surroundWithQuotes(s string) string {
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:]
} else {
value = value[:startPosition-3] + innerValue + value[endPosition+3:]

View File

@ -116,3 +116,22 @@ func TestSurroundWithQuotesButNoSpaceExample(
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)
}
}

View File

@ -32,7 +32,7 @@ func analyzeValuesAreValid(
Severity: &common.SeverityError,
},
)
ctx.document.Indexes.UnknownOptions = append(ctx.document.Indexes.UnknownOptions, info)
ctx.document.Indexes.UnknownOptions[info.Option.Start.Line] = info
continue
}

View 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
}
}

View 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
}

View File

@ -44,5 +44,6 @@ type SSHIndexes struct {
// This is used for code actions.
// This stores a list of unknown option, so that we can provide
// 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
}

View File

@ -16,7 +16,7 @@ func NewSSHIndexes() *SSHIndexes {
AllOptionsPerName: make(map[fields.NormalizedOptionName](map[ast.SSHBlock]([]*ast.SSHOption)), 0),
Includes: make([]*SSHIndexIncludeLine, 0),
IgnoredOptions: make(map[ast.SSHBlock]SSHIndexIgnoredUnknowns),
UnknownOptions: make([]ast.AllOptionInfo, 0),
UnknownOptions: make(map[uint32]ast.AllOptionInfo, 0),
}
}

View File

@ -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`.

View 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
}

View 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
}

View File

@ -3,6 +3,7 @@ package roothandler
import (
aliases "config-lsp/handlers/aliases/lsp"
hosts "config-lsp/handlers/hosts/lsp"
sshconfig "config-lsp/handlers/ssh_config/lsp"
wireguard "config-lsp/handlers/wireguard/lsp"
"github.com/tliron/glsp"
@ -30,7 +31,7 @@ func TextDocumentCodeAction(context *glsp.Context, params *protocol.CodeActionPa
case LanguageSSHDConfig:
return nil, nil
case LanguageSSHConfig:
return nil, nil
return sshconfig.TextDocumentCodeAction(context, params)
case LanguageWireguard:
return wireguard.TextDocumentCodeAction(context, params)
case LanguageAliases:

View File

@ -3,6 +3,7 @@ package roothandler
import (
aliases "config-lsp/handlers/aliases/lsp"
hosts "config-lsp/handlers/hosts/lsp"
sshconfig "config-lsp/handlers/ssh_config/lsp"
wireguard "config-lsp/handlers/wireguard/lsp"
"strings"
@ -24,6 +25,8 @@ func WorkspaceExecuteCommand(context *glsp.Context, params *protocol.ExecuteComm
edit, err = hosts.WorkspaceExecuteCommand(context, params)
case "aliases":
edit, err = aliases.WorkspaceExecuteCommand(context, params)
case "sshconfig":
edit, err = sshconfig.WorkspaceExecuteCommand(context, params)
}
if err != nil {