diff --git a/internal/handler/definition.go b/internal/handler/definition.go index 08800d46..6c8b2cd6 100644 --- a/internal/handler/definition.go +++ b/internal/handler/definition.go @@ -2,11 +2,11 @@ package handler import ( "context" - "errors" "fmt" "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" @@ -16,16 +16,14 @@ import ( ) func (h *langHandler) Definition(ctx context.Context, params *lsp.DefinitionParams) (result []lsp.Location, err error) { - doc, ok := h.documents.Get(params.TextDocument.URI) - if !ok { - return nil, errors.New("Could not get document: " + params.TextDocument.URI.Filename()) - } - chart, err := h.chartStore.GetChartForDoc(params.TextDocument.URI) + genericDocumentUseCase, err := h.NewGenericDocumentUseCase(params.TextDocumentPositionParams) if err != nil { - logger.Error("Error getting chart info for file", params.TextDocument.URI, err) + return nil, err } + doc := genericDocumentUseCase.Document + chart := genericDocumentUseCase.Chart - result, err = h.definitionAstParsing(chart, doc, params.Position) + result, err = h.definitionAstParsing(genericDocumentUseCase, chart, doc, params.Position) if err != nil { // suppress errors for clients // otherwise using go-to-definition on words that have no definition @@ -36,21 +34,16 @@ func (h *langHandler) Definition(ctx context.Context, params *lsp.DefinitionPara return result, nil } -func (h *langHandler) definitionAstParsing(chart *charts.Chart, doc *lsplocal.Document, position lsp.Position) ([]lsp.Location, error) { +func (h *langHandler) definitionAstParsing(genericDocumentUseCase languagefeatures.GenericDocumentUseCase, chart *charts.Chart, doc *lsplocal.Document, position lsp.Position) ([]lsp.Location, error) { var ( - currentNode = lsplocal.NodeAtPosition(doc.Ast, position) - pointToLookUp = sitter.Point{ - Row: position.Line, - Column: position.Character, - } - relevantChildNode = lsplocal.FindRelevantChildNode(currentNode, pointToLookUp) + relevantChildNode = genericDocumentUseCase.Node + parentType = relevantChildNode.Parent().Type() ) nodeType := relevantChildNode.Type() switch nodeType { case gotemplate.NodeTypeIdentifier: logger.Println("Parent type", relevantChildNode.Parent().Type()) - parentType := relevantChildNode.Parent().Type() if parentType == gotemplate.NodeTypeVariable { return h.getDefinitionForVariable(relevantChildNode, doc) } @@ -63,16 +56,21 @@ func (h *langHandler) definitionAstParsing(chart *charts.Chart, doc *lsplocal.Do return h.getDefinitionForValue(chart, relevantChildNode, doc) } + if parentType == gotemplate.NodeTypeArgumentList { + includesCallFeature := languagefeatures.NewIncludesCallFeature(genericDocumentUseCase) + return includesCallFeature.Definition() + } + return []lsp.Location{}, fmt.Errorf("Definition not implemented for node type %s", relevantChildNode.Type()) } func (h *langHandler) getDefinitionForVariable(node *sitter.Node, doc *lsplocal.Document) ([]lsp.Location, error) { variableName := node.Content([]byte(doc.Content)) - defintionNode := lsplocal.GetVariableDefinition(variableName, node.Parent(), doc.Content) - if defintionNode == nil { + definitionNode := lsplocal.GetVariableDefinition(variableName, node.Parent(), doc.Content) + if definitionNode == nil { return []lsp.Location{}, fmt.Errorf("Could not find definition for %s. Variable definition not found", variableName) } - return []lsp.Location{{URI: doc.URI, Range: lsp.Range{Start: util.PointToPosition(defintionNode.StartPoint())}}}, nil + return []lsp.Location{{URI: doc.URI, Range: lsp.Range{Start: util.PointToPosition(definitionNode.StartPoint())}}}, nil } // getDefinitionForFixedIdentifier checks if the current identifier has a constant definition and returns it @@ -119,7 +117,7 @@ func (h *langHandler) getDefinitionForValue(chart *charts.Chart, node *sitter.No } } - if err == nil && definitionFileURI != "" { + if definitionFileURI != "" { locations := []lsp.Location{} for _, position := range positions { locations = append(locations, lsp.Location{ diff --git a/internal/handler/definition_test.go b/internal/handler/definition_test.go index 2bb7616a..348b8fd8 100644 --- a/internal/handler/definition_test.go +++ b/internal/handler/definition_test.go @@ -7,6 +7,7 @@ import ( "testing" "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" sitter "github.com/smacker/go-tree-sitter" diff --git a/internal/handler/hover.go b/internal/handler/hover.go index 344b1bef..a294771e 100644 --- a/internal/handler/hover.go +++ b/internal/handler/hover.go @@ -4,12 +4,11 @@ import ( "context" "errors" "fmt" - "path/filepath" "reflect" - "sort" "strings" "github.com/mrjosh/helm-ls/internal/charts" + 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" @@ -17,18 +16,15 @@ import ( "github.com/mrjosh/helm-ls/pkg/chart" "github.com/mrjosh/helm-ls/pkg/chartutil" lsp "go.lsp.dev/protocol" - "go.lsp.dev/uri" ) func (h *langHandler) Hover(ctx context.Context, params *lsp.HoverParams) (result *lsp.Hover, err error) { - doc, ok := h.documents.Get(params.TextDocument.URI) - if !ok { - return nil, errors.New("Could not get document: " + params.TextDocument.URI.Filename()) - } - chart, err := h.chartStore.GetChartForDoc(params.TextDocument.URI) + genericDocumentUseCase, err := h.NewGenericDocumentUseCase(params.TextDocumentPositionParams) if err != nil { - logger.Error("Error getting chart info for file", params.TextDocument.URI, err) + return nil, err } + doc := genericDocumentUseCase.Document + chart := genericDocumentUseCase.Chart var ( currentNode = lspinternal.NodeAtPosition(doc.Ast, params.Position) @@ -62,6 +58,11 @@ func (h *langHandler) Hover(ctx context.Context, params *lsp.HoverParams) (resul if ct == gotemplate.NodeTypeDot { word = lspinternal.TraverseIdentifierPathUp(currentNode, doc) } + if pt == gotemplate.NodeTypeArgumentList { + includesCallFeature := languagefeatures.NewIncludesCallFeature(genericDocumentUseCase) + response, err := includesCallFeature.Hover() + return util.BuildHoverResponse(response, wordRange), err + } var ( splitted = strings.Split(word, ".") @@ -145,39 +146,20 @@ func (h *langHandler) getChartMetadataHover(metadata *chart.Metadata, key string func (h *langHandler) getValueHover(chart *charts.Chart, splittedVar []string) (result string, err error) { var ( - valuesFiles = chart.ResolveValueFiles(splittedVar, h.chartStore) - results = map[uri.URI]string{} + 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 { - results[valuesFile.URI] = result + hoverResults = append(hoverResults, util.HoverResultWithFile{URI: valuesFile.URI, Value: result}) } } } - keys := make([]string, 0, len(results)) - for u := range results { - keys = append(keys, string(u)) - } - - sort.Sort(sort.Reverse(sort.StringSlice(keys))) - - for _, key := range keys { - uriKey := uri.New(key) - value := results[uriKey] - if value == "" { - value = "\"\"" - } - filepath, err := filepath.Rel(h.chartStore.RootURI.Filename(), uriKey.Filename()) - if err != nil { - filepath = uriKey.Filename() - } - result += fmt.Sprintf("### %s\n%s\n\n", filepath, value) - } - return result, nil + return hoverResults.Format(h.chartStore.RootURI), nil } func (h *langHandler) getTableOrValueForSelector(values chartutil.Values, selector string) (string, error) { diff --git a/internal/handler/references_test.go b/internal/handler/references_test.go index 9257d2a7..f380da65 100644 --- a/internal/handler/references_test.go +++ b/internal/handler/references_test.go @@ -22,7 +22,7 @@ func TestRefercesIncludes(t *testing.T) { expected := []lsp.Location{ { - URI: "file:///tmp/testfile.yaml", + URI: uri.File("/tmp/testfile.yaml"), Range: protocol.Range{ Start: protocol.Position{ Line: 0x1, Character: 0x3, @@ -34,7 +34,7 @@ func TestRefercesIncludes(t *testing.T) { }, }, protocol.Location{ - URI: "file:///tmp/testfile.yaml", + URI: uri.File("/tmp/testfile.yaml"), Range: protocol.Range{ Start: protocol.Position{ Line: 0x2, @@ -47,7 +47,7 @@ func TestRefercesIncludes(t *testing.T) { }, }, protocol.Location{ - URI: "file:///tmp/testfile.yaml", + URI: uri.File("/tmp/testfile.yaml"), Range: protocol.Range{ Start: protocol.Position{ Line: 0x0, diff --git a/internal/language_features/includes.go b/internal/language_features/includes.go index d4033e59..f1640950 100644 --- a/internal/language_features/includes.go +++ b/internal/language_features/includes.go @@ -11,12 +11,12 @@ type IncludesFeature struct { GenericDocumentUseCase } -// {{ include "name" . }} +// should be called on {{ include "name" . }} type IncludesCallFeature struct { IncludesFeature } -// {{ define "name" }} +// should be called on {{ define "name" }} type IncludesDefinitionFeature struct { IncludesFeature } @@ -34,8 +34,7 @@ func NewIncludesDefinitionFeature(genericDocumentUseCase GenericDocumentUseCase) } func (f *IncludesCallFeature) References() (result []lsp.Location, err error) { - functionCallNode := f.Node.Parent().Parent() - includeName, err := lsplocal.ParseIncludeFunctionCall(functionCallNode, []byte(f.GenericDocumentUseCase.Document.Content)) + includeName, err := f.getIncludeName() if err != nil { return []lsp.Location{}, err } @@ -44,6 +43,12 @@ func (f *IncludesCallFeature) References() (result []lsp.Location, err error) { return locations, nil } +func (f *IncludesCallFeature) getIncludeName() (string, error) { + functionCallNode := f.Node.Parent().Parent() + includeName, err := lsplocal.ParseIncludeFunctionCall(functionCallNode, []byte(f.GenericDocumentUseCase.Document.Content)) + return includeName, err +} + func (f *IncludesDefinitionFeature) References() (result []lsp.Location, err error) { includeName := util.RemoveQuotes(f.GenericDocumentUseCase.NodeContent()) @@ -62,3 +67,51 @@ func (f *IncludesFeature) getReferenceLocations(includeName string) []lsp.Locati return locations } + +func (f *IncludesFeature) getDefinitionLocations(includeName string) []lsp.Location { + locations := []lsp.Location{} + for _, doc := range f.GenericDocumentUseCase.DocumentStore.GetAllDocs() { + referenceRanges := doc.SymbolTable.GetIncludeDefinitions(includeName) + for _, referenceRange := range referenceRanges { + locations = append(locations, util.RangeToLocation(doc.URI, referenceRange)) + } + } + + return locations +} + +func (f *IncludesFeature) getDefinitionsHover(includeName string) util.HoverResultsWithFiles { + result := util.HoverResultsWithFiles{} + for _, doc := range f.GenericDocumentUseCase.DocumentStore.GetAllDocs() { + referenceRanges := doc.SymbolTable.GetIncludeDefinitions(includeName) + for _, referenceRange := range referenceRanges { + node := doc.Ast.RootNode().NamedDescendantForPointRange(referenceRange.StartPoint, referenceRange.EndPoint) + if node != nil { + result = append(result, util.HoverResultWithFile{ + Value: node.Content([]byte(doc.Content)), + URI: doc.URI, + }) + } + } + } + + return result +} + +func (f *IncludesCallFeature) Hover() (string, error) { + includeName, err := f.getIncludeName() + if err != nil { + return "", err + } + + result := f.getDefinitionsHover(includeName) + return result.Format(f.GenericDocumentUseCase.Document.URI), nil +} + +func (f *IncludesCallFeature) Definition() (result []lsp.Location, err error) { + includeName, err := f.getIncludeName() + if err != nil { + return []lsp.Location{}, err + } + return f.getDefinitionLocations(includeName), nil +} diff --git a/internal/lsp/symbol_table.go b/internal/lsp/symbol_table.go index bdb5e8be..b5283f7e 100644 --- a/internal/lsp/symbol_table.go +++ b/internal/lsp/symbol_table.go @@ -9,14 +9,14 @@ import ( type SymbolTable struct { values map[string][]sitter.Range includeDefinitions map[string][]sitter.Range - includeReferences map[string][]sitter.Range + includeUseages map[string][]sitter.Range } func NewSymbolTable(ast *sitter.Tree, content []byte) *SymbolTable { s := &SymbolTable{ values: make(map[string][]sitter.Range), includeDefinitions: make(map[string][]sitter.Range), - includeReferences: make(map[string][]sitter.Range), + includeUseages: make(map[string][]sitter.Range), } s.parseTree(ast, content) return s @@ -35,19 +35,15 @@ func (s *SymbolTable) AddIncludeDefinition(symbol string, pointRange sitter.Rang } func (s *SymbolTable) AddIncludeReference(symbol string, pointRange sitter.Range) { - s.includeReferences[symbol] = append(s.includeReferences[symbol], pointRange) + s.includeUseages[symbol] = append(s.includeUseages[symbol], pointRange) } -func (s *SymbolTable) GetIncludeDefinitions(symbol string) ([]sitter.Range, bool) { - result, ok := s.includeDefinitions[symbol] - if !ok { - return []sitter.Range{}, false - } - return result, true +func (s *SymbolTable) GetIncludeDefinitions(symbol string) []sitter.Range { + return s.includeDefinitions[symbol] } func (s *SymbolTable) GetIncludeReference(symbol string) []sitter.Range { - result := s.includeReferences[symbol] + result := s.includeUseages[symbol] definitions := s.includeDefinitions[symbol] return append(result, definitions...) } diff --git a/internal/util/lsp.go b/internal/util/lsp.go index e1fd5932..6cea72cc 100644 --- a/internal/util/lsp.go +++ b/internal/util/lsp.go @@ -1,6 +1,43 @@ package util -import lsp "go.lsp.dev/protocol" +import ( + "fmt" + "path/filepath" + "sort" + + lsp "go.lsp.dev/protocol" + "go.lsp.dev/uri" +) + +type HoverResultWithFile struct { + Value string + URI uri.URI +} + +type HoverResultsWithFiles []HoverResultWithFile + +func (h HoverResultsWithFiles) Format(rootURI uri.URI) string { + var formatted string + sort.Slice(h, func(i, j int) bool { + return h[i].URI < h[j].URI + }) + for _, result := range h { + value := result.Value + if value == "" { + value = "\"\"" + } + filepath, err := filepath.Rel(rootURI.Filename(), result.URI.Filename()) + if err != nil { + filepath = result.URI.Filename() + } + formatted += fmt.Sprintf("### %s\n%s\n\n", filepath, value) + } + return formatted +} + +func (h *HoverResultsWithFiles) Add(hoverResult HoverResultWithFile) { + *h = append(*h, hoverResult) +} func BuildHoverResponse(value string, wordRange lsp.Range) *lsp.Hover { content := lsp.MarkupContent{