Skip to content

Commit

Permalink
refactor(completion): rework more stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
qvalentin committed May 1, 2024
1 parent 4e015ff commit f051d2e
Show file tree
Hide file tree
Showing 10 changed files with 191 additions and 363 deletions.
8 changes: 7 additions & 1 deletion internal/documentation/helm/helm-documentation.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package helmdocs

import "github.com/mrjosh/helm-ls/internal/util"
import (
"slices"

"github.com/mrjosh/helm-ls/internal/util"
)

type HelmDocumentation struct {
Name string
Expand Down Expand Up @@ -187,6 +191,8 @@ var (
{"required", "required $str $val", "fail template with message $str if $val is not provided or is empty"},
}

AllFuncs = slices.Concat(HelmFuncs, SprigFuncs, BuiltinFuncs)

Check failure on line 194 in internal/documentation/helm/helm-documentation.go

View workflow job for this annotation

GitHub Actions / tests (1.21.5, ubuntu-latest)

undefined: slices.Concat

Check failure on line 194 in internal/documentation/helm/helm-documentation.go

View workflow job for this annotation

GitHub Actions / lint (1.21.5, ubuntu-latest)

undefined: slices.Concat (typecheck)

Check failure on line 194 in internal/documentation/helm/helm-documentation.go

View workflow job for this annotation

GitHub Actions / lint (1.21.5, ubuntu-latest)

undefined: slices.Concat) (typecheck)

Check failure on line 194 in internal/documentation/helm/helm-documentation.go

View workflow job for this annotation

GitHub Actions / lint (1.21.5, ubuntu-latest)

undefined: slices.Concat) (typecheck)

Check failure on line 194 in internal/documentation/helm/helm-documentation.go

View workflow job for this annotation

GitHub Actions / tests (1.21.5, macos-latest)

undefined: slices.Concat

CapabilitiesVals = []HelmDocumentation{
{"TillerVersion", ".Capabilities.TillerVersion", "Tiller version"},
{"APIVersions", "Capabilities.APIVersions", "A set of versions."},
Expand Down
188 changes: 9 additions & 179 deletions internal/handler/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,35 @@ package handler

import (
"context"
"errors"
"fmt"
"reflect"
"strings"

"github.com/mrjosh/helm-ls/internal/charts"
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"
"github.com/mrjosh/helm-ls/internal/util"
"github.com/mrjosh/helm-ls/pkg/chartutil"
sitter "github.com/smacker/go-tree-sitter"
"go.lsp.dev/protocol"
lsp "go.lsp.dev/protocol"
yaml "gopkg.in/yaml.v2"

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

Check failure on line 15 in internal/handler/completion.go

View workflow job for this annotation

GitHub Actions / lint (1.21.5, ubuntu-latest)

could not import github.com/mrjosh/helm-ls/internal/documentation/helm (-: # github.com/mrjosh/helm-ls/internal/documentation/helm
)

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

func init() {
functionsCompletionItems = append(functionsCompletionItems, getFunctionCompletionItems(helmdocs.HelmFuncs)...)
functionsCompletionItems = append(functionsCompletionItems, getFunctionCompletionItems(helmdocs.BuiltinFuncs)...)
functionsCompletionItems = append(functionsCompletionItems, getFunctionCompletionItems(helmdocs.SprigFuncs)...)
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)
if err != nil {
return nil, err
}

var (
currentNode = lsplocal.NodeAtPosition(genericDocumentUseCase.Document.Ast, params.Position)
pointToLoopUp = sitter.Point{
Expand All @@ -48,10 +41,6 @@ func (h *langHandler) Completion(ctx context.Context, params *lsp.CompletionPara
)
genericDocumentUseCase = genericDocumentUseCase.WithNode(relevantChildNode)

if err != nil {
return nil, err
}

usecases := []languagefeatures.CompletionUseCase{
languagefeatures.NewTemplateContextFeature(genericDocumentUseCase),
languagefeatures.NewFunctionCallFeature(genericDocumentUseCase),
Expand All @@ -62,12 +51,8 @@ func (h *langHandler) Completion(ctx context.Context, params *lsp.CompletionPara
return usecase.Completion()
}
}
doc, ok := h.documents.Get(params.TextDocument.URI)
if !ok {
return nil, errors.New("Could not get document: " + params.TextDocument.URI.Filename())
}

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

if isTextNode {
result := make([]lsp.CompletionItem, 0)
Expand All @@ -77,41 +62,16 @@ func (h *langHandler) Completion(ctx context.Context, params *lsp.CompletionPara
return &protocol.CompletionList{IsIncomplete: false, Items: result}, err
}

var (
splitted = strings.Split(word, ".")
items []lsp.CompletionItem
variableSplitted = []string{}
)

for n, s := range splitted {
// we want to keep the last empty string to be able
// distinguish between 'global.' and 'global'
if s == "" && n != len(splitted)-1 {
continue
}
variableSplitted = append(variableSplitted, s)
}

logger.Println(fmt.Sprintf("Word found for completions is < %s >", word))
items = make([]lsp.CompletionItem, 0)
items := []lsp.CompletionItem{}
for _, v := range helmdocs.BuiltInObjects {
items = append(items, lsp.CompletionItem{
Label: v.Name,
InsertText: v.Name,
InsertText: "." + v.Name,
Detail: v.Detail,
Documentation: v.Doc,
})
}
if len(variableSplitted) == 0 {
return &lsp.CompletionList{IsIncomplete: false, Items: items}, err
}

// $ always points to the root context so we can safely remove it
// as long the LSP does not know about ranges
if variableSplitted[0] == "$" && len(variableSplitted) > 1 {
variableSplitted = variableSplitted[1:]
}

return &lsp.CompletionList{IsIncomplete: false, Items: items}, err
}

Expand All @@ -136,9 +96,6 @@ func completionAstParsing(doc *lsplocal.Document, position lsp.Position) (string
word string
)

logger.Println("currentNode", currentNode)
logger.Println("relevantChildNode", relevantChildNode)

nodeType := relevantChildNode.Type()
switch nodeType {
case gotemplate.NodeTypeIdentifier:
Expand All @@ -150,133 +107,6 @@ func completionAstParsing(doc *lsplocal.Document, position lsp.Position) (string
return word, false
}

func (h *langHandler) getValuesCompletions(chart *charts.Chart, splittedVar []string) (result []lsp.CompletionItem) {
m := make(map[string]lsp.CompletionItem)
for _, queriedValuesFiles := range chart.ResolveValueFiles(splittedVar, h.chartStore) {
for _, valuesFile := range queriedValuesFiles.ValuesFiles.AllValuesFiles() {
for _, item := range h.getValue(valuesFile.Values, queriedValuesFiles.Selector) {
m[item.InsertText] = item
}
}
}

for _, item := range m {
result = append(result, item)
}

return result
}

func (h *langHandler) getValue(values chartutil.Values, splittedVar []string) []lsp.CompletionItem {
var (
err error
tableName = strings.Join(splittedVar, ".")
localValues chartutil.Values
items = make([]lsp.CompletionItem, 0)
)

if len(splittedVar) > 0 {

localValues, err = values.Table(tableName)
if err != nil {
logger.Println(err)
if len(splittedVar) > 1 {
// the current tableName was not found, maybe because it is incomplete, we can use the previous one
// e.g. gobal.im -> im was not found
// but global contains the key image, so we return all keys of global
localValues, err = values.Table(strings.Join(splittedVar[:len(splittedVar)-1], "."))
if err != nil {
logger.Println(err)
return emptyItems
}
values = localValues
}
} else {
values = localValues
}

}

for variable, value := range values {
items = h.setItem(items, value, variable)
}

return items
}

func (h *langHandler) setItem(items []lsp.CompletionItem, value interface{}, variable string) []lsp.CompletionItem {
var (
itemKind = lsp.CompletionItemKindVariable
valueOf = reflect.ValueOf(value)
documentation = valueOf.String()
)

logger.Debug("ValueKind: ", valueOf)

switch valueOf.Kind() {
case reflect.Slice, reflect.Map:
itemKind = lsp.CompletionItemKindStruct
documentation = h.toYAML(value)
case reflect.Bool:
itemKind = lsp.CompletionItemKindVariable
documentation = util.GetBoolType(value)
case reflect.Float32, reflect.Float64:
documentation = fmt.Sprintf("%.2f", valueOf.Float())
itemKind = lsp.CompletionItemKindVariable
case reflect.Invalid:
documentation = "<Unknown>"
default:
itemKind = lsp.CompletionItemKindField
}

return append(items, lsp.CompletionItem{
Label: variable,
InsertText: variable,
Documentation: documentation,
Detail: valueOf.Kind().String(),
Kind: itemKind,
})
}

func (h *langHandler) toYAML(value interface{}) string {
valBytes, _ := yaml.Marshal(value)
return string(valBytes)
}

func getVariableCompletionItems(helmDocs []helmdocs.HelmDocumentation) (result []lsp.CompletionItem) {
for _, item := range helmDocs {
result = append(result, variableCompletionItem(item))
}
return result
}

func variableCompletionItem(helmDocumentation helmdocs.HelmDocumentation) lsp.CompletionItem {
return lsp.CompletionItem{
Label: helmDocumentation.Name,
InsertText: helmDocumentation.Name,
Detail: helmDocumentation.Detail,
Documentation: helmDocumentation.Doc,
Kind: lsp.CompletionItemKindVariable,
}
}

func getFunctionCompletionItems(helmDocs []helmdocs.HelmDocumentation) (result []lsp.CompletionItem) {
for _, item := range helmDocs {
result = append(result, functionCompletionItem(item))
}
return result
}

func functionCompletionItem(helmDocumentation helmdocs.HelmDocumentation) lsp.CompletionItem {
return lsp.CompletionItem{
Label: helmDocumentation.Name,
InsertText: helmDocumentation.Name,
Detail: helmDocumentation.Detail,
Documentation: helmDocumentation.Doc,
Kind: lsp.CompletionItemKindFunction,
}
}

func getTextCompletionItems(gotemplateSnippet []godocs.GoTemplateSnippet) (result []lsp.CompletionItem) {
for _, item := range gotemplateSnippet {
result = append(result, textCompletionItem(item))
Expand Down
12 changes: 12 additions & 0 deletions internal/handler/completion_main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ func TestCompletionMain(t *testing.T) {
notExpectedInsertTexts []string
expectedError error
}{
{
desc: "Test completion on {{ if (and .Values. ) }}",
position: lsp.Position{
Line: 8,
Character: 19,
},
expectedInsertText: "replicaCount",
notExpectedInsertTexts: []string{
helmdocs.HelmFuncs[0].Name,
},
expectedError: nil,
},
{
desc: "Test completion on .Chart.N",
position: lsp.Position{
Expand Down
Loading

0 comments on commit f051d2e

Please sign in to comment.