Skip to content

Commit

Permalink
feat: completion for unfinished_selector_expression
Browse files Browse the repository at this point in the history
  • Loading branch information
qvalentin committed May 9, 2024
1 parent d8e5cd4 commit 88706f0
Show file tree
Hide file tree
Showing 38 changed files with 836 additions and 659 deletions.
2 changes: 1 addition & 1 deletion internal/adapter/yamlls/completion_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func TestYamllsCompletionIntegration(t *testing.T) {
expected []lsp.CompletionItem
}{
{
desc: "test hover on deployment.yaml",
desc: "test completion on deployment.yaml",
file: "../../../testdata/example/templates/deployment.yaml",
position: lsp.Position{
Line: 42,
Expand Down
7 changes: 7 additions & 0 deletions internal/adapter/yamlls/diagnostics.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package yamlls

import (
"context"
"runtime"
"strings"

lsplocal "github.com/mrjosh/helm-ls/internal/lsp"
sitter "github.com/smacker/go-tree-sitter"
Expand Down Expand Up @@ -71,6 +73,11 @@ func diagnisticIsRelevant(diagnostic lsp.Diagnostic, node *sitter.Node) bool {
// {{- end }}
return false
default:
// TODO: remove this once the tree-sitter grammar behavior for windows newlines is the same as for unix
if strings.HasPrefix(diagnostic.Message, "Incorrect type. Expected") && runtime.GOOS == "windows" {
return false
}

return true
}
}
3 changes: 3 additions & 0 deletions internal/adapter/yamlls/documentSync.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ func (yamllsConnector Connector) InitiallySyncOpenDocuments(docs []*lsplocal.Doc
return
}
for _, doc := range docs {
if !doc.IsOpen {
continue
}
yamllsConnector.DocumentDidOpen(doc.Ast, lsp.DidOpenTextDocumentParams{
TextDocument: lsp.TextDocumentItem{
URI: doc.URI,
Expand Down
22 changes: 17 additions & 5 deletions internal/adapter/yamlls/hover.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ package yamlls
import (
"context"

"github.com/mrjosh/helm-ls/internal/util"
"github.com/mrjosh/helm-ls/internal/protocol"
lsp "go.lsp.dev/protocol"
)

// Calls the Hover method of yamlls to get a fitting hover response
// If hover returns nothing appropriate, calls yamlls for completions
//
// 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 {
return &lsp.Hover{}, nil
Expand All @@ -25,15 +28,17 @@ func (yamllsConnector Connector) CallHover(ctx context.Context, params lsp.Hover
return (yamllsConnector).getHoverFromCompletion(ctx, params, word)
}

func (yamllsConnector Connector) getHoverFromCompletion(ctx context.Context, params lsp.HoverParams, word string) (*lsp.Hover, error) {
func (yamllsConnector Connector) getHoverFromCompletion(ctx context.Context, params lsp.HoverParams, word string) (response *lsp.Hover, err error) {
var (
err error
resultRange lsp.Range
documentation string
completionParams = lsp.CompletionParams{
TextDocumentPositionParams: params.TextDocumentPositionParams,
}
)

word = removeTrailingColon(word)

completionList, err := yamllsConnector.server.Completion(ctx, &completionParams)
if err != nil {
logger.Error("Error calling yamlls for Completion", err)
Expand All @@ -43,10 +48,17 @@ func (yamllsConnector Connector) getHoverFromCompletion(ctx context.Context, par
for _, completionItem := range completionList.Items {
if completionItem.InsertText == word {
documentation = completionItem.Documentation.(string)
resultRange = completionItem.TextEdit.Range
break
}
}

response := util.BuildHoverResponse(documentation, lsp.Range{})
return response, nil
return protocol.BuildHoverResponse(documentation, resultRange), nil
}

func removeTrailingColon(word string) string {
if len(word) > 2 && string(word[len(word)-1]) == ":" {
word = word[0 : len(word)-1]
}
return word
}
2 changes: 1 addition & 1 deletion internal/adapter/yamlls/hover_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func TestYamllsHoverIntegration(t *testing.T) {
},
}, tt.word)
return err == nil && strings.Contains(result.Contents.Value, tt.expected)
}, time.Second*10, time.Second/2)
}, time.Second*40, time.Second*2)
})
}
}
5 changes: 3 additions & 2 deletions internal/charts/chart.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,9 @@ func (c *Chart) ResolveValueFiles(query []string, chartStore *ChartStore) []*Que
}

func (c *Chart) GetValueLocation(templateContext []string) (lsp.Location, error) {
modifyedVar := make([]string, 0)
// for Charts, we make the first letter lowercase
modifyedVar := make([]string, len(templateContext))
// make the first letter lowercase since in the template the first letter is
// capitalized, but it is not in the Chart.yaml file
for _, value := range templateContext {
restOfString := ""
if (len(value)) > 1 {
Expand Down
8 changes: 1 addition & 7 deletions internal/documentation/helm/helm-documentation.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,13 +261,7 @@ var (
)

func GetFunctionByName(name string) (HelmDocumentation, bool) {
completionItems := [][]HelmDocumentation{
BuiltinFuncs,
SprigFuncs,
HelmFuncs,
}
toSearch := util.ConcatMultipleSlices(completionItems)
for _, completionItem := range toSearch {
for _, completionItem := range AllFuncs {
if name == completionItem.Name {
return completionItem, true
}
Expand Down
109 changes: 12 additions & 97 deletions internal/handler/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,27 @@ package handler

import (
"context"
"fmt"

languagefeatures "github.com/mrjosh/helm-ls/internal/language_features"
lsplocal "github.com/mrjosh/helm-ls/internal/lsp"
gotemplate "github.com/mrjosh/helm-ls/internal/tree-sitter/gotemplate"
sitter "github.com/smacker/go-tree-sitter"
"go.lsp.dev/protocol"
"github.com/mrjosh/helm-ls/internal/protocol"
lsp "go.lsp.dev/protocol"

"github.com/mrjosh/helm-ls/internal/documentation/godocs"
helmdocs "github.com/mrjosh/helm-ls/internal/documentation/helm"
)

var (
emptyItems = make([]lsp.CompletionItem, 0)
textCompletionsItems = make([]lsp.CompletionItem, 0)
)

func init() {
textCompletionsItems = append(textCompletionsItems, getTextCompletionItems(godocs.TextSnippets)...)
}

func (h *langHandler) Completion(ctx context.Context, params *lsp.CompletionParams) (result *lsp.CompletionList, err error) {
logger.Debug("Running completion with params", params)
genericDocumentUseCase, err := h.NewGenericDocumentUseCase(params.TextDocumentPositionParams)
genericDocumentUseCase, err := h.NewGenericDocumentUseCase(params.TextDocumentPositionParams, lsplocal.NestedNodeAtPositionForCompletion)
if err != nil {
return nil, err
}

var (
currentNode = lsplocal.NodeAtPosition(genericDocumentUseCase.Document.Ast, params.Position)
pointToLoopUp = sitter.Point{
Row: params.Position.Line,
Column: params.Position.Character,
}
relevantChildNode = lsplocal.FindRelevantChildNodeCompletion(currentNode, pointToLoopUp)
)
genericDocumentUseCase = genericDocumentUseCase.WithNode(relevantChildNode)

usecases := []languagefeatures.CompletionUseCase{
languagefeatures.NewTemplateContextFeature(genericDocumentUseCase),
languagefeatures.NewFunctionCallFeature(genericDocumentUseCase),
languagefeatures.NewTextFeature(ctx, genericDocumentUseCase, h.yamllsConnector, &params.TextDocumentPositionParams),
languagefeatures.NewIncludesCallFeature(genericDocumentUseCase),
}

for _, usecase := range usecases {
Expand All @@ -52,79 +31,15 @@ func (h *langHandler) Completion(ctx context.Context, params *lsp.CompletionPara
}
}

word, isTextNode := completionAstParsing(genericDocumentUseCase.Document, params.Position)

if isTextNode {
result := make([]lsp.CompletionItem, 0)
result = append(result, textCompletionsItems...)
result = append(result, yamllsCompletions(ctx, h, params)...)
logger.Debug("Sending completions ", result)
return &protocol.CompletionList{IsIncomplete: false, Items: result}, err
}

logger.Println(fmt.Sprintf("Word found for completions is < %s >", word))
items := []lsp.CompletionItem{}
// If no usecase matched, we assume we are at {{ }}
// and provide the basic BuiltInObjects and functions
items := []helmdocs.HelmDocumentation{}
for _, v := range helmdocs.BuiltInObjects {
items = append(items, lsp.CompletionItem{
Label: v.Name,
InsertText: "." + v.Name,
Detail: v.Detail,
Documentation: v.Doc,
})
v.Name = "." + v.Name
items = append(items, v)
}
return &lsp.CompletionList{IsIncomplete: false, Items: items}, err
}

func yamllsCompletions(ctx context.Context, h *langHandler, params *lsp.CompletionParams) []lsp.CompletionItem {
response, err := h.yamllsConnector.CallCompletion(ctx, params)
if err != nil {
logger.Error("Error getting yamlls completions", err)
return []lsp.CompletionItem{}
}
logger.Debug("Got completions from yamlls", response)
return response.Items
}

func completionAstParsing(doc *lsplocal.Document, position lsp.Position) (string, bool) {
var (
currentNode = lsplocal.NodeAtPosition(doc.Ast, position)
pointToLoopUp = sitter.Point{
Row: position.Line,
Column: position.Character,
}
relevantChildNode = lsplocal.FindRelevantChildNode(currentNode, pointToLoopUp)
word string
)

nodeType := relevantChildNode.Type()
switch nodeType {
case gotemplate.NodeTypeIdentifier:
word = relevantChildNode.Content([]byte(doc.Content))
case gotemplate.NodeTypeText, gotemplate.NodeTypeTemplate:
return word, true
}
logger.Debug("word", word)
return word, false
}

func getTextCompletionItems(gotemplateSnippet []godocs.GoTemplateSnippet) (result []lsp.CompletionItem) {
for _, item := range gotemplateSnippet {
result = append(result, textCompletionItem(item))
}
return result
}

func textCompletionItem(gotemplateSnippet godocs.GoTemplateSnippet) lsp.CompletionItem {
return lsp.CompletionItem{
Label: gotemplateSnippet.Name,
// TextEdit: &lsp.TextEdit{
// // Range: lsp.Range{}, // TODO: range must contain the requested range
// NewText: gotemplateSnippet.Snippet,
// },
InsertText: gotemplateSnippet.Snippet,
Detail: gotemplateSnippet.Detail,
Documentation: gotemplateSnippet.Doc,
Kind: lsp.CompletionItemKindText,
InsertTextFormat: lsp.InsertTextFormatSnippet,
}
return protocol.CompletionResults{}.
WithDocs(items, lsp.CompletionItemKindConstant).
WithDocs(helmdocs.AllFuncs, lsp.CompletionItemKindFunction).ToList(), nil
}
Loading

0 comments on commit 88706f0

Please sign in to comment.