mirror of
https://github.com/Myzel394/config-lsp.git
synced 2025-06-19 07:25:27 +02:00
feat(sshd_config): Add include support
This commit is contained in:
parent
774ee52a3b
commit
6c9ebc1b16
@ -13,19 +13,54 @@ func Analyze(
|
|||||||
d *sshdconfig.SSHDocument,
|
d *sshdconfig.SSHDocument,
|
||||||
) []protocol.Diagnostic {
|
) []protocol.Diagnostic {
|
||||||
errors := analyzeOptionsAreValid(d)
|
errors := analyzeOptionsAreValid(d)
|
||||||
|
|
||||||
|
if len(errors) > 0 {
|
||||||
|
return errsToDiagnostics(errors)
|
||||||
|
}
|
||||||
|
|
||||||
indexes, indexErrors := indexes.CreateIndexes(*d.Config)
|
indexes, indexErrors := indexes.CreateIndexes(*d.Config)
|
||||||
_ = indexes
|
|
||||||
|
d.Indexes = indexes
|
||||||
|
|
||||||
errors = append(errors, indexErrors...)
|
errors = append(errors, indexErrors...)
|
||||||
|
|
||||||
if len(errors) > 0 {
|
if len(errors) > 0 {
|
||||||
|
return errsToDiagnostics(errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
includeErrors := analyzeIncludeValues(d)
|
||||||
|
|
||||||
|
if len(includeErrors) > 0 {
|
||||||
|
errors = append(errors, includeErrors...)
|
||||||
|
} else {
|
||||||
|
for _, include := range d.Indexes.Includes {
|
||||||
|
for _, value := range include.Values {
|
||||||
|
for _, path := range value.Paths {
|
||||||
|
_, err := parseFile(string(path))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
errors = append(errors, common.LSPError{
|
||||||
|
Range: value.LocationRange,
|
||||||
|
Err: err,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errors) > 0 {
|
||||||
|
return errsToDiagnostics(errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func errsToDiagnostics(errs []common.LSPError) []protocol.Diagnostic {
|
||||||
return utils.Map(
|
return utils.Map(
|
||||||
errors,
|
errs,
|
||||||
func(err common.LSPError) protocol.Diagnostic {
|
func(err common.LSPError) protocol.Diagnostic {
|
||||||
return err.ToDiagnostic()
|
return err.ToDiagnostic()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
95
handlers/sshd_config/analyzer/include.go
Normal file
95
handlers/sshd_config/analyzer/include.go
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package analyzer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"config-lsp/common"
|
||||||
|
sshdconfig "config-lsp/handlers/sshd_config"
|
||||||
|
"config-lsp/handlers/sshd_config/ast"
|
||||||
|
"config-lsp/handlers/sshd_config/indexes"
|
||||||
|
"config-lsp/utils"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var whitespacePattern = regexp.MustCompile(`\S+`)
|
||||||
|
|
||||||
|
func analyzeIncludeValues(
|
||||||
|
d *sshdconfig.SSHDocument,
|
||||||
|
) []common.LSPError {
|
||||||
|
errs := make([]common.LSPError, 0)
|
||||||
|
|
||||||
|
for _, include := range d.Indexes.Includes {
|
||||||
|
for _, value := range include.Values {
|
||||||
|
validPaths, err := createIncludePaths(value.Value)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, common.LSPError{
|
||||||
|
Range: value.LocationRange,
|
||||||
|
Err: err,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
value.Paths = validPaths
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
func createIncludePaths(
|
||||||
|
suggestedPath string,
|
||||||
|
) ([]indexes.ValidPath, error) {
|
||||||
|
var absolutePath string
|
||||||
|
|
||||||
|
if path.IsAbs(suggestedPath) {
|
||||||
|
absolutePath = suggestedPath
|
||||||
|
} else {
|
||||||
|
absolutePath = path.Join("/etc", "ssh", suggestedPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
files, err := filepath.Glob(absolutePath)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New(fmt.Sprintf("Could not find file %s (error: %s)", absolutePath, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(files) == 0 {
|
||||||
|
return nil, errors.New(fmt.Sprintf("Could not find file %s", absolutePath))
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.Map(
|
||||||
|
files,
|
||||||
|
func(file string) indexes.ValidPath {
|
||||||
|
return indexes.ValidPath(file)
|
||||||
|
},
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFile(
|
||||||
|
filePath string,
|
||||||
|
) (*sshdconfig.SSHDocument, error) {
|
||||||
|
c := ast.NewSSHConfig()
|
||||||
|
|
||||||
|
content, err := os.ReadFile(filePath)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Parse(string(content))
|
||||||
|
|
||||||
|
d := &sshdconfig.SSHDocument{
|
||||||
|
Config: c,
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := Analyze(d)
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return nil, errors.New(fmt.Sprintf("Errors in %s", filePath))
|
||||||
|
}
|
||||||
|
|
||||||
|
return d, nil
|
||||||
|
}
|
@ -60,10 +60,12 @@ func checkOption(
|
|||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
if option.OptionValue == nil {
|
if option.OptionValue == nil || option.OptionValue.Value == "" {
|
||||||
return errs
|
errs = append(errs, common.LSPError{
|
||||||
}
|
Range: option.Key.LocationRange,
|
||||||
|
Err: errors.New(fmt.Sprintf("Option '%s' requires a value", option.Key.Value)),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
invalidValues := docOption.CheckIsValid(option.OptionValue.Value)
|
invalidValues := docOption.CheckIsValid(option.OptionValue.Value)
|
||||||
|
|
||||||
errs = append(
|
errs = append(
|
||||||
@ -79,6 +81,7 @@ func checkOption(
|
|||||||
)...,
|
)...,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
@ -391,9 +391,7 @@ See PATTERNS in ssh_config(5) for more information on patterns. This keyword may
|
|||||||
Value: docvalues.ArrayValue{
|
Value: docvalues.ArrayValue{
|
||||||
Separator: " ",
|
Separator: " ",
|
||||||
DuplicatesExtractor: &docvalues.SimpleDuplicatesExtractor,
|
DuplicatesExtractor: &docvalues.SimpleDuplicatesExtractor,
|
||||||
SubValue: docvalues.PathValue{
|
SubValue: docvalues.StringValue{},
|
||||||
RequiredType: docvalues.PathTypeFile,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
// TODO: Add extra check
|
// TODO: Add extra check
|
||||||
},
|
},
|
||||||
|
47
handlers/sshd_config/handlers/definition.go
Normal file
47
handlers/sshd_config/handlers/definition.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"config-lsp/handlers/sshd_config/indexes"
|
||||||
|
"config-lsp/utils"
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetIncludeOptionLocation(
|
||||||
|
include *indexes.SSHIndexIncludeLine,
|
||||||
|
cursor uint32,
|
||||||
|
) []protocol.Location {
|
||||||
|
index, found := slices.BinarySearchFunc(
|
||||||
|
include.Values,
|
||||||
|
cursor,
|
||||||
|
func(i *indexes.SSHIndexIncludeValue, cursor uint32) int {
|
||||||
|
if cursor < i.Start.Character {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
if cursor > i.End.Character {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
path := include.Values[index]
|
||||||
|
println("paths", fmt.Sprintf("%v", path.Paths))
|
||||||
|
|
||||||
|
return utils.Map(
|
||||||
|
path.Paths,
|
||||||
|
func(path indexes.ValidPath) protocol.Location {
|
||||||
|
return protocol.Location{
|
||||||
|
URI: path.AsURI(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
@ -5,6 +5,7 @@ import (
|
|||||||
"config-lsp/handlers/sshd_config/ast"
|
"config-lsp/handlers/sshd_config/ast"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var allowedDoubleOptions = map[string]struct{}{
|
var allowedDoubleOptions = map[string]struct{}{
|
||||||
@ -27,6 +28,25 @@ type SSHIndexAllOption struct {
|
|||||||
Option *ast.SSHOption
|
Option *ast.SSHOption
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ValidPath string
|
||||||
|
|
||||||
|
func (v ValidPath) AsURI() string {
|
||||||
|
return "file://" + string(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
type SSHIndexIncludeValue struct {
|
||||||
|
common.LocationRange
|
||||||
|
Value string
|
||||||
|
|
||||||
|
// Actual valid paths, these will be set by the analyzer
|
||||||
|
Paths []ValidPath
|
||||||
|
}
|
||||||
|
|
||||||
|
type SSHIndexIncludeLine struct {
|
||||||
|
Values []*SSHIndexIncludeValue
|
||||||
|
Option *SSHIndexAllOption
|
||||||
|
}
|
||||||
|
|
||||||
type SSHIndexes struct {
|
type SSHIndexes struct {
|
||||||
// Contains a map of `Option name + MatchBlock` to a list of options with that name
|
// Contains a map of `Option name + MatchBlock` to a list of options with that name
|
||||||
// This means an option may be specified inside a match block, and to get this
|
// This means an option may be specified inside a match block, and to get this
|
||||||
@ -35,18 +55,22 @@ type SSHIndexes struct {
|
|||||||
OptionsPerRelativeKey map[SSHIndexKey][]*ast.SSHOption
|
OptionsPerRelativeKey map[SSHIndexKey][]*ast.SSHOption
|
||||||
|
|
||||||
// This is a map of `Option name` to a list of options with that name
|
// This is a map of `Option name` to a list of options with that name
|
||||||
AllOptionsPerName map[string][]*SSHIndexAllOption
|
AllOptionsPerName map[string]map[uint32]*SSHIndexAllOption
|
||||||
|
|
||||||
|
Includes map[uint32]*SSHIndexIncludeLine
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var whitespacePattern = regexp.MustCompile(`\S+`)
|
||||||
|
|
||||||
func CreateIndexes(config ast.SSHConfig) (*SSHIndexes, []common.LSPError) {
|
func CreateIndexes(config ast.SSHConfig) (*SSHIndexes, []common.LSPError) {
|
||||||
errs := make([]common.LSPError, 0)
|
errs := make([]common.LSPError, 0)
|
||||||
indexes := &SSHIndexes{
|
indexes := &SSHIndexes{
|
||||||
OptionsPerRelativeKey: make(map[SSHIndexKey][]*ast.SSHOption),
|
OptionsPerRelativeKey: make(map[SSHIndexKey][]*ast.SSHOption),
|
||||||
AllOptionsPerName: make(map[string][]*SSHIndexAllOption),
|
AllOptionsPerName: make(map[string]map[uint32]*SSHIndexAllOption),
|
||||||
|
Includes: make(map[uint32]*SSHIndexIncludeLine),
|
||||||
}
|
}
|
||||||
|
|
||||||
it := config.Options.Iterator()
|
it := config.Options.Iterator()
|
||||||
|
|
||||||
for it.Next() {
|
for it.Next() {
|
||||||
entry := it.Value().(ast.SSHEntry)
|
entry := it.Value().(ast.SSHEntry)
|
||||||
|
|
||||||
@ -59,7 +83,6 @@ func CreateIndexes(config ast.SSHConfig) (*SSHIndexes, []common.LSPError) {
|
|||||||
matchBlock := entry.(*ast.SSHMatchBlock)
|
matchBlock := entry.(*ast.SSHMatchBlock)
|
||||||
|
|
||||||
it := matchBlock.Options.Iterator()
|
it := matchBlock.Options.Iterator()
|
||||||
|
|
||||||
for it.Next() {
|
for it.Next() {
|
||||||
option := it.Value().(*ast.SSHOption)
|
option := it.Value().(*ast.SSHOption)
|
||||||
|
|
||||||
@ -68,6 +91,43 @@ func CreateIndexes(config ast.SSHConfig) (*SSHIndexes, []common.LSPError) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add Includes
|
||||||
|
for _, includeOption := range indexes.AllOptionsPerName["Include"] {
|
||||||
|
rawValue := includeOption.Option.OptionValue.Value
|
||||||
|
pathIndexes := whitespacePattern.FindAllStringIndex(rawValue, -1)
|
||||||
|
paths := make([]*SSHIndexIncludeValue, 0)
|
||||||
|
|
||||||
|
for _, pathIndex := range pathIndexes {
|
||||||
|
startIndex := pathIndex[0]
|
||||||
|
endIndex := pathIndex[1]
|
||||||
|
|
||||||
|
rawPath := rawValue[startIndex:endIndex]
|
||||||
|
|
||||||
|
offset := includeOption.Option.OptionValue.Start.Character
|
||||||
|
path := SSHIndexIncludeValue{
|
||||||
|
LocationRange: common.LocationRange{
|
||||||
|
Start: common.Location{
|
||||||
|
Line: includeOption.Option.Start.Line,
|
||||||
|
Character: uint32(startIndex) + offset,
|
||||||
|
},
|
||||||
|
End: common.Location{
|
||||||
|
Line: includeOption.Option.Start.Line,
|
||||||
|
Character: uint32(endIndex) + offset - 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Value: rawPath,
|
||||||
|
Paths: make([]ValidPath, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
paths = append(paths, &path)
|
||||||
|
}
|
||||||
|
|
||||||
|
indexes.Includes[includeOption.Option.Start.Line] = &SSHIndexIncludeLine{
|
||||||
|
Values: paths,
|
||||||
|
Option: includeOption,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return indexes, errs
|
return indexes, errs
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,15 +157,14 @@ func addOption(
|
|||||||
i.OptionsPerRelativeKey[indexEntry] = []*ast.SSHOption{option}
|
i.OptionsPerRelativeKey[indexEntry] = []*ast.SSHOption{option}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, found := i.AllOptionsPerName[option.Key.Value]; found {
|
||||||
if existingEntry, found := i.AllOptionsPerName[option.Key.Value]; found {
|
i.AllOptionsPerName[option.Key.Value][option.Start.Line] = &SSHIndexAllOption{
|
||||||
i.AllOptionsPerName[option.Key.Value] = append(existingEntry, &SSHIndexAllOption{
|
|
||||||
MatchBlock: matchBlock,
|
MatchBlock: matchBlock,
|
||||||
Option: option,
|
Option: option,
|
||||||
})
|
}
|
||||||
} else {
|
} else {
|
||||||
i.AllOptionsPerName[option.Key.Value] = []*SSHIndexAllOption{
|
i.AllOptionsPerName[option.Key.Value] = map[uint32]*SSHIndexAllOption{
|
||||||
{
|
option.Start.Line: {
|
||||||
MatchBlock: matchBlock,
|
MatchBlock: matchBlock,
|
||||||
Option: option,
|
Option: option,
|
||||||
},
|
},
|
||||||
|
@ -116,3 +116,48 @@ Match Address 192.168.0.1/24
|
|||||||
t.Errorf("Expected 3 PermitRootLogin options, but got %v", indexes.AllOptionsPerName["PermitRootLogin"])
|
t.Errorf("Expected 3 PermitRootLogin options, but got %v", indexes.AllOptionsPerName["PermitRootLogin"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIncludeExample(
|
||||||
|
t *testing.T,
|
||||||
|
) {
|
||||||
|
input := utils.Dedent(`
|
||||||
|
PermitRootLogin yes
|
||||||
|
Include /etc/ssh/sshd_config.d/*.conf hello_world
|
||||||
|
`)
|
||||||
|
config := ast.NewSSHConfig()
|
||||||
|
errors := config.Parse(input)
|
||||||
|
|
||||||
|
if len(errors) > 0 {
|
||||||
|
t.Fatalf("Expected no errors, but got %v", len(errors))
|
||||||
|
}
|
||||||
|
|
||||||
|
indexes, errors := CreateIndexes(*config)
|
||||||
|
|
||||||
|
if len(errors) > 0 {
|
||||||
|
t.Fatalf("Expected no errors, but got %v", len(errors))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(len(indexes.Includes) == 1) {
|
||||||
|
t.Fatalf("Expected 1 include, but got %v", len(indexes.Includes))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(len(indexes.Includes[1].Values) == 2) {
|
||||||
|
t.Fatalf("Expected 2 include path, but got %v", len(indexes.Includes[1].Values))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(indexes.Includes[1].Values[0].Value == "/etc/ssh/sshd_config.d/*.conf" &&
|
||||||
|
indexes.Includes[1].Values[0].Start.Line == 1 &&
|
||||||
|
indexes.Includes[1].Values[0].End.Line == 1 &&
|
||||||
|
indexes.Includes[1].Values[0].Start.Character == 8 &&
|
||||||
|
indexes.Includes[1].Values[0].End.Character == 36) {
|
||||||
|
t.Errorf("Expected '/etc/ssh/sshd_config.d/*.conf' on line 1, but got %v on line %v", indexes.Includes[1].Values[0].Value, indexes.Includes[1].Values[0].Start.Line)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !(indexes.Includes[1].Values[1].Value == "hello_world" &&
|
||||||
|
indexes.Includes[1].Values[1].Start.Line == 1 &&
|
||||||
|
indexes.Includes[1].Values[1].End.Line == 1 &&
|
||||||
|
indexes.Includes[1].Values[1].Start.Character == 38 &&
|
||||||
|
indexes.Includes[1].Values[1].End.Character == 48) {
|
||||||
|
t.Errorf("Expected 'hello_world' on line 1, but got %v on line %v", indexes.Includes[1].Values[1].Value, indexes.Includes[1].Values[1].Start.Line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
23
handlers/sshd_config/lsp/text-document-definition.go
Normal file
23
handlers/sshd_config/lsp/text-document-definition.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package lsp
|
||||||
|
|
||||||
|
import (
|
||||||
|
sshdconfig "config-lsp/handlers/sshd_config"
|
||||||
|
"config-lsp/handlers/sshd_config/handlers"
|
||||||
|
|
||||||
|
"github.com/tliron/glsp"
|
||||||
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TextDocumentDefinition(context *glsp.Context, params *protocol.DefinitionParams) ([]protocol.Location, error) {
|
||||||
|
d := sshdconfig.DocumentParserMap[params.TextDocument.URI]
|
||||||
|
cursor := params.Position.Character
|
||||||
|
line := params.Position.Line
|
||||||
|
|
||||||
|
if include, found := d.Indexes.Includes[line]; found {
|
||||||
|
relativeCursor := cursor - include.Option.Option.LocationRange.Start.Character
|
||||||
|
|
||||||
|
return handlers.GetIncludeOptionLocation(include, relativeCursor), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
@ -2,12 +2,14 @@ package sshdconfig
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"config-lsp/handlers/sshd_config/ast"
|
"config-lsp/handlers/sshd_config/ast"
|
||||||
|
"config-lsp/handlers/sshd_config/indexes"
|
||||||
|
|
||||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SSHDocument struct {
|
type SSHDocument struct {
|
||||||
Config *ast.SSHConfig
|
Config *ast.SSHConfig
|
||||||
|
Indexes *indexes.SSHIndexes
|
||||||
}
|
}
|
||||||
|
|
||||||
var DocumentParserMap = map[protocol.DocumentUri]*SSHDocument{}
|
var DocumentParserMap = map[protocol.DocumentUri]*SSHDocument{}
|
||||||
|
@ -2,6 +2,7 @@ package roothandler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
aliases "config-lsp/handlers/aliases/lsp"
|
aliases "config-lsp/handlers/aliases/lsp"
|
||||||
|
sshdconfig "config-lsp/handlers/sshd_config/lsp"
|
||||||
|
|
||||||
"github.com/tliron/glsp"
|
"github.com/tliron/glsp"
|
||||||
protocol "github.com/tliron/glsp/protocol_3_16"
|
protocol "github.com/tliron/glsp/protocol_3_16"
|
||||||
@ -24,7 +25,7 @@ func TextDocumentDefinition(context *glsp.Context, params *protocol.DefinitionPa
|
|||||||
case LanguageHosts:
|
case LanguageHosts:
|
||||||
return nil, nil
|
return nil, nil
|
||||||
case LanguageSSHDConfig:
|
case LanguageSSHDConfig:
|
||||||
return nil, nil
|
return sshdconfig.TextDocumentDefinition(context, params)
|
||||||
case LanguageFstab:
|
case LanguageFstab:
|
||||||
return nil, nil
|
return nil, nil
|
||||||
case LanguageWireguard:
|
case LanguageWireguard:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user