208 lines
6.3 KiB
Go
208 lines
6.3 KiB
Go
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.IsTrue() && !userPrefs.ImplementationsCodeLensEnabled.IsTrue() {
|
|
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.IsTrue() && isValidReferenceLensNode(node, userPrefs) {
|
|
result = append(result, l.newCodeLensForNode(documentURI, file, node, lsproto.CodeLensKindReferences))
|
|
}
|
|
|
|
if userPrefs.ImplementationsCodeLensEnabled.IsTrue() && 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, orchestrator CrossProjectOrchestrator) (*lsproto.CodeLens, error) {
|
|
uri := codeLens.Data.Uri
|
|
textDoc := lsproto.TextDocumentIdentifier{Uri: uri}
|
|
locale := locale.FromContext(ctx)
|
|
var locs []lsproto.Location
|
|
var lensTitle string
|
|
switch codeLens.Data.Kind {
|
|
case lsproto.CodeLensKindReferences:
|
|
referencesResp, err := l.ProvideReferences(ctx, &lsproto.ReferenceParams{
|
|
TextDocument: textDoc,
|
|
Position: codeLens.Range.Start,
|
|
Context: &lsproto.ReferenceContext{
|
|
// Don't include the declaration in the references count.
|
|
IncludeDeclaration: false,
|
|
},
|
|
}, orchestrator)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if referencesResp.Locations != nil {
|
|
locs = *referencesResp.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:
|
|
|
|
implementations, err := l.provideImplementationsEx(
|
|
ctx,
|
|
&lsproto.ImplementationParams{
|
|
TextDocument: textDoc,
|
|
Position: codeLens.Range.Start,
|
|
},
|
|
// "Force" link support to be false so that we only get `Locations` back,
|
|
// and don't include the "current" node in the results.
|
|
symbolEntryTransformOptions{
|
|
requireLocationsResult: true,
|
|
dropOriginNodes: true,
|
|
},
|
|
orchestrator,
|
|
)
|
|
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.IsTrue() && node.Parent.Kind == ast.KindInterfaceDeclaration
|
|
|
|
// If configured, show on all class methods - but not private ones.
|
|
case ast.KindMethodDeclaration:
|
|
if userPrefs.ImplementationsCodeLensShowOnAllClassMethods.IsTrue() && 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.IsTrue() {
|
|
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
|
|
}
|