From 2c253fc5cf0882ee3bec8cc319d781f25ade22ed Mon Sep 17 00:00:00 2001 From: qvalentin Date: Sat, 13 Apr 2024 21:28:00 +0200 Subject: [PATCH] template_context hover usecase --- .../helm}/helm-documentation.go | 53 +++++-- internal/handler/completion.go | 37 +++-- internal/handler/hover.go | 131 +++--------------- internal/handler/hover_main_test.go | 2 +- .../language_features/template_context.go | 81 ++++++++++- .../template_context_hover_test.go} | 11 +- internal/language_features/usecases.go | 5 + internal/util/values.go | 50 +++++++ 8 files changed, 214 insertions(+), 156 deletions(-) rename internal/{handler => documentation/helm}/helm-documentation.go (82%) rename internal/{handler/hover_test.go => language_features/template_context_hover_test.go} (95%) create mode 100644 internal/util/values.go diff --git a/internal/handler/helm-documentation.go b/internal/documentation/helm/helm-documentation.go similarity index 82% rename from internal/handler/helm-documentation.go rename to internal/documentation/helm/helm-documentation.go index 9b81877a..fc18e352 100644 --- a/internal/handler/helm-documentation.go +++ b/internal/documentation/helm/helm-documentation.go @@ -1,4 +1,4 @@ -package handler +package helmdocs type HelmDocumentation struct { Name string @@ -7,9 +7,10 @@ type HelmDocumentation struct { } var ( - basicItems = []HelmDocumentation{ + BuiltInObjects = []HelmDocumentation{ {"Values", ".Values", `The values made available through values.yaml, --set and -f.`}, {"Chart", ".Chart", "Chart metadata"}, + {"Subcharts", ".Subcharts", "This provides access to the scope (.Values, .Charts, .Releases etc.) of subcharts to the parent. For example .Subcharts.mySubChart.myValue to access the myValue in the mySubChart chart."}, {"Files", ".Files.Get $str", "access non-template files within the chart"}, {"Capabilities", ".Capabilities.KubeVersion ", "access capabilities of Kubernetes"}, {"Release", ".Release", `Built-in release values. Attributes include: @@ -21,8 +22,9 @@ var ( - .Release.IsInstall: True if this is an install - .Release.Revision: The revision number `}, + {"Template", ".Template", "Contains information about the current template that is being executed"}, } - builtinFuncs = []HelmDocumentation{ + BuiltinFuncs = []HelmDocumentation{ {"template", "template $str $ctx", "render the template at location $str"}, {"define", "define $str", "define a template with the name $str"}, {"and", "and $a $b ...", "if $a then $b else $a"}, @@ -44,7 +46,7 @@ var ( {"le", "le $a $b", "returns true if $a <= $b"}, {"ge", "ge $a $b", "returns true if $a >= $b"}, } - sprigFuncs = []HelmDocumentation{ + SprigFuncs = []HelmDocumentation{ // 2.12.0 {"snakecase", "snakecase $str", "Convert $str to snake_case"}, {"camelcase", "camelcase $str", "convert string to camelCase"}, @@ -173,7 +175,7 @@ var ( {"derivePassword", "derivePassword $counter $long $pass $user $domain", "generate a password from [Master Password](http://masterpasswordapp.com/algorithm.html) spec"}, {"generatePrivateKey", "generatePrivateKey 'ecdsa'", "generate private PEM key (takes dsa, rsa, or ecdsa)"}, } - helmFuncs = []HelmDocumentation{ + HelmFuncs = []HelmDocumentation{ {"include", "include $str $ctx", "(chainable) include the named template with the given context."}, {"toYaml", "toYaml $var", "convert $var to YAML"}, {"toJson", "toJson $var", "convert $var to JSON"}, @@ -183,13 +185,22 @@ var ( {"required", "required $str $val", "fail template with message $str if $val is not provided or is empty"}, } - capabilitiesVals = []HelmDocumentation{ - {"KubeVersion", ".Capabilities.KubeVersion", "Kubernetes version"}, + CapabilitiesVals = []HelmDocumentation{ {"TillerVersion", ".Capabilities.TillerVersion", "Tiller version"}, - {"ApiVersions.Has", `.Capabilities.ApiVersions.Has "batch/v1"`, "Returns true if the given Kubernetes API/version is present on the cluster"}, + {"APIVersions", "Capabilities.APIVersions", "A set of versions."}, + {"APIVersions.Has", "Capabilities.APIVersions.Has $version", "Indicates whether a version (e.g., batch/v1) or resource (e.g., apps/v1/Deployment) is available on the cluster."}, + {"KubeVersion", "Capabilities.KubeVersion", "The Kubernetes version."}, + {"KubeVersion.Version", "Capabilities.KubeVersion.Version", "The Kubernetes version in semver format."}, + {"KubeVersion.Major", "Capabilities.KubeVersion.Major", "The Kubernetes major version."}, + {"KubeVersion.Minor", "Capabilities.KubeVersion.Minor", "The Kubernetes minor version."}, + {"KubeVersion.GitCommit", "Capabilities.HelmVersion", "The object containing the Helm Version details, it is the same output of helm version."}, + {"KubeVersion.GitTreeState", "Capabilities.HelmVersion.Version", "The current Helm version in semver format."}, + {"HelmVersion.GitCommit", "Capabilities.HelmVersion.GitCommit", "The Helm git sha1."}, + {"HelmVersion.GitTreeState", "Capabilities.HelmVersion.GitTreeState", "The state of the Helm git tree."}, + {"HelmVersion.GoVersion", "Capabilities.HelmVersion.GoVersion", "The version of the Go compiler used."}, } - chartVals = []HelmDocumentation{ + ChartVals = []HelmDocumentation{ {"Name", ".Chart.Name", "Name of the chart"}, {"Version", ".Chart.Version", "Version of the chart"}, {"Description", ".Chart.Description", "Chart description"}, @@ -201,9 +212,21 @@ var ( {"AppVersion", ".Chart.AppVersion", "The version of the main app contained in this chart"}, {"Deprecated", ".Chart.Deprecated", "If true, this chart is no longer maintained"}, {"TillerVersion", ".Chart.TillerVersion", "The version (range) if Tiller that this chart can run on."}, + {"APIVersion", ".Chart.APIVersion", "The API Version of this chart"}, + {"Condition", ".Chart.Condition", "The condition to check to enable chart"}, + {"Tags", ".Chart.Tags", "The tags to check to enable chart"}, + {"Annotations", ".Chart.Annotations", "Additional annotations (key-value pairs)"}, + {"KubeVersion", ".Chart.KubeVersion", "Kubernetes version required"}, + {"Dependencies", ".Chart.Dependencies", "List of chart dependencies"}, + {"Type", ".Chart.Type", "Chart type (application or library)"}, } - releaseVals = []HelmDocumentation{ + TemplateVals = []HelmDocumentation{ + {"Name", ".Template.Name", "A namespaced file path to the current template (e.g. mychart/templates/mytemplate.yaml)"}, + {"BasePath", ".Template.BasePath", "The namespaced path to the templates directory of the current chart (e.g. mychart/templates)."}, + } + + ReleaseVals = []HelmDocumentation{ {"Name", ".Release.Name", "Name of the release"}, {"Time", ".Release.Time", "Time of the release"}, {"Namespace", ".Release.Namespace", "Default namespace of the release"}, @@ -213,7 +236,7 @@ var ( {"Revision", ".Release.Revision", "Release revision number (starts at 1)"}, } - filesVals = []HelmDocumentation{ + FilesVals = []HelmDocumentation{ {"Get", ".Files.Get $path", "Get file contents. Path is relative to chart."}, {"GetBytes", ".Files.GetBytes $path", "Get file contents as a byte array. Path is relative to chart."}, {"Glob", ".Files.Glob $glob", "Returns a list of files whose names match the given shell glob pattern."}, @@ -221,4 +244,12 @@ var ( {"AsSecrets", ".Files.AsSecrets $path", "Returns the file bodies as Base 64 encoded strings."}, {"AsConfig", ".Files.AsConfig $path", "Returns file bodies as a YAML map."}, } + + BuiltInOjectVals = map[string][]HelmDocumentation{ + "Chart": ChartVals, + "Release": ReleaseVals, + "Files": FilesVals, + "Capabilities": CapabilitiesVals, + "Template": TemplateVals, + } ) diff --git a/internal/handler/completion.go b/internal/handler/completion.go index f6a9feaa..c5c32cb0 100644 --- a/internal/handler/completion.go +++ b/internal/handler/completion.go @@ -10,6 +10,7 @@ import ( "github.com/mrjosh/helm-ls/internal/charts" 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" @@ -17,6 +18,7 @@ import ( yaml "gopkg.in/yaml.v2" "github.com/mrjosh/helm-ls/internal/documentation/godocs" + helmdocs "github.com/mrjosh/helm-ls/internal/documentation/helm" ) var ( @@ -26,9 +28,9 @@ var ( ) func init() { - functionsCompletionItems = append(functionsCompletionItems, getFunctionCompletionItems(helmFuncs)...) - functionsCompletionItems = append(functionsCompletionItems, getFunctionCompletionItems(builtinFuncs)...) - functionsCompletionItems = append(functionsCompletionItems, getFunctionCompletionItems(sprigFuncs)...) + functionsCompletionItems = append(functionsCompletionItems, getFunctionCompletionItems(helmdocs.HelmFuncs)...) + functionsCompletionItems = append(functionsCompletionItems, getFunctionCompletionItems(helmdocs.BuiltinFuncs)...) + functionsCompletionItems = append(functionsCompletionItems, getFunctionCompletionItems(helmdocs.SprigFuncs)...) textCompletionsItems = append(textCompletionsItems, getTextCompletionItems(godocs.TextSnippets)...) } @@ -69,7 +71,7 @@ func (h *langHandler) Completion(ctx context.Context, params *lsp.CompletionPara logger.Println(fmt.Sprintf("Word found for completions is < %s >", word)) items = make([]lsp.CompletionItem, 0) - for _, v := range basicItems { + for _, v := range helmdocs.BuiltInObjects { items = append(items, lsp.CompletionItem{ Label: v.Name, InsertText: v.Name, @@ -89,17 +91,17 @@ func (h *langHandler) Completion(ctx context.Context, params *lsp.CompletionPara switch variableSplitted[0] { case "Chart": - items = getVariableCompletionItems(chartVals) + items = getVariableCompletionItems(helmdocs.ChartVals) case "Values": items = h.getValuesCompletions(chart, variableSplitted[1:]) case "Release": - items = getVariableCompletionItems(releaseVals) + items = getVariableCompletionItems(helmdocs.ReleaseVals) case "Files": - items = getVariableCompletionItems(filesVals) + items = getVariableCompletionItems(helmdocs.FilesVals) case "Capabilities": - items = getVariableCompletionItems(capabilitiesVals) + items = getVariableCompletionItems(helmdocs.CapabilitiesVals) default: - items = getVariableCompletionItems(basicItems) + items = getVariableCompletionItems(helmdocs.BuiltInObjects) items = append(items, functionsCompletionItems...) } @@ -214,7 +216,7 @@ func (h *langHandler) setItem(items []lsp.CompletionItem, value interface{}, var documentation = h.toYAML(value) case reflect.Bool: itemKind = lsp.CompletionItemKindVariable - documentation = h.getBoolType(value) + documentation = util.GetBoolType(value) case reflect.Float32, reflect.Float64: documentation = fmt.Sprintf("%.2f", valueOf.Float()) itemKind = lsp.CompletionItemKindVariable @@ -238,21 +240,14 @@ func (h *langHandler) toYAML(value interface{}) string { return string(valBytes) } -func (h *langHandler) getBoolType(value interface{}) string { - if val, ok := value.(bool); ok && val { - return "True" - } - return "False" -} - -func getVariableCompletionItems(helmDocs []HelmDocumentation) (result []lsp.CompletionItem) { +func getVariableCompletionItems(helmDocs []helmdocs.HelmDocumentation) (result []lsp.CompletionItem) { for _, item := range helmDocs { result = append(result, variableCompletionItem(item)) } return result } -func variableCompletionItem(helmDocumentation HelmDocumentation) lsp.CompletionItem { +func variableCompletionItem(helmDocumentation helmdocs.HelmDocumentation) lsp.CompletionItem { return lsp.CompletionItem{ Label: helmDocumentation.Name, InsertText: helmDocumentation.Name, @@ -262,14 +257,14 @@ func variableCompletionItem(helmDocumentation HelmDocumentation) lsp.CompletionI } } -func getFunctionCompletionItems(helmDocs []HelmDocumentation) (result []lsp.CompletionItem) { +func getFunctionCompletionItems(helmDocs []helmdocs.HelmDocumentation) (result []lsp.CompletionItem) { for _, item := range helmDocs { result = append(result, functionCompletionItem(item)) } return result } -func functionCompletionItem(helmDocumentation HelmDocumentation) lsp.CompletionItem { +func functionCompletionItem(helmDocumentation helmdocs.HelmDocumentation) lsp.CompletionItem { return lsp.CompletionItem{ Label: helmDocumentation.Name, InsertText: helmDocumentation.Name, diff --git a/internal/handler/hover.go b/internal/handler/hover.go index 0a453b0f..6c6ea382 100644 --- a/internal/handler/hover.go +++ b/internal/handler/hover.go @@ -4,17 +4,14 @@ import ( "context" "errors" "fmt" - "reflect" "strings" - "github.com/mrjosh/helm-ls/internal/charts" + helmdocs "github.com/mrjosh/helm-ls/internal/documentation/helm" languagefeatures "github.com/mrjosh/helm-ls/internal/language_features" lspinternal "github.com/mrjosh/helm-ls/internal/lsp" "github.com/mrjosh/helm-ls/internal/tree-sitter/gotemplate" "github.com/mrjosh/helm-ls/internal/util" - "github.com/mrjosh/helm-ls/pkg/chart" - "github.com/mrjosh/helm-ls/pkg/chartutil" lsp "go.lsp.dev/protocol" ) @@ -24,7 +21,6 @@ func (h *langHandler) Hover(ctx context.Context, params *lsp.HoverParams) (resul return nil, err } doc := genericDocumentUseCase.Document - chart := genericDocumentUseCase.Chart var ( currentNode = lspinternal.NodeAtPosition(doc.Ast, params.Position) @@ -38,6 +34,17 @@ func (h *langHandler) Hover(ctx context.Context, params *lsp.HoverParams) (resul return nil, err } + usecases := []languagefeatures.HoverUseCase{ + languagefeatures.NewValuesFeature(genericDocumentUseCase), + } + + for _, usecase := range usecases { + if usecase.AppropriateForNode(genericDocumentUseCase.NodeType, genericDocumentUseCase.ParentNodeType, genericDocumentUseCase.Node) { + result, err := usecase.Hover() + return util.BuildHoverResponse(result, wordRange), err + } + } + pt := parent.Type() ct := currentNode.Type() if ct == gotemplate.NodeTypeText { @@ -48,21 +55,9 @@ func (h *langHandler) Hover(ctx context.Context, params *lsp.HoverParams) (resul response, err := h.yamllsConnector.CallHover(ctx, *params, word) return response, err } - // if (pt == gotemplate.NodeTypeField && ct == gotemplate.NodeTypeIdentifier) || ct == gotemplate.NodeTypeFieldIdentifier || ct == gotemplate.NodeTypeField { - // valuesFeature := languagefeatures.NewValuesFeature(genericDocumentUseCase) - // response, err := valuesFeature.Hover() - // return util.BuildHoverResponse(response, wordRange), err - // } if pt == gotemplate.NodeTypeFunctionCall && ct == gotemplate.NodeTypeIdentifier { word = currentNode.Content([]byte(doc.Content)) } - if (pt == gotemplate.NodeTypeSelectorExpression || pt == gotemplate.NodeTypeField) && - (ct == gotemplate.NodeTypeIdentifier || ct == gotemplate.NodeTypeFieldIdentifier) { - word = lspinternal.GetFieldIdentifierPath(currentNode, doc) - } - if ct == gotemplate.NodeTypeDot { - word = lspinternal.TraverseIdentifierPathUp(currentNode, doc) - } if pt == gotemplate.NodeTypeArgumentList && ct == gotemplate.NodeTypeInterpretedStringLiteral { includesCallFeature := languagefeatures.NewIncludesCallFeature(genericDocumentUseCase) response, err := includesCallFeature.Hover() @@ -93,19 +88,6 @@ func (h *langHandler) Hover(ctx context.Context, params *lsp.HoverParams) (resul logger.Println(fmt.Sprintf("Hover checking for word < %s >", word)) if len(variableSplitted) > 1 { - switch variableSplitted[0] { - case "Values": - value, err = h.getValueHover(chart, variableSplitted[1:]) - case "Chart": - value, err = h.getChartMetadataHover(&chart.ChartMetadata.Metadata, variableSplitted[1]) - case "Release": - value, err = h.getBuiltInObjectsHover(releaseVals, variableSplitted[1]) - case "Files": - value, err = h.getBuiltInObjectsHover(filesVals, variableSplitted[1]) - case "Capabilities": - value, err = h.getBuiltInObjectsHover(capabilitiesVals, variableSplitted[1]) - } - if err == nil { if value == "" { value = "\"\"" @@ -117,11 +99,10 @@ func (h *langHandler) Hover(ctx context.Context, params *lsp.HoverParams) (resul } searchWord := variableSplitted[0] - completionItems := [][]HelmDocumentation{ - basicItems, - builtinFuncs, - sprigFuncs, - helmFuncs, + completionItems := [][]helmdocs.HelmDocumentation{ + helmdocs.BuiltinFuncs, + helmdocs.SprigFuncs, + helmdocs.HelmFuncs, } toSearch := util.ConcatMultipleSlices(completionItems) @@ -134,83 +115,3 @@ func (h *langHandler) Hover(ctx context.Context, params *lsp.HoverParams) (resul } return nil, err } - -func (h *langHandler) getChartMetadataHover(metadata *chart.Metadata, key string) (string, error) { - for _, completionItem := range chartVals { - if key == completionItem.Name { - logger.Println("Getting metadatafield of " + key) - - documentation := completionItem.Doc - value := h.getMetadataField(metadata, key) - - return fmt.Sprintf("%s\n\n%s\n", documentation, value), nil - } - } - return "", fmt.Errorf("%s was no known Chart Metadata property", key) -} - -func (h *langHandler) getValueHover(chart *charts.Chart, splittedVar []string) (result string, err error) { - var ( - valuesFiles = chart.ResolveValueFiles(splittedVar, h.chartStore) - hoverResults = util.HoverResultsWithFiles{} - ) - - for _, valuesFiles := range valuesFiles { - for _, valuesFile := range valuesFiles.ValuesFiles.AllValuesFiles() { - result, err := h.getTableOrValueForSelector(valuesFile.Values, strings.Join(valuesFiles.Selector, ".")) - if err == nil { - hoverResults = append(hoverResults, util.HoverResultWithFile{URI: valuesFile.URI, Value: result}) - } - } - } - - return hoverResults.Format(h.chartStore.RootURI), nil -} - -func (h *langHandler) getTableOrValueForSelector(values chartutil.Values, selector string) (string, error) { - if len(selector) > 0 { - localValues, err := values.Table(selector) - if err != nil { - logger.Debug("values.PathValue(tableName) because of error", err) - value, err := values.PathValue(selector) - return h.formatToYAML(reflect.Indirect(reflect.ValueOf(value)), selector), err - } - logger.Debug("converting to YAML", localValues) - return localValues.YAML() - } - return values.YAML() -} - -func (h *langHandler) getBuiltInObjectsHover(items []HelmDocumentation, key string) (string, error) { - for _, completionItem := range items { - if key == completionItem.Name { - documentation := completionItem.Doc - return fmt.Sprintf("%s\n", documentation), nil - } - } - return "", fmt.Errorf("%s was no known built-in object", key) -} - -func (h *langHandler) getMetadataField(v *chart.Metadata, fieldName string) string { - r := reflect.ValueOf(v) - field := reflect.Indirect(r).FieldByName(fieldName) - return h.formatToYAML(field, fieldName) -} - -func (h *langHandler) formatToYAML(field reflect.Value, fieldName string) string { - switch field.Kind() { - case reflect.String: - return field.String() - case reflect.Map: - return h.toYAML(field.Interface()) - case reflect.Slice: - return h.toYAML(map[string]interface{}{fieldName: field.Interface()}) - case reflect.Bool: - return fmt.Sprint(h.getBoolType(field)) - case reflect.Float32, reflect.Float64: - return fmt.Sprint(field.Float()) - default: - logger.Error("Unknown kind for hover type: ", field.Kind()) - return "" - } -} diff --git a/internal/handler/hover_main_test.go b/internal/handler/hover_main_test.go index 494b03b0..ad7eb212 100644 --- a/internal/handler/hover_main_test.go +++ b/internal/handler/hover_main_test.go @@ -74,7 +74,7 @@ func TestHoverMain(t *testing.T) { Line: 68, Character: 24, }, - expected: "Returns a list of files whose names match the given shell glob pattern.\n", + expected: "Returns a list of files whose names match the given shell glob pattern.", expectedError: nil, }, { diff --git a/internal/language_features/template_context.go b/internal/language_features/template_context.go index 0eaeb736..5b0847e0 100644 --- a/internal/language_features/template_context.go +++ b/internal/language_features/template_context.go @@ -2,12 +2,17 @@ package languagefeatures import ( "fmt" + "reflect" + "strings" lsp "go.lsp.dev/protocol" + helmdocs "github.com/mrjosh/helm-ls/internal/documentation/helm" lsplocal "github.com/mrjosh/helm-ls/internal/lsp" "github.com/mrjosh/helm-ls/internal/tree-sitter/gotemplate" "github.com/mrjosh/helm-ls/internal/util" + "github.com/mrjosh/helm-ls/pkg/chart" + "github.com/mrjosh/helm-ls/pkg/chartutil" sitter "github.com/smacker/go-tree-sitter" ) @@ -22,6 +27,9 @@ func NewValuesFeature(genericDocumentUseCase *GenericDocumentUseCase) *TemplateC } func (f *TemplateContextFeature) AppropriateForNode(currentNodeType string, parentNodeType string, node *sitter.Node) bool { + if currentNodeType == gotemplate.NodeTypeDot { + return true + } return (parentNodeType == gotemplate.NodeTypeField && currentNodeType == gotemplate.NodeTypeIdentifier) || currentNodeType == gotemplate.NodeTypeFieldIdentifier || currentNodeType == gotemplate.NodeTypeField } @@ -57,10 +65,6 @@ func (f *TemplateContextFeature) getReferenceLocations(templateContext lsplocal. func (f *TemplateContextFeature) getDefinitionLocations(templateContext lsplocal.TemplateContext) []lsp.Location { locations := []lsp.Location{} - if len(templateContext) == 0 || templateContext == nil { - return []lsp.Location{} - } - switch templateContext[0] { case "Values": for _, value := range f.Chart.ResolveValueFiles(templateContext.Tail(), f.ChartStore) { @@ -72,5 +76,74 @@ func (f *TemplateContextFeature) getDefinitionLocations(templateContext lsplocal func (f *TemplateContextFeature) Hover() (string, error) { templateContext, err := f.getTemplateContext() + + if len(templateContext) == 1 { + return f.builtInObjectHover(templateContext[0]) + } + + switch templateContext[0] { + case "Values": + return f.valuesHover(templateContext.Tail()) + case "Chart": + doc, error := f.builtInOjectValues(templateContext.Tail().Format(), helmdocs.BuiltInOjectVals[templateContext[0]]) + value := f.getMetadataField(&f.Chart.ChartMetadata.Metadata, doc.Name) + return fmt.Sprintf("%s\n\n%s\n", doc.Doc, value), error + case "Release", "Files", "Capabilities", "Template": + doc, error := f.builtInOjectValues(templateContext.Tail().Format(), helmdocs.BuiltInOjectVals[templateContext[0]]) + return doc.Doc, error + } + return templateContext.Format(), err } + +func (f *TemplateContextFeature) valuesHover(templateContext lsplocal.TemplateContext) (string, error) { + var ( + valuesFiles = f.Chart.ResolveValueFiles(templateContext, f.ChartStore) + hoverResults = util.HoverResultsWithFiles{} + ) + for _, valuesFiles := range valuesFiles { + for _, valuesFile := range valuesFiles.ValuesFiles.AllValuesFiles() { + result, err := f.getTableOrValueForSelector(valuesFile.Values, strings.Join(valuesFiles.Selector, ".")) + if err == nil { + hoverResults = append(hoverResults, util.HoverResultWithFile{URI: valuesFile.URI, Value: result}) + } + } + } + return hoverResults.Format(f.ChartStore.RootURI), nil +} + +func (f *TemplateContextFeature) builtInOjectValues(key string, docs []helmdocs.HelmDocumentation) (helmdocs.HelmDocumentation, error) { + for _, item := range docs { + if key == item.Name { + return item, nil + } + } + return helmdocs.HelmDocumentation{}, fmt.Errorf("%s not found on built-in object", key) +} + +func (f *TemplateContextFeature) builtInObjectHover(key string) (string, error) { + for _, item := range helmdocs.BuiltInObjects { + if key == item.Name { + return item.Doc, nil + } + } + return "", fmt.Errorf("%s was no known built-in object", key) +} + +func (f *TemplateContextFeature) getMetadataField(v *chart.Metadata, fieldName string) string { + r := reflect.ValueOf(v) + field := reflect.Indirect(r).FieldByName(fieldName) + return util.FormatToYAML(field, fieldName) +} + +func (f *TemplateContextFeature) getTableOrValueForSelector(values chartutil.Values, selector string) (string, error) { + if len(selector) > 0 { + localValues, err := values.Table(selector) + if err != nil { + value, err := values.PathValue(selector) + return util.FormatToYAML(reflect.Indirect(reflect.ValueOf(value)), selector), err + } + return localValues.YAML() + } + return values.YAML() +} diff --git a/internal/handler/hover_test.go b/internal/language_features/template_context_hover_test.go similarity index 95% rename from internal/handler/hover_test.go rename to internal/language_features/template_context_hover_test.go index 38a2499f..59a1f2d4 100644 --- a/internal/handler/hover_test.go +++ b/internal/language_features/template_context_hover_test.go @@ -1,4 +1,4 @@ -package handler +package languagefeatures import ( "path/filepath" @@ -240,13 +240,16 @@ value } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - h := &langHandler{ - chartStore: &charts.ChartStore{ + genericDocumentUseCase := &GenericDocumentUseCase{ + Chart: tt.args.chart, + ChartStore: &charts.ChartStore{ RootURI: uri.New("file://tmp/"), Charts: tt.args.parentCharts, }, + // Node: tt.args.chart.ValuesFiles.MainValuesFile.Node, } - got, err := h.getValueHover(tt.args.chart, tt.args.splittedVar) + valuesFeature := NewValuesFeature(genericDocumentUseCase) + got, err := valuesFeature.valuesHover(tt.args.splittedVar) if (err != nil) != tt.wantErr { t.Errorf("langHandler.getValueHover() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/internal/language_features/usecases.go b/internal/language_features/usecases.go index d5b2c801..390ca87a 100644 --- a/internal/language_features/usecases.go +++ b/internal/language_features/usecases.go @@ -14,3 +14,8 @@ type ReferencesUseCase interface { UseCase References() (result []lsp.Location, err error) } + +type HoverUseCase interface { + UseCase + Hover() (result string, err error) +} diff --git a/internal/util/values.go b/internal/util/values.go new file mode 100644 index 00000000..e04c20df --- /dev/null +++ b/internal/util/values.go @@ -0,0 +1,50 @@ +package util + +import ( + "fmt" + "reflect" + + "github.com/mrjosh/helm-ls/pkg/chartutil" + "gopkg.in/yaml.v2" +) + +func GetTableOrValueForSelector(values chartutil.Values, selector string) (string, error) { + if len(selector) > 0 { + localValues, err := values.Table(selector) + if err != nil { + value, err := values.PathValue(selector) + return FormatToYAML(reflect.Indirect(reflect.ValueOf(value)), selector), err + } + return localValues.YAML() + } + return values.YAML() +} + +func FormatToYAML(field reflect.Value, fieldName string) string { + switch field.Kind() { + case reflect.String: + return field.String() + case reflect.Map: + return toYAML(field.Interface()) + case reflect.Slice: + return toYAML(map[string]interface{}{fieldName: field.Interface()}) + case reflect.Bool: + return fmt.Sprint(GetBoolType(field)) + case reflect.Float32, reflect.Float64: + return fmt.Sprint(field.Float()) + default: + return "" + } +} + +func toYAML(value interface{}) string { + valBytes, _ := yaml.Marshal(value) + return string(valBytes) +} + +func GetBoolType(value interface{}) string { + if val, ok := value.(bool); ok && val { + return "True" + } + return "False" +}