internal/ls/codelens.go (179 lines of code) (raw):

package ls import ( "context" "github.com/microsoft/typescript-go/internal/ast" "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/diagnostics" "github.com/microsoft/typescript-go/internal/locale" "github.com/microsoft/typescript-go/internal/ls/lsutil" "github.com/microsoft/typescript-go/internal/lsp/lsproto" "github.com/microsoft/typescript-go/internal/scanner" ) func (l *LanguageService) ProvideCodeLenses(ctx context.Context, documentURI lsproto.DocumentUri) (lsproto.CodeLensResponse, error) { _, file := l.getProgramAndFile(documentURI) userPrefs := &l.UserPreferences().CodeLens if !userPrefs.ReferencesCodeLensEnabled && !userPrefs.ImplementationsCodeLensEnabled { return lsproto.CodeLensResponse{}, nil } // Keeps track of the last symbol to avoid duplicating code lenses across overloads. var lastSymbol *ast.Symbol var result []*lsproto.CodeLens var visit func(node *ast.Node) bool visit = func(node *ast.Node) bool { if ctx.Err() != nil { return true } if currentSymbol := node.Symbol(); lastSymbol != currentSymbol { lastSymbol = currentSymbol if userPrefs.ReferencesCodeLensEnabled && isValidReferenceLensNode(node, userPrefs) { result = append(result, l.newCodeLensForNode(documentURI, file, node, lsproto.CodeLensKindReferences)) } if userPrefs.ImplementationsCodeLensEnabled && isValidImplementationsCodeLensNode(node, userPrefs) { result = append(result, l.newCodeLensForNode(documentURI, file, node, lsproto.CodeLensKindImplementations)) } } savedLastSymbol := lastSymbol node.ForEachChild(visit) lastSymbol = savedLastSymbol return false } visit(file.AsNode()) return lsproto.CodeLensResponse{ CodeLenses: &result, }, nil } func (l *LanguageService) ResolveCodeLens(ctx context.Context, codeLens *lsproto.CodeLens, showLocationsCommandName *string) (*lsproto.CodeLens, error) { uri := codeLens.Data.Uri _, sourceFile := l.tryGetProgramAndFile(uri.FileName()) if sourceFile == nil || l.converters.PositionToLineAndCharacter(sourceFile, core.TextPos(sourceFile.End())).Line < codeLens.Range.Start.Line { // This can happen if a codeLens/resolve request comes in after a program change. // While it's true that handlers should latch onto a specific snapshot // while processing requests, we just set `Data.Uri` based on // some older snapshot's contents. The content could have been modified, // or the file itself could have been removed from the session entirely. // Note this won't bail out on every change, but will prevent crashing // based on non-existent files and line maps from shortened files. return codeLens, lsproto.ErrorCodeContentModified } textDoc := lsproto.TextDocumentIdentifier{ Uri: uri, } locale := locale.FromContext(ctx) var locs []lsproto.Location var lensTitle string switch codeLens.Data.Kind { case lsproto.CodeLensKindReferences: origNode, symbolsAndEntries, ok := l.ProvideSymbolsAndEntries(ctx, uri, codeLens.Range.Start, false /*isRename*/) if ok { references, err := l.ProvideReferencesFromSymbolAndEntries( ctx, &lsproto.ReferenceParams{ TextDocument: textDoc, Position: codeLens.Range.Start, Context: &lsproto.ReferenceContext{ // Don't include the declaration in the references count. IncludeDeclaration: false, }, }, origNode, symbolsAndEntries, ) if err != nil { return nil, err } if references.Locations != nil { locs = *references.Locations } } if len(locs) == 1 { lensTitle = diagnostics.X_1_reference.Localize(locale) } else { lensTitle = diagnostics.X_0_references.Localize(locale, len(locs)) } case lsproto.CodeLensKindImplementations: // "Force" link support to be false so that we only get `Locations` back, // and don't include the "current" node in the results. findImplsOptions := provideImplementationsOpts{ requireLocationsResult: true, dropOriginNodes: true, } implementations, err := l.provideImplementationsEx( ctx, &lsproto.ImplementationParams{ TextDocument: textDoc, Position: codeLens.Range.Start, }, findImplsOptions, ) if err != nil { return nil, err } if implementations.Locations != nil { locs = *implementations.Locations } if len(locs) == 1 { lensTitle = diagnostics.X_1_implementation.Localize(locale) } else { lensTitle = diagnostics.X_0_implementations.Localize(locale, len(locs)) } } cmd := &lsproto.Command{ Title: lensTitle, } if len(locs) > 0 && showLocationsCommandName != nil { cmd.Command = *showLocationsCommandName cmd.Arguments = &[]any{ uri, codeLens.Range.Start, locs, } } codeLens.Command = cmd return codeLens, nil } func (l *LanguageService) newCodeLensForNode(fileUri lsproto.DocumentUri, file *ast.SourceFile, node *ast.Node, kind lsproto.CodeLensKind) *lsproto.CodeLens { nodeForRange := node nodeName := node.Name() if nodeName != nil { nodeForRange = nodeName } pos := scanner.SkipTrivia(file.Text(), nodeForRange.Pos()) return &lsproto.CodeLens{ Range: lsproto.Range{ Start: l.converters.PositionToLineAndCharacter(file, core.TextPos(pos)), End: l.converters.PositionToLineAndCharacter(file, core.TextPos(node.End())), }, Data: &lsproto.CodeLensData{ Kind: kind, Uri: fileUri, }, } } func isValidImplementationsCodeLensNode(node *ast.Node, userPrefs *lsutil.CodeLensUserPreferences) bool { switch node.Kind { // Always show on interfaces case ast.KindInterfaceDeclaration: // TODO: ast.KindTypeAliasDeclaration? return true // If configured, show on interface methods case ast.KindMethodSignature: return userPrefs.ImplementationsCodeLensShowOnInterfaceMethods && node.Parent.Kind == ast.KindInterfaceDeclaration // If configured, show on all class methods - but not private ones. case ast.KindMethodDeclaration: if userPrefs.ImplementationsCodeLensShowOnAllClassMethods && node.Parent.Kind == ast.KindClassDeclaration { return !ast.HasModifier(node, ast.ModifierFlagsPrivate) && node.Name().Kind != ast.KindPrivateIdentifier } fallthrough // Always show on abstract classes/properties/methods case ast.KindClassDeclaration, ast.KindConstructor, ast.KindGetAccessor, ast.KindSetAccessor, ast.KindPropertyDeclaration: return ast.HasModifier(node, ast.ModifierFlagsAbstract) } return false } func isValidReferenceLensNode(node *ast.Node, userPrefs *lsutil.CodeLensUserPreferences) bool { switch node.Kind { case ast.KindFunctionDeclaration: if userPrefs.ReferencesCodeLensShowOnAllFunctions { return true } fallthrough case ast.KindVariableDeclaration: return ast.GetCombinedModifierFlags(node)&ast.ModifierFlagsExport != 0 case ast.KindClassDeclaration, ast.KindInterfaceDeclaration, ast.KindTypeAliasDeclaration, ast.KindEnumDeclaration, ast.KindEnumMember: return true case ast.KindMethodDeclaration, ast.KindMethodSignature, ast.KindConstructor, ast.KindGetAccessor, ast.KindSetAccessor, ast.KindPropertyDeclaration, ast.KindPropertySignature: // Don't show if child and parent have same start // For https://github.com/microsoft/vscode/issues/90396 // !!! switch node.Parent.Kind { case ast.KindClassDeclaration, ast.KindInterfaceDeclaration, ast.KindTypeLiteral: return true } } return false }