Merge pull request #24 from Myzel394/improvements

This commit is contained in:
Myzel394 2024-10-27 12:14:22 +01:00 committed by GitHub
commit fcdd70030f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 630 additions and 429 deletions

View File

@ -29,7 +29,7 @@ jobs:
- name: Check version in code matches flake version
shell: bash
run: |
if ! [ $(grep '// CI:CD-VERSION$' server/root-handler/handler.go | cut -d'"' -f 2) = $(grep '# CI:CD-VERSION$' flake.nix | cut -d'"' -f 2) ];
if ! [ $(grep '// CI:CD-VERSION$' server/root-handler/common.go | cut -d'"' -f 2) = $(grep '# CI:CD-VERSION$' flake.nix | cut -d'"' -f 2) ];
then
echo "Version mismatch between code and flake"
exit 1

View File

@ -28,11 +28,52 @@ You are welcome to request any config file, as far as it's fairly well known.
## Installation
### VS Code Extension
[Install the extension from the marketplace](https://marketplace.visualstudio.com/items?itemName=myzel394.config-lsp)
Alternatively, you can also manually install the extension:
1. Download the latest extension version from the [release page](https://github.com/Myzel394/config-lsp/releases) - You can find the extension under the "assets" section. The filename ends with `.vsix`
2. Open VS Code
3. Open the extensions sidebar
4. In the top bar, click on the three dots and select "Install from VSIX..."
5. Select the just downloaded `.vsix` file
6. You may need to restart VS Code
7. Enjoy!
### Manual installation
To use `config-lsp` in any other editor, you'll need to install it manually.
Don't worry, it's easy!
#### Installing the latest Binary
##### Brew
```sh
brew install myzel394/formulae/config-lsp
```
##### Manual Binary
Download the latest binary from the [releases page](https://github.com/Myzel394/config-lsp/releases) and put it in your PATH.
Follow the instructions for your editor below.
##### Compiling
### Neovim installation
You can either compile the binary using go:
```sh
go build -o config-lsp
```
or build it using Nix:
```sh
nix flake build
```
#### Neovim installation
Using [nvim-lspconfig](https://github.com/neovim/nvim-lspconfig) you can add `config-lsp` by adding the following to your `lsp.lua` (filename might differ):
@ -57,14 +98,6 @@ end
lspconfig.config_lsp.setup {}
`````
### VS Code installation
The VS Code extension is currently in development. An official extension will be released soon.
However, at the moment you can also compile the extension yourself and run it in development mode.
**Do not create an extension and publish it yourself. Contribute to the official extension instead.**
## Supporting config-lsp
You can either contribute to the project, [see CONTRIBUTING.md](CONTRIBUTING.md), or you can sponsor me via [GitHub Sponsors](https://github.com/sponsors/Myzel394) or via [crypto currencies](https://github.com/Myzel394/contact-me?tab=readme-ov-file#donations).

View File

@ -23,7 +23,7 @@
"aarch64-windows"
] (system:
let
version = "0.1.1"; # CI:CD-VERSION
version = "0.1.2"; # CI:CD-VERSION
pkgs = import nixpkgs {
inherit system;
overlays = [

View File

@ -2,6 +2,8 @@ package main
import (
roothandler "config-lsp/root-handler"
"fmt"
"os"
"github.com/tliron/commonlog"
@ -11,6 +13,13 @@ import (
)
func main() {
if len(os.Args) > 1 && (os.Args[1] == "--version" || os.Args[1] == "version") {
fmt.Println(roothandler.Version)
os.Exit(0)
return
}
// This increases logging verbosity (optional)
commonlog.Configure(1, nil)

View File

@ -0,0 +1,5 @@
package roothandler
// The comment below at the end of the line is required for the CI:CD to work.
// Do not remove it
var Version = "0.1.2" // CI:CD-VERSION

View File

@ -1,6 +1,8 @@
package roothandler
import (
"config-lsp/root-handler/lsp"
"config-lsp/root-handler/shared"
"github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16"
@ -9,32 +11,29 @@ import (
const lsName = "config-lsp"
// The comment below at the end of the line is required for the CI:CD to work.
// Do not remove it
var version = "0.1.1" // CI:CD-VERSION
var lspHandler protocol.Handler
// The root handler which handles all the LSP requests and then forwards them to the appropriate handler
func SetUpRootHandler() {
rootHandler = NewRootHandler()
shared.Handler = shared.NewRootHandler()
lspHandler = protocol.Handler{
Initialize: initialize,
Initialized: initialized,
Shutdown: shutdown,
SetTrace: setTrace,
TextDocumentDidOpen: TextDocumentDidOpen,
TextDocumentDidChange: TextDocumentDidChange,
TextDocumentCompletion: TextDocumentCompletion,
TextDocumentHover: TextDocumentHover,
TextDocumentDidClose: TextDocumentDidClose,
TextDocumentCodeAction: TextDocumentCodeAction,
TextDocumentDefinition: TextDocumentDefinition,
WorkspaceExecuteCommand: WorkspaceExecuteCommand,
TextDocumentRename: TextDocumentRename,
TextDocumentPrepareRename: TextDocumentPrepareRename,
TextDocumentSignatureHelp: TextDocumentSignatureHelp,
TextDocumentRangeFormatting: TextDocumentRangeFormattingFunc,
TextDocumentDidOpen: lsp.TextDocumentDidOpen,
TextDocumentDidChange: lsp.TextDocumentDidChange,
TextDocumentCompletion: lsp.TextDocumentCompletion,
TextDocumentHover: lsp.TextDocumentHover,
TextDocumentDidClose: lsp.TextDocumentDidClose,
TextDocumentCodeAction: lsp.TextDocumentCodeAction,
TextDocumentDefinition: lsp.TextDocumentDefinition,
WorkspaceExecuteCommand: lsp.WorkspaceExecuteCommand,
TextDocumentRename: lsp.TextDocumentRename,
TextDocumentPrepareRename: lsp.TextDocumentPrepareRename,
TextDocumentSignatureHelp: lsp.TextDocumentSignatureHelp,
TextDocumentRangeFormatting: lsp.TextDocumentRangeFormattingFunc,
}
server := server.NewServer(&lspHandler, lsName, false)
@ -72,7 +71,7 @@ func initialize(context *glsp.Context, params *protocol.InitializeParams) (any,
Capabilities: capabilities,
ServerInfo: &protocol.InitializeResultServerInfo{
Name: lsName,
Version: &version,
Version: &Version,
},
}, nil
}

View File

@ -1,149 +0,0 @@
package roothandler
import (
"config-lsp/common"
"config-lsp/utils"
"fmt"
"regexp"
"strings"
protocol "github.com/tliron/glsp/protocol_3_16"
)
type SupportedLanguage string
const (
LanguageSSHConfig SupportedLanguage = "ssh_config"
LanguageSSHDConfig SupportedLanguage = "sshd_config"
LanguageFstab SupportedLanguage = "fstab"
LanguageWireguard SupportedLanguage = "languagewireguard"
LanguageHosts SupportedLanguage = "hosts"
LanguageAliases SupportedLanguage = "aliases"
)
var AllSupportedLanguages = []string{
string(LanguageSSHConfig),
string(LanguageSSHDConfig),
string(LanguageFstab),
string(LanguageWireguard),
string(LanguageHosts),
string(LanguageAliases),
}
type FatalFileNotReadableError struct {
FileURI protocol.DocumentUri
Err error
}
func (e FatalFileNotReadableError) Error() string {
return fmt.Sprintf("Fatal error! config-lsp was unable to read the file (%s); error: %s", e.FileURI, e.Err.Error())
}
type UnsupportedLanguageError struct {
SuggestedLanguage string
}
func (e UnsupportedLanguageError) Error() string {
return fmt.Sprintf("Language '%s' is not supported. Choose one of: %s", e.SuggestedLanguage, strings.Join(AllSupportedLanguages, ", "))
}
type LanguageUndetectableError struct{}
func (e LanguageUndetectableError) Error() string {
return "Please add: '#?lsp.language=<language>' to the top of the file. config-lsp was unable to detect the appropriate language for this file."
}
var valueToLanguageMap = map[string]SupportedLanguage{
"sshd_config": LanguageSSHDConfig,
"sshdconfig": LanguageSSHDConfig,
"ssh_config": LanguageSSHConfig,
"sshconfig": LanguageSSHConfig,
".ssh/config": LanguageSSHConfig,
"~/.ssh/config": LanguageSSHConfig,
"fstab": LanguageFstab,
"etc/fstab": LanguageFstab,
"wireguard": LanguageWireguard,
"wg": LanguageWireguard,
"languagewireguard": LanguageWireguard,
"host": LanguageHosts,
"hosts": LanguageHosts,
"etc/hosts": LanguageHosts,
"aliases": LanguageAliases,
"mailaliases": LanguageAliases,
"etc/aliases": LanguageAliases,
}
var typeOverwriteRegex = regexp.MustCompile(`#\?\s*lsp\.language\s*=\s*(\w+)\s*`)
var wireguardPattern = regexp.MustCompile(`/wg\d+\.conf$`)
var undetectableError = common.ParseError{
Line: 0,
Err: LanguageUndetectableError{},
}
func DetectLanguage(
content string,
advertisedLanguage string,
uri protocol.DocumentUri,
) (SupportedLanguage, error) {
if match := typeOverwriteRegex.FindStringSubmatch(content); match != nil {
suggestedLanguage := strings.ToLower(match[1])
foundLanguage, ok := valueToLanguageMap[suggestedLanguage]
if ok {
return foundLanguage, nil
}
matchIndex := strings.Index(content, match[0])
contentUntilMatch := content[:matchIndex]
return "", common.ParseError{
Line: uint32(utils.CountCharacterOccurrences(contentUntilMatch, '\n')),
Err: UnsupportedLanguageError{
SuggestedLanguage: suggestedLanguage,
},
}
}
if language, ok := valueToLanguageMap[advertisedLanguage]; ok {
return language, nil
}
switch uri {
case "file:///etc/ssh/sshd_config":
fallthrough
case "file:///etc/ssh/ssh_config":
return LanguageSSHDConfig, nil
case "file:///etc/fstab":
return LanguageFstab, nil
// Darwin
case "file:///private/etc/hosts":
fallthrough
case "file:///etc/hosts":
return LanguageHosts, nil
// Darwin
case "file:///private/etc/aliases":
fallthrough
case "file:///etc/aliases":
return LanguageAliases, nil
}
if strings.HasPrefix(uri, "file:///etc/wireguard/") || wireguardPattern.MatchString(uri) {
return LanguageWireguard, nil
}
if strings.HasSuffix(uri, ".ssh/config") {
return LanguageSSHConfig, nil
}
return "", undetectableError
}

View File

@ -1,40 +1,36 @@
package roothandler
package lsp
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"
"config-lsp/root-handler/shared"
utils "config-lsp/root-handler/utils"
"github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16"
)
func TextDocumentCodeAction(context *glsp.Context, params *protocol.CodeActionParams) (any, error) {
language := rootHandler.GetLanguageForDocument(params.TextDocument.URI)
language := shared.Handler.GetLanguageForDocument(params.TextDocument.URI)
if language == nil {
showParseError(
context,
params.TextDocument.URI,
undetectableError,
)
return nil, nil
return utils.FetchAddLanguageActions(params.TextDocument.URI)
}
switch *language {
case LanguageFstab:
case shared.LanguageFstab:
return nil, nil
case LanguageHosts:
case shared.LanguageHosts:
return hosts.TextDocumentCodeAction(context, params)
case LanguageSSHDConfig:
case shared.LanguageSSHDConfig:
return nil, nil
case LanguageSSHConfig:
case shared.LanguageSSHConfig:
return sshconfig.TextDocumentCodeAction(context, params)
case LanguageWireguard:
case shared.LanguageWireguard:
return wireguard.TextDocumentCodeAction(context, params)
case LanguageAliases:
case shared.LanguageAliases:
return aliases.TextDocumentCodeAction(context, params)
}

View File

@ -1,4 +1,4 @@
package roothandler
package lsp
import (
aliases "config-lsp/handlers/aliases/lsp"
@ -7,36 +7,30 @@ import (
sshconfig "config-lsp/handlers/ssh_config/lsp"
sshdconfig "config-lsp/handlers/sshd_config/lsp"
wireguard "config-lsp/handlers/wireguard/lsp"
"config-lsp/root-handler/shared"
"github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16"
)
func TextDocumentCompletion(context *glsp.Context, params *protocol.CompletionParams) (any, error) {
language := rootHandler.GetLanguageForDocument(params.TextDocument.URI)
language := shared.Handler.GetLanguageForDocument(params.TextDocument.URI)
if language == nil {
showParseError(
context,
params.TextDocument.URI,
undetectableError,
)
return nil, undetectableError.Err
return nil, nil
}
switch *language {
case LanguageFstab:
case shared.LanguageFstab:
return fstab.TextDocumentCompletion(context, params)
case LanguageSSHDConfig:
case shared.LanguageSSHDConfig:
return sshdconfig.TextDocumentCompletion(context, params)
case LanguageSSHConfig:
case shared.LanguageSSHConfig:
return sshconfig.TextDocumentCompletion(context, params)
case LanguageWireguard:
case shared.LanguageWireguard:
return wireguard.TextDocumentCompletion(context, params)
case LanguageHosts:
case shared.LanguageHosts:
return hosts.TextDocumentCompletion(context, params)
case LanguageAliases:
case shared.LanguageAliases:
return aliases.TextDocumentCompletion(context, params)
}

View File

@ -1,39 +1,35 @@
package roothandler
package lsp
import (
aliases "config-lsp/handlers/aliases/lsp"
sshconfig "config-lsp/handlers/ssh_config/lsp"
sshdconfig "config-lsp/handlers/sshd_config/lsp"
"config-lsp/root-handler/shared"
"config-lsp/root-handler/utils"
"github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16"
)
func TextDocumentDefinition(context *glsp.Context, params *protocol.DefinitionParams) (any, error) {
language := rootHandler.GetLanguageForDocument(params.TextDocument.URI)
language := shared.Handler.GetLanguageForDocument(params.TextDocument.URI)
if language == nil {
showParseError(
context,
params.TextDocument.URI,
undetectableError,
)
return nil, undetectableError.Err
return nil, utils.LanguageUndetectableError{}
}
switch *language {
case LanguageHosts:
case shared.LanguageHosts:
return nil, nil
case LanguageSSHDConfig:
case shared.LanguageSSHDConfig:
return sshdconfig.TextDocumentDefinition(context, params)
case LanguageSSHConfig:
case shared.LanguageSSHConfig:
return sshconfig.TextDocumentDefinition(context, params)
case LanguageFstab:
case shared.LanguageFstab:
return nil, nil
case LanguageWireguard:
case shared.LanguageWireguard:
return nil, nil
case LanguageAliases:
case shared.LanguageAliases:
return aliases.TextDocumentDefinition(context, params)
}

View File

@ -1,4 +1,4 @@
package roothandler
package lsp
import (
aliases "config-lsp/handlers/aliases/lsp"
@ -7,15 +7,14 @@ import (
sshconfig "config-lsp/handlers/ssh_config/lsp"
sshdconfig "config-lsp/handlers/sshd_config/lsp"
wireguard "config-lsp/handlers/wireguard/lsp"
"config-lsp/root-handler/shared"
"github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16"
)
func TextDocumentDidChange(context *glsp.Context, params *protocol.DidChangeTextDocumentParams) error {
language := rootHandler.GetLanguageForDocument(params.TextDocument.URI)
language := shared.Handler.GetLanguageForDocument(params.TextDocument.URI)
if language == nil {
content := params.ContentChanges[0].(protocol.TextDocumentContentChangeEventWhole).Text
newLanguage, err := initFile(
context,
@ -28,6 +27,7 @@ func TextDocumentDidChange(context *glsp.Context, params *protocol.DidChangeText
return err
}
if newLanguage != language {
language = newLanguage
params := &protocol.DidOpenTextDocumentParams{
@ -40,33 +40,33 @@ func TextDocumentDidChange(context *glsp.Context, params *protocol.DidChangeText
}
switch *language {
case LanguageFstab:
case shared.LanguageFstab:
return fstab.TextDocumentDidOpen(context, params)
case LanguageSSHDConfig:
case shared.LanguageSSHDConfig:
return sshdconfig.TextDocumentDidOpen(context, params)
case LanguageSSHConfig:
case shared.LanguageSSHConfig:
return sshconfig.TextDocumentDidOpen(context, params)
case LanguageWireguard:
case shared.LanguageWireguard:
return wireguard.TextDocumentDidOpen(context, params)
case LanguageHosts:
case shared.LanguageHosts:
return hosts.TextDocumentDidOpen(context, params)
case LanguageAliases:
case shared.LanguageAliases:
return aliases.TextDocumentDidOpen(context, params)
}
}
switch *language {
case LanguageFstab:
case shared.LanguageFstab:
return fstab.TextDocumentDidChange(context, params)
case LanguageSSHDConfig:
case shared.LanguageSSHDConfig:
return sshdconfig.TextDocumentDidChange(context, params)
case LanguageSSHConfig:
case shared.LanguageSSHConfig:
return sshconfig.TextDocumentDidChange(context, params)
case LanguageWireguard:
case shared.LanguageWireguard:
return wireguard.TextDocumentDidChange(context, params)
case LanguageHosts:
case shared.LanguageHosts:
return hosts.TextDocumentDidChange(context, params)
case LanguageAliases:
case shared.LanguageAliases:
return aliases.TextDocumentDidChange(context, params)
}

View File

@ -1,4 +1,4 @@
package roothandler
package lsp
import (
aliases "config-lsp/handlers/aliases/lsp"
@ -7,39 +7,34 @@ import (
sshconfig "config-lsp/handlers/ssh_config/lsp"
sshdconfig "config-lsp/handlers/sshd_config/lsp"
wireguard "config-lsp/handlers/wireguard/lsp"
"config-lsp/root-handler/shared"
"github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16"
)
func TextDocumentDidClose(context *glsp.Context, params *protocol.DidCloseTextDocumentParams) error {
language := rootHandler.GetLanguageForDocument(params.TextDocument.URI)
language := shared.Handler.GetLanguageForDocument(params.TextDocument.URI)
if language == nil {
showParseError(
context,
params.TextDocument.URI,
undetectableError,
)
return undetectableError.Err
return nil
}
delete(openedFiles, params.TextDocument.URI)
rootHandler.RemoveDocument(params.TextDocument.URI)
delete(shared.OpenedFiles, params.TextDocument.URI)
delete(shared.LanguagesOverwrites, params.TextDocument.URI)
shared.Handler.RemoveDocument(params.TextDocument.URI)
switch *language {
case LanguageSSHDConfig:
case shared.LanguageSSHDConfig:
return sshdconfig.TextDocumentDidClose(context, params)
case LanguageSSHConfig:
case shared.LanguageSSHConfig:
return sshconfig.TextDocumentDidClose(context, params)
case LanguageFstab:
case shared.LanguageFstab:
return fstab.TextDocumentDidClose(context, params)
case LanguageWireguard:
case shared.LanguageWireguard:
return wireguard.TextDocumentDidClose(context, params)
case LanguageHosts:
case shared.LanguageHosts:
return hosts.TextDocumentDidClose(context, params)
case LanguageAliases:
case shared.LanguageAliases:
return aliases.TextDocumentDidClose(context, params)
default:
}

View File

@ -1,7 +1,9 @@
package roothandler
package lsp
import (
"config-lsp/common"
"config-lsp/root-handler/shared"
"config-lsp/root-handler/utils"
"fmt"
aliases "config-lsp/handlers/aliases/lsp"
@ -32,60 +34,43 @@ func TextDocumentDidOpen(context *glsp.Context, params *protocol.DidOpenTextDocu
}
switch *language {
case LanguageFstab:
case shared.LanguageFstab:
return fstab.TextDocumentDidOpen(context, params)
case LanguageSSHDConfig:
case shared.LanguageSSHDConfig:
return sshdconfig.TextDocumentDidOpen(context, params)
case LanguageSSHConfig:
case shared.LanguageSSHConfig:
return sshconfig.TextDocumentDidOpen(context, params)
case LanguageWireguard:
case shared.LanguageWireguard:
return wireguard.TextDocumentDidOpen(context, params)
case LanguageHosts:
case shared.LanguageHosts:
return hosts.TextDocumentDidOpen(context, params)
case LanguageAliases:
case shared.LanguageAliases:
return aliases.TextDocumentDidOpen(context, params)
}
panic(fmt.Sprintf("unexpected roothandler.SupportedLanguage: %#v", language))
}
func showParseError(
context *glsp.Context,
uri protocol.DocumentUri,
err common.ParseError,
) {
context.Notify(
"window/showMessage",
protocol.ShowMessageParams{
Type: protocol.MessageTypeError,
Message: err.Err.Error(),
},
)
}
func initFile(
context *glsp.Context,
content string,
uri protocol.DocumentUri,
advertisedLanguage string,
) (*SupportedLanguage, error) {
language, err := DetectLanguage(content, advertisedLanguage, uri)
) (*shared.SupportedLanguage, error) {
language, err := utils.DetectLanguage(content, advertisedLanguage, uri)
if err != nil {
parseError := err.(common.ParseError)
showParseError(
context,
uri,
parseError,
)
utils.NotifyLanguageUndetectable(context, uri)
return nil, parseError.Err
return nil, utils.LanguageUndetectableError{}
} else {
utils.NotifyDetectedLanguage(context, uri, language)
}
openedFiles[uri] = struct{}{}
shared.OpenedFiles[uri] = struct{}{}
// Everything okay, now we can handle the file
rootHandler.AddDocument(uri, language)
shared.Handler.AddDocument(uri, language)
return &language, nil
}

View File

@ -1,4 +1,4 @@
package roothandler
package lsp
import (
aliases "config-lsp/handlers/aliases/lsp"
@ -7,36 +7,30 @@ import (
sshconfig "config-lsp/handlers/ssh_config/lsp"
sshdconfig "config-lsp/handlers/sshd_config/lsp"
wireguard "config-lsp/handlers/wireguard/lsp"
"config-lsp/root-handler/shared"
"github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16"
)
func TextDocumentHover(context *glsp.Context, params *protocol.HoverParams) (*protocol.Hover, error) {
language := rootHandler.GetLanguageForDocument(params.TextDocument.URI)
language := shared.Handler.GetLanguageForDocument(params.TextDocument.URI)
if language == nil {
showParseError(
context,
params.TextDocument.URI,
undetectableError,
)
return nil, undetectableError.Err
return nil, nil
}
switch *language {
case LanguageHosts:
case shared.LanguageHosts:
return hosts.TextDocumentHover(context, params)
case LanguageSSHDConfig:
case shared.LanguageSSHDConfig:
return sshdconfig.TextDocumentHover(context, params)
case LanguageSSHConfig:
case shared.LanguageSSHConfig:
return sshconfig.TextDocumentHover(context, params)
case LanguageFstab:
case shared.LanguageFstab:
return fstab.TextDocumentHover(context, params)
case LanguageWireguard:
case shared.LanguageWireguard:
return wireguard.TextDocumentHover(context, params)
case LanguageAliases:
case shared.LanguageAliases:
return aliases.TextDocumentHover(context, params)
}

View File

@ -1,8 +1,10 @@
package roothandler
package lsp
import (
aliases "config-lsp/handlers/aliases/lsp"
sshconfig "config-lsp/handlers/ssh_config/lsp"
"config-lsp/root-handler/shared"
"config-lsp/root-handler/utils"
"github.com/tliron/glsp"
@ -10,30 +12,24 @@ import (
)
func TextDocumentPrepareRename(context *glsp.Context, params *protocol.PrepareRenameParams) (any, error) {
language := rootHandler.GetLanguageForDocument(params.TextDocument.URI)
language := shared.Handler.GetLanguageForDocument(params.TextDocument.URI)
if language == nil {
showParseError(
context,
params.TextDocument.URI,
undetectableError,
)
return nil, undetectableError.Err
return nil, utils.LanguageUndetectableError{}
}
switch *language {
case LanguageHosts:
case shared.LanguageHosts:
return nil, nil
case LanguageSSHDConfig:
case shared.LanguageSSHDConfig:
return nil, nil
case LanguageSSHConfig:
case shared.LanguageSSHConfig:
return sshconfig.TextDocumentPrepareRename(context, params)
case LanguageFstab:
case shared.LanguageFstab:
return nil, nil
case LanguageWireguard:
case shared.LanguageWireguard:
return nil, nil
case LanguageAliases:
case shared.LanguageAliases:
return aliases.TextDocumentPrepareRename(context, params)
}

View File

@ -1,8 +1,10 @@
package roothandler
package lsp
import (
sshconfig "config-lsp/handlers/ssh_config/lsp"
sshdconfig "config-lsp/handlers/sshd_config/lsp"
"config-lsp/root-handler/shared"
"config-lsp/root-handler/utils"
"github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16"
@ -12,30 +14,24 @@ func TextDocumentRangeFormattingFunc(
context *glsp.Context,
params *protocol.DocumentRangeFormattingParams,
) ([]protocol.TextEdit, error) {
language := rootHandler.GetLanguageForDocument(params.TextDocument.URI)
language := shared.Handler.GetLanguageForDocument(params.TextDocument.URI)
if language == nil {
showParseError(
context,
params.TextDocument.URI,
undetectableError,
)
return nil, undetectableError.Err
return nil, utils.LanguageUndetectableError{}
}
switch *language {
case LanguageHosts:
case shared.LanguageHosts:
return nil, nil
case LanguageSSHDConfig:
case shared.LanguageSSHDConfig:
return sshdconfig.TextDocumentRangeFormatting(context, params)
case LanguageSSHConfig:
case shared.LanguageSSHConfig:
return sshconfig.TextDocumentRangeFormatting(context, params)
case LanguageFstab:
case shared.LanguageFstab:
return nil, nil
case LanguageWireguard:
case shared.LanguageWireguard:
return nil, nil
case LanguageAliases:
case shared.LanguageAliases:
return nil, nil
}

View File

@ -1,38 +1,34 @@
package roothandler
package lsp
import (
aliases "config-lsp/handlers/aliases/lsp"
sshconfig "config-lsp/handlers/ssh_config/lsp"
"config-lsp/root-handler/shared"
"config-lsp/root-handler/utils"
"github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16"
)
func TextDocumentRename(context *glsp.Context, params *protocol.RenameParams) (*protocol.WorkspaceEdit, error) {
language := rootHandler.GetLanguageForDocument(params.TextDocument.URI)
language := shared.Handler.GetLanguageForDocument(params.TextDocument.URI)
if language == nil {
showParseError(
context,
params.TextDocument.URI,
undetectableError,
)
return nil, undetectableError.Err
return nil, utils.LanguageUndetectableError{}
}
switch *language {
case LanguageHosts:
case shared.LanguageHosts:
return nil, nil
case LanguageSSHDConfig:
case shared.LanguageSSHDConfig:
return nil, nil
case LanguageSSHConfig:
case shared.LanguageSSHConfig:
return sshconfig.TextDocumentRename(context, params)
case LanguageFstab:
case shared.LanguageFstab:
return nil, nil
case LanguageWireguard:
case shared.LanguageWireguard:
return nil, nil
case LanguageAliases:
case shared.LanguageAliases:
return aliases.TextDocumentRename(context, params)
}

View File

@ -1,39 +1,33 @@
package roothandler
package lsp
import (
aliases "config-lsp/handlers/aliases/lsp"
sshconfig "config-lsp/handlers/ssh_config/lsp"
sshdconfig "config-lsp/handlers/sshd_config/lsp"
"config-lsp/root-handler/shared"
"github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16"
)
func TextDocumentSignatureHelp(context *glsp.Context, params *protocol.SignatureHelpParams) (*protocol.SignatureHelp, error) {
language := rootHandler.GetLanguageForDocument(params.TextDocument.URI)
language := shared.Handler.GetLanguageForDocument(params.TextDocument.URI)
if language == nil {
showParseError(
context,
params.TextDocument.URI,
undetectableError,
)
return nil, undetectableError.Err
return nil, nil
}
switch *language {
case LanguageHosts:
case shared.LanguageHosts:
return nil, nil
case LanguageSSHDConfig:
case shared.LanguageSSHDConfig:
return sshdconfig.TextDocumentSignatureHelp(context, params)
case LanguageSSHConfig:
case shared.LanguageSSHConfig:
return sshconfig.TextDocumentSignatureHelp(context, params)
case LanguageFstab:
case shared.LanguageFstab:
return nil, nil
case LanguageWireguard:
case shared.LanguageWireguard:
return nil, nil
case LanguageAliases:
case shared.LanguageAliases:
return aliases.TextDocumentSignatureHelp(context, params)
}

View File

@ -1,4 +1,4 @@
package roothandler
package lsp
import (
aliases "config-lsp/handlers/aliases/lsp"

View File

@ -1,5 +0,0 @@
package roothandler
import protocol "github.com/tliron/glsp/protocol_3_16"
var openedFiles = make(map[protocol.DocumentUri]struct{})

View File

@ -0,0 +1,21 @@
package shared
type SupportedLanguage string
const (
LanguageSSHConfig SupportedLanguage = "ssh_config"
LanguageSSHDConfig SupportedLanguage = "sshd_config"
LanguageFstab SupportedLanguage = "fstab"
LanguageWireguard SupportedLanguage = "languagewireguard"
LanguageHosts SupportedLanguage = "hosts"
LanguageAliases SupportedLanguage = "aliases"
)
var AllSupportedLanguages = []string{
string(LanguageSSHConfig),
string(LanguageSSHDConfig),
string(LanguageFstab),
string(LanguageWireguard),
string(LanguageHosts),
string(LanguageAliases),
}

View File

@ -0,0 +1,16 @@
package shared
import (
protocol "github.com/tliron/glsp/protocol_3_16"
)
type LanguageOverwrite struct {
Language SupportedLanguage
// The start of the overwrite
Raw string
Line uint32
Character uint32
}
var LanguagesOverwrites = map[protocol.DocumentUri]LanguageOverwrite{}

View File

@ -1,10 +1,12 @@
package roothandler
package shared
import (
protocol "github.com/tliron/glsp/protocol_3_16"
)
var rootHandler RootHandler
var Handler RootHandler
var OpenedFiles = make(map[protocol.DocumentUri]struct{})
type RootHandler struct {
languageMap map[protocol.DocumentUri]SupportedLanguage

View File

@ -0,0 +1,44 @@
package utils
import (
"config-lsp/root-handler/shared"
"fmt"
protocol "github.com/tliron/glsp/protocol_3_16"
)
func FetchAddLanguageActions(uri protocol.DocumentUri) ([]protocol.CodeAction, error) {
actions := make([]protocol.CodeAction, 0, len(shared.AllSupportedLanguages))
kind := protocol.CodeActionKindQuickFix
isPreferred := true
for _, language := range shared.AllSupportedLanguages {
actions = append(actions, protocol.CodeAction{
Title: fmt.Sprintf("Use %s for this file", language),
Kind: &kind,
IsPreferred: &isPreferred,
Edit: &protocol.WorkspaceEdit{
Changes: map[protocol.DocumentUri][]protocol.TextEdit{
uri: {
{
Range: protocol.Range{
Start: protocol.Position{
Line: 0,
Character: 0,
},
End: protocol.Position{
Line: 0,
Character: 0,
},
},
NewText: fmt.Sprintf("#?lsp.language=%s\n", language),
},
},
},
},
})
}
return actions, nil
}

View File

@ -0,0 +1,158 @@
package utils
import (
"config-lsp/common"
"config-lsp/root-handler/shared"
"config-lsp/utils"
"fmt"
"path"
"regexp"
"strings"
protocol "github.com/tliron/glsp/protocol_3_16"
)
type UnsupportedLanguageError struct {
SuggestedLanguage string
}
func (e UnsupportedLanguageError) Error() string {
return fmt.Sprintf("Language '%s' is not supported. Choose one of: %s", e.SuggestedLanguage, strings.Join(shared.AllSupportedLanguages, ", "))
}
type LanguageUndetectableError struct{}
func (e LanguageUndetectableError) Error() string {
return "Please add: '#?lsp.language=<language>' to the top of the file. config-lsp was unable to detect the appropriate language for this file."
}
var valueToLanguageMap = map[string]shared.SupportedLanguage{
"sshd_config": shared.LanguageSSHDConfig,
"sshdconfig": shared.LanguageSSHDConfig,
"ssh_config": shared.LanguageSSHConfig,
"sshconfig": shared.LanguageSSHConfig,
".ssh/config": shared.LanguageSSHConfig,
"~/.ssh/config": shared.LanguageSSHConfig,
"fstab": shared.LanguageFstab,
"etc/fstab": shared.LanguageFstab,
"wireguard": shared.LanguageWireguard,
"wg": shared.LanguageWireguard,
"languagewireguard": shared.LanguageWireguard,
"host": shared.LanguageHosts,
"hosts": shared.LanguageHosts,
"etc/hosts": shared.LanguageHosts,
"aliases": shared.LanguageAliases,
"mailaliases": shared.LanguageAliases,
"etc/aliases": shared.LanguageAliases,
}
var filenameToLanguageMap = map[string]shared.SupportedLanguage{
"sshd_config": shared.LanguageSSHDConfig,
"sshdconfig": shared.LanguageSSHDConfig,
"sshd": shared.LanguageSSHDConfig,
"sshd_conf": shared.LanguageSSHDConfig,
"sshdconf": shared.LanguageSSHDConfig,
"ssh_config": shared.LanguageSSHConfig,
"sshconfig": shared.LanguageSSHConfig,
"ssh": shared.LanguageSSHConfig,
"ssh_conf": shared.LanguageSSHConfig,
"sshconf": shared.LanguageSSHConfig,
"fstab": shared.LanguageFstab,
"hosts": shared.LanguageHosts,
"aliases": shared.LanguageAliases,
"mailaliases": shared.LanguageAliases,
}
var typeOverwriteRegex = regexp.MustCompile(`#\?\s*lsp\.language\s*=\s*(\w+)\s*`)
var wireguardPattern = regexp.MustCompile(`wg(\d+)?(\.conf)?$`)
var undetectableError = common.ParseError{
Line: 0,
Err: LanguageUndetectableError{},
}
func DetectLanguage(
content string,
advertisedLanguage string,
uri protocol.DocumentUri,
) (shared.SupportedLanguage, error) {
if match := typeOverwriteRegex.FindStringSubmatchIndex(content); match != nil {
text := content[match[0]:match[1]]
language := content[match[2]:match[3]]
suggestedLanguage := strings.ToLower(language)
foundLanguage, ok := valueToLanguageMap[suggestedLanguage]
contentUntilMatch := content[:match[0]]
if ok {
line := uint32(utils.CountCharacterOccurrences(contentUntilMatch, '\n'))
shared.LanguagesOverwrites[uri] = shared.LanguageOverwrite{
Language: foundLanguage,
Raw: text,
Line: line,
Character: uint32(match[0]),
}
return foundLanguage, nil
}
return "", common.ParseError{
Line: uint32(utils.CountCharacterOccurrences(contentUntilMatch, '\n')),
Err: UnsupportedLanguageError{
SuggestedLanguage: suggestedLanguage,
},
}
}
if language, ok := valueToLanguageMap[advertisedLanguage]; ok {
return language, nil
}
switch uri {
case "file:///etc/ssh/sshd_config":
fallthrough
case "file:///etc/ssh/ssh_config":
return shared.LanguageSSHDConfig, nil
case "file:///etc/fstab":
return shared.LanguageFstab, nil
// Darwin
case "file:///private/etc/hosts":
fallthrough
case "file:///etc/hosts":
return shared.LanguageHosts, nil
// Darwin
case "file:///private/etc/aliases":
fallthrough
case "file:///etc/aliases":
return shared.LanguageAliases, nil
}
filename := path.Base(string(uri))
if language, found := filenameToLanguageMap[filename]; found {
return language, nil
}
if strings.HasPrefix(uri, "file:///etc/wireguard/") || wireguardPattern.MatchString(uri) {
return shared.LanguageWireguard, nil
}
if strings.HasSuffix(uri, ".ssh/config") {
return shared.LanguageSSHConfig, nil
}
return "", undetectableError
}

View File

@ -0,0 +1,46 @@
package utils
import (
"config-lsp/root-handler/shared"
"github.com/tliron/glsp"
protocol "github.com/tliron/glsp/protocol_3_16"
)
type lspNotification struct {
Uri string
}
type lspDetectedLanguage struct {
lspNotification
Language string
}
func NotifyLanguageUndetectable(context *glsp.Context, uri protocol.DocumentUri) {
go context.Notify(
"$/config-lsp/languageUndetectable",
lspNotification{
Uri: string(uri),
},
)
go context.Notify(
"window/showMessage",
protocol.ShowMessageParams{
Type: protocol.MessageTypeError,
Message: "config-lsp was unable to detect the appropriate language for this file. Please add: '#?lsp.language=<language>'.",
},
)
}
func NotifyDetectedLanguage(context *glsp.Context, uri protocol.DocumentUri, language shared.SupportedLanguage) {
go context.Notify(
"$/config-lsp/detectedLanguage",
lspDetectedLanguage{
lspNotification: lspNotification{
Uri: string(uri),
},
Language: string(language),
},
)
}

View File

@ -2,7 +2,7 @@
"name": "config-lsp",
"description": "Language Features (completions, diagnostics, etc.) for your config files: gitconfig, fstab, aliases, hosts, wireguard, ssh_config, sshd_config, and more to come!",
"author": "Myzel394",
"version": "0.1.1",
"version": "0.1.2",
"repository": {
"type": "git",
"url": "https://github.com/Myzel394/config-lsp"
@ -13,6 +13,62 @@
"Formatters"
],
"preview": true,
"contributes": {
"languages": [
{
"id": "sshconfig",
"extensions": ["sshconfig", "ssh_config"],
"aliases": ["SSH Config", "sshconfig"],
"filenames": ["sshconfig", "ssh_config"],
"filenamePatterns": ["~/.ssh/config", "**/sshconfig", "**/ssh_config"]
},
{
"id": "sshdconfig",
"extensions": ["sshdconfig", "sshd_config"],
"aliases": ["SSH Daemon Config", "sshdconfig"],
"filenames": ["sshdconfig", "sshd_config"],
"filenamePatterns": ["/etc/ssh/sshd_config", "**/sshdconfig", "**/sshd_config"]
},
{
"id": "aliases",
"extensions": ["aliases", "mailaliases"],
"aliases": ["Mail Aliases", "aliases", "mailaliases"],
"filenames": ["aliases", "mailaliases"],
"filenamePatterns": ["/etc/aliases", "**/aliases", "**/mailaliases"]
},
{
"id": "fstab",
"extensions": ["fstab"],
"aliases": ["fstab"],
"filenames": ["fstab"],
"filenamePatterns": ["/etc/fstab", "**/fstab", "**/etc/fstab"]
},
{
"id": "hosts",
"extensions": ["hosts"],
"aliases": ["hosts"],
"filenames": ["hosts"],
"filenamePatterns": ["/etc/hosts", "**/hosts", "**/etc/hosts"]
},
{
"id": "wireguard",
"extensions": ["wireguard", "wg"],
"aliases": ["WireGuard", "wireguard", "wg"],
"filenames": ["wireguard", "wg0.conf", "wg1.conf", "wg0", "wg1"],
"filenamePatterns": ["/etc/wireguard/*.conf", "**/wireguard", "**/wireguard.conf"]
}
]
},
"activationEvents": [
"onLanguage:plaintext",
"onLanguage:yaml",
"onLanguage:sshconfig",
"onLanguage:sshdconfig",
"onLanguage:aliases",
"onLanguage:fstab",
"onLanguage:hosts",
"onLanguage:wireguard"
],
"sponsor": {
"url": "https://github.com/Myzel394/contact-me"
},
@ -50,16 +106,12 @@
"engines": {
"vscode": "^1.74.0"
},
"activationEvents": [
"onLanguage:plaintext",
"onLanguage:yaml"
],
"main": "./out/extension.js",
"scripts": {
"compile": "node esbuild.js",
"compile:prod": "node esbuild.js --production",
"watch": "tsc -b -w",
"lint": "eslint ./src --ext .ts,.tsx"
"lint": "eslint ./src"
},
"dependencies": {
"vscode-languageclient": "^9.0.1",

View File

@ -0,0 +1,34 @@
import { GenericNotificationHandler } from "vscode-languageclient";
import * as vscode from "vscode";
const ACTION_SELECT_LANGUAGE = "Select Language";
const ACTION_DISABLE = "Ignore for this file";
const ignoredFiled = new Set<string>();
export const onUndetectable: GenericNotificationHandler = async (params: LSPLanguageUndetectable) => {
if (ignoredFiled.has(params.Uri)) {
return;
}
const result = await vscode.window.showWarningMessage(
"config-lsp was unable to detect the appropriate language for this file",
{
detail: "Either select a language or add '#?lsp.language=<language>' to the top of the file",
},
ACTION_SELECT_LANGUAGE,
ACTION_DISABLE,
)
switch (result) {
case ACTION_SELECT_LANGUAGE:
vscode.commands.executeCommand("workbench.action.editor.changeLanguageMode");
break;
case ACTION_DISABLE:
ignoredFiled.add(params.Uri);
break;
undefined:
break;
}
}

View File

@ -0,0 +1,6 @@
interface LSPNotification {
Uri: string;
}
interface LSPLanguageUndetectable extends LSPNotification {}

View File

@ -1,32 +1,30 @@
import * as path from "path"
import { ExtensionContext, workspace } from 'vscode';
import * as path from "path";
import { ExtensionContext, workspace } from "vscode";
import {
Executable,
LanguageClient,
type LanguageClientOptions,
type ServerOptions,
} from 'vscode-languageclient/node';
} from "vscode-languageclient/node";
import { onUndetectable } from "./events/on-undetectable";
const IS_DEBUG = process.env.VSCODE_DEBUG_MODE === 'true' || process.env.NODE_ENV === 'development';
const IS_DEBUG =
process.env.VSCODE_DEBUG_MODE === "true" ||
process.env.NODE_ENV === "development";
let client: LanguageClient;
export function activate(context: ExtensionContext) {
export async function activate({subscriptions}: ExtensionContext) {
console.info("config-lsp activated");
const initOptions = workspace.getConfiguration('config-lsp');
const initOptions = workspace.getConfiguration("config-lsp");
const clientOptions: LanguageClientOptions = {
documentSelector: [
{
scheme: 'file',
language: 'plaintext',
pattern: "**/{config,sshconfig,sshd_config,sshdconfig,fstab,hosts,aliases}",
},
// Some configs seem to be incorrectly detected as yaml
{
scheme: 'file',
language: 'yaml',
pattern: "**/{config,sshconfig,sshd_config,sshdconfig,fstab,hosts,aliases}",
},
{language: "sshconfig"},
{language: "sshdconfig"},
{language: "aliases"},
{language: "fstab"},
{language: "hosts"},
{language: "wireguard"},
],
initializationOptions: initOptions,
};
@ -35,38 +33,28 @@ export function activate(context: ExtensionContext) {
console.info(`Found config-lsp path at ${path}`);
const run: Executable = {
command: getBundledPath() ,
}
};
const serverOptions: ServerOptions = {
run,
debug: run,
}
};
client = new LanguageClient(
'config-lsp',
"config-lsp",
serverOptions,
clientOptions,
IS_DEBUG,
IS_DEBUG
);
console.info("Starting config-lsp...");
await client.start();
console.info("Started config-lsp");
client.start();
console.info("config-lsp started");
// const serverOptions: ServerOptions = {
// }
//
// // Create the language client and start the client.
// client = new LanguageClient(
// 'languageServerExample',
// clientOptions
// );
//
// // Start the client. This will also launch the server
// client.start();
subscriptions.push(client.onNotification("$/config-lsp/languageUndetectable", onUndetectable))
}
function getBundledPath(): string {
const filePath = path.resolve(__dirname, "config-lsp")
const filePath = path.resolve(__dirname, "config-lsp");
return filePath;
}