Skip to content

Commit

Permalink
feat(yamlls): add config to enable yamlls per filetype
Browse files Browse the repository at this point in the history
  • Loading branch information
qvalentin committed Jul 21, 2024
1 parent 6cab70b commit 712e74f
Show file tree
Hide file tree
Showing 12 changed files with 160 additions and 42 deletions.
56 changes: 29 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,37 +10,37 @@
\/ /_/ \___|_|_| |_| |_\____/___/
</pre>

## Helm Language Server
## Helm Language Server

Helm-ls is a [helm](https://github.com/helm/helm) language server protocol [LSP](https://microsoft.github.io/language-server-protocol/) implementation.

<!-- vim-markdown-toc GFM -->

* [Demo](#demo)
* [Getting Started](#getting-started)
* [Installation with a package manager](#installation-with-a-package-manager)
* [Homebrew](#homebrew)
* [Nix](#nix)
* [Arch Linux](#arch-linux)
* [Windows](#windows)
* [mason (neovim)](#mason-neovim)
* [Manual download](#manual-download)
* [Make it executable](#make-it-executable)
* [Integration with yaml-language-server](#integration-with-yaml-language-server)
* [Configuration options](#configuration-options)
* [General](#general)
* [Values Files](#values-files)
* [yaml-language-server config](#yaml-language-server-config)
* [Default Configuration](#default-configuration)
* [Editor Config examples](#editor-config-examples)
* [Neovim](#neovim-using-nvim-lspconfig)
* [Vim Helm Plugin](#vim-helm-plugin)
* [nvim-lspconfig setup](#nvim-lspconfig-setup)
* [coc.nvim setup](#cocnvim-setup)
* [VSCode](#vscode)
* [Emacs eglot setup](#emacs-eglot-setup)
* [Contributing](#contributing)
* [License](#license)
- [Demo](#demo)
- [Getting Started](#getting-started)
- [Installation with a package manager](#installation-with-a-package-manager)
- [Homebrew](#homebrew)
- [Nix](#nix)
- [Arch Linux](#arch-linux)
- [Windows](#windows)
- [mason (neovim)](#mason-neovim)
- [Manual download](#manual-download)
- [Make it executable](#make-it-executable)
- [Integration with yaml-language-server](#integration-with-yaml-language-server)
- [Configuration options](#configuration-options)
- [General](#general)
- [Values Files](#values-files)
- [yaml-language-server config](#yaml-language-server-config)
- [Default Configuration](#default-configuration)
- [Editor Config examples](#editor-config-examples)
- [Neovim](#neovim)
- [Vim Helm Plugin](#vim-helm-plugin)
- [nvim-lspconfig setup](#nvim-lspconfig-setup)
- [coc.nvim setup](#cocnvim-setup)
- [VSCode](#vscode)
- [Emacs eglot setup](#emacs-eglot-setup)
- [Contributing](#contributing)
- [License](#license)

<!-- vim-markdown-toc -->

Expand Down Expand Up @@ -78,7 +78,7 @@ You can install it from the [aur](https://aur.archlinux.org/packages/helm-ls/) u

```bash
yay -S helm-ls
# or
# or
yay -S helm-ls-bin
```

Expand Down Expand Up @@ -157,6 +157,7 @@ You can configure helm-ls with lsp workspace configurations.
### yaml-language-server config
- **Enable yaml-language-server**: Toggle support of this feature.
- **EnabledForFilesGlob**: A glob pattern defining for which files yaml-language-server should be enabled.
- **Path to yaml-language-server**: Specify the executable location.
- **Diagnostics Settings**:
Expand All @@ -181,6 +182,7 @@ settings = {
},
yamlls = {
enabled = true,
enabledForFilesGlob = "*.{yaml,yml}",
diagnosticsLimit = 50,
showDiagnosticsDirectly = false,
path = "yaml-language-server",
Expand Down
2 changes: 1 addition & 1 deletion internal/adapter/yamlls/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
)

func (yamllsConnector Connector) CallCompletion(ctx context.Context, params *lsp.CompletionParams) (*lsp.CompletionList, error) {
if yamllsConnector.server == nil {
if !yamllsConnector.shouldRun(params.TextDocumentPositionParams.TextDocument.URI) {
return &lsp.CompletionList{}, nil
}

Expand Down
5 changes: 5 additions & 0 deletions internal/adapter/yamlls/diagnostics.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,23 +32,28 @@ func (c Connector) PublishDiagnostics(ctx context.Context, params *protocol.Publ

func filterDiagnostics(diagnostics []lsp.Diagnostic, ast *sitter.Tree, content string) (filtered []lsp.Diagnostic) {
filtered = []lsp.Diagnostic{}

for _, diagnostic := range diagnostics {
node := lsplocal.NodeAtPosition(ast, diagnostic.Range.Start)
childNode := lsplocal.FindRelevantChildNode(ast.RootNode(), lsplocal.GetSitterPointForLspPos(diagnostic.Range.Start))

if node.Type() == "text" && childNode.Type() == "text" {
logger.Debug("Diagnostic", diagnostic)
logger.Debug("Node", node.Content([]byte(content)))

if diagnisticIsRelevant(diagnostic, childNode) {
diagnostic.Message = "Yamlls: " + diagnostic.Message
filtered = append(filtered, diagnostic)
}
}
}

return filtered
}

func diagnisticIsRelevant(diagnostic lsp.Diagnostic, node *sitter.Node) bool {
logger.Debug("Checking if diagnostic is relevant", diagnostic.Message)

switch diagnostic.Message {
case "Map keys must be unique":
return !lsplocal.IsInElseBranch(node)
Expand Down
17 changes: 13 additions & 4 deletions internal/adapter/yamlls/documentSync.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,17 @@ func (yamllsConnector Connector) InitiallySyncOpenDocuments(docs []*lsplocal.Doc
if yamllsConnector.server == nil {
return
}

for _, doc := range docs {
if !doc.IsOpen {
continue
}

doc.IsYaml = lsplocal.IsYamlDocument(doc.URI, yamllsConnector.config)
if !yamllsConnector.isRelevantFile(doc.URI) {
continue
}

yamllsConnector.DocumentDidOpen(doc.Ast, lsp.DidOpenTextDocumentParams{
TextDocument: lsp.TextDocumentItem{
URI: doc.URI,
Expand All @@ -28,7 +35,8 @@ func (yamllsConnector Connector) InitiallySyncOpenDocuments(docs []*lsplocal.Doc

func (yamllsConnector Connector) DocumentDidOpen(ast *sitter.Tree, params lsp.DidOpenTextDocumentParams) {
logger.Debug("YamllsConnector DocumentDidOpen", params.TextDocument.URI)
if yamllsConnector.server == nil {

if !yamllsConnector.shouldRun(params.TextDocument.URI) {
return
}
params.TextDocument.Text = lsplocal.TrimTemplate(ast, params.TextDocument.Text)
Expand All @@ -40,9 +48,10 @@ func (yamllsConnector Connector) DocumentDidOpen(ast *sitter.Tree, params lsp.Di
}

func (yamllsConnector Connector) DocumentDidSave(doc *lsplocal.Document, params lsp.DidSaveTextDocumentParams) {
if yamllsConnector.server == nil {
if !yamllsConnector.shouldRun(doc.URI) {
return
}

params.Text = lsplocal.TrimTemplate(doc.Ast, doc.Content)

err := yamllsConnector.server.DidSave(context.Background(), &params)
Expand All @@ -58,7 +67,7 @@ func (yamllsConnector Connector) DocumentDidSave(doc *lsplocal.Document, params
}

func (yamllsConnector Connector) DocumentDidChange(doc *lsplocal.Document, params lsp.DidChangeTextDocumentParams) {
if yamllsConnector.server == nil {
if !yamllsConnector.shouldRun(doc.URI) {
return
}
trimmedText := lsplocal.TrimTemplate(doc.Ast, doc.Content)
Expand Down Expand Up @@ -87,7 +96,7 @@ func (yamllsConnector Connector) DocumentDidChange(doc *lsplocal.Document, param
}

func (yamllsConnector Connector) DocumentDidChangeFullSync(doc *lsplocal.Document, params lsp.DidChangeTextDocumentParams) {
if yamllsConnector.server == nil {
if !yamllsConnector.shouldRun(doc.URI) {
return
}

Expand Down
2 changes: 1 addition & 1 deletion internal/adapter/yamlls/hover.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
// Yamlls can not handle hover if the schema validation returns error,
// thats why we fall back to calling completion
func (yamllsConnector Connector) CallHover(ctx context.Context, params lsp.HoverParams, word string) (*lsp.Hover, error) {
if yamllsConnector.server == nil {
if !yamllsConnector.shouldRun(params.TextDocumentPositionParams.TextDocument.URI) {
return &lsp.Hover{}, nil
}

Expand Down
17 changes: 17 additions & 0 deletions internal/adapter/yamlls/yamlls.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/mrjosh/helm-ls/internal/util"
"go.lsp.dev/jsonrpc2"
"go.lsp.dev/protocol"
lsp "go.lsp.dev/protocol"
"go.uber.org/zap"
)

Expand Down Expand Up @@ -75,3 +76,19 @@ func NewConnector(ctx context.Context, yamllsConfiguration util.YamllsConfigurat
yamllsConnector.server = server
return &yamllsConnector
}

func (yamllsConnector *Connector) isRelevantFile(uri lsp.URI) bool {
doc, ok := yamllsConnector.documents.Get(uri)
if !ok {
logger.Error("Could not find document", uri)
return true
}
return doc.IsYaml
}

func (yamllsConnector *Connector) shouldRun(uri lsp.DocumentURI) bool {
if yamllsConnector.server == nil {
return false
}
return yamllsConnector.isRelevantFile(uri)
}
37 changes: 37 additions & 0 deletions internal/adapter/yamlls/yamlls_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package yamlls

import (
"testing"

lsplocal "github.com/mrjosh/helm-ls/internal/lsp"
"github.com/mrjosh/helm-ls/internal/util"
"github.com/stretchr/testify/assert"
"go.lsp.dev/uri"
)

func TestIsRelevantFile(t *testing.T) {
connector := Connector{
config: util.YamllsConfiguration{
Enabled: true,
},
}

connector.documents = &lsplocal.DocumentStore{}
yamlFile := uri.File("../../../testdata/example/templates/deployment.yaml")
nonYamlFile := uri.File("../../../testdata/example/templates/_helpers.tpl")
connector.documents.Store(yamlFile, util.DefaultConfig)
connector.documents.Store(nonYamlFile, util.DefaultConfig)

assert.True(t, connector.isRelevantFile(yamlFile))
assert.False(t, connector.isRelevantFile(nonYamlFile))
}

func TestShouldRun(t *testing.T) {
connector := Connector{
config: util.YamllsConfiguration{
Enabled: true,
},
}
assert.False(t, connector.shouldRun(uri.File("test.yaml")))
assert.False(t, connector.shouldRun(uri.File("_helpers.tpl")))
}
16 changes: 14 additions & 2 deletions internal/handler/initialization.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"os"

"github.com/gobwas/glob"
"github.com/mrjosh/helm-ls/internal/adapter/yamlls"
"github.com/mrjosh/helm-ls/internal/charts"
"github.com/mrjosh/helm-ls/internal/util"
Expand Down Expand Up @@ -58,17 +59,28 @@ func (h *langHandler) Initialized(ctx context.Context, _ *lsp.InitializedParams)
func (h *langHandler) initializationWithConfig(ctx context.Context) {
configureLogLevel(h.helmlsConfig)
h.chartStore.SetValuesFilesConfig(h.helmlsConfig.ValuesFilesConfig)
configureYamlls(ctx, h)
h.configureYamlls(ctx)
}

func configureYamlls(ctx context.Context, h *langHandler) {
func (h *langHandler) configureYamlsEnabledGlob() {
globObject, err := glob.Compile(h.helmlsConfig.YamllsConfiguration.EnabledForFilesGlob)
if err != nil {
logger.Error("Error compiling glob for yamlls EnabledForFilesGlob", err)
globObject = util.DefaultConfig.YamllsConfiguration.EnabledForFilesGlobObject
}
h.helmlsConfig.YamllsConfiguration.EnabledForFilesGlobObject = globObject
}

func (h *langHandler) configureYamlls(ctx context.Context) {
config := h.helmlsConfig
if config.YamllsConfiguration.Enabled {
h.configureYamlsEnabledGlob()
h.yamllsConnector = yamlls.NewConnector(ctx, config.YamllsConfiguration, h.client, h.documents)
err := h.yamllsConnector.CallInitialize(ctx, h.chartStore.RootURI)
if err != nil {
logger.Error("Error initializing yamlls", err)
}

h.yamllsConnector.InitiallySyncOpenDocuments(h.documents.GetAllDocs())
}
}
Expand Down
1 change: 1 addition & 0 deletions internal/lsp/document.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type Document struct {
DiagnosticsCache DiagnosticsCache
IsOpen bool
SymbolTable *SymbolTable
IsYaml bool
}

// ApplyChanges updates the content of the document from LSP textDocument/didChange events.
Expand Down
12 changes: 12 additions & 0 deletions internal/lsp/document_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,19 @@ func (s *DocumentStore) DidOpen(params *lsp.DidOpenTextDocumentParams, helmlsCon
DiagnosticsCache: NewDiagnosticsCache(helmlsConfig),
IsOpen: true,
SymbolTable: NewSymbolTable(ast, []byte(params.TextDocument.Text)),
IsYaml: IsYamlDocument(uri, helmlsConfig.YamllsConfiguration),
}
logger.Debug("Storing doc ", path)
s.documents.Store(path, doc)
return doc, nil
}

func (s *DocumentStore) Store(uri uri.URI, helmlsConfig util.HelmlsConfiguration) error {
_, ok := s.documents.Load(uri.Filename())
if ok {
return nil
}

content, err := os.ReadFile(uri.Filename())
if err != nil {
logger.Error("Could not open file ", uri.Filename(), " ", err)
Expand All @@ -67,6 +73,7 @@ func (s *DocumentStore) Store(uri uri.URI, helmlsConfig util.HelmlsConfiguration
DiagnosticsCache: NewDiagnosticsCache(helmlsConfig),
IsOpen: false,
SymbolTable: NewSymbolTable(ast, content),
IsYaml: IsYamlDocument(uri, helmlsConfig.YamllsConfiguration),
},
)
return nil
Expand All @@ -75,8 +82,13 @@ func (s *DocumentStore) Store(uri uri.URI, helmlsConfig util.HelmlsConfiguration
func (s *DocumentStore) Get(docuri uri.URI) (*Document, bool) {
path := docuri.Filename()
d, ok := s.documents.Load(path)

if !ok {
return nil, false
}
return d.(*Document), ok
}

func IsYamlDocument(uri lsp.URI, yamllsConfiguration util.YamllsConfiguration) bool {
return yamllsConfiguration.EnabledForFilesGlobObject.Match(uri.Filename())
}
17 changes: 17 additions & 0 deletions internal/lsp/document_store_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package lsp

import (
"testing"

"github.com/mrjosh/helm-ls/internal/util"
"github.com/stretchr/testify/assert"
"go.lsp.dev/uri"
)

func TestIsYamlDocument(t *testing.T) {
assert := assert.New(t)
assert.True(IsYamlDocument(uri.File("test.yaml"), util.DefaultConfig.YamllsConfiguration))
assert.False(IsYamlDocument(uri.File("test.tpl"), util.DefaultConfig.YamllsConfiguration))
assert.True(IsYamlDocument(uri.File("../../testdata/example/templates/hpa.yaml"), util.DefaultConfig.YamllsConfiguration))
assert.False(IsYamlDocument(uri.File("../../testdata/example/templates/_helpers.tpl"), util.DefaultConfig.YamllsConfiguration))
}
Loading

0 comments on commit 712e74f

Please sign in to comment.