diff --git a/packages/typescript/index.ts b/packages/typescript/index.ts index 4865aaea..1e6995d1 100644 --- a/packages/typescript/index.ts +++ b/packages/typescript/index.ts @@ -2,20 +2,27 @@ import type { CancellationToken, CompletionList, CompletionTriggerKind, + DocumentHighlight, FileChangeType, + Location, + ParameterInformation, Result, ServiceContext, ServicePlugin, ServicePluginInstance, + SignatureHelpTriggerKind, + SignatureInformation, VirtualCode, WorkspaceEdit } from '@volar/language-service'; import { getDocumentRegistry } from '@volar/typescript'; +import * as path from 'path-browserify'; import * as semver from 'semver'; import type * as ts from 'typescript'; import * as tsWithImportCache from 'typescript-auto-import-cache'; import type { TextDocument } from 'vscode-languageserver-textdocument'; import { getFormatCodeSettings } from './lib/configs/getFormatCodeSettings'; +import { getUserPreferences } from './lib/configs/getUserPreferences'; import * as codeActions from './lib/features/codeAction'; import * as codeActionResolve from './lib/features/codeActionResolve'; import * as completions from './lib/features/completions/basic'; @@ -23,34 +30,28 @@ import * as directiveCommentCompletions from './lib/features/completions/directi import * as jsDocCompletions from './lib/features/completions/jsDoc'; import * as completionResolve from './lib/features/completions/resolve'; import * as diagnostics from './lib/features/diagnostics'; -import * as documentHighlight from './lib/features/documentHighlight'; -import * as fileReferences from './lib/features/fileReferences'; -import * as fileRename from './lib/features/fileRename'; -import * as implementation from './lib/features/implementation'; -import * as inlayHints from './lib/features/inlayHints'; -import * as references from './lib/features/references'; -import * as selectionRanges from './lib/features/selectionRanges'; import * as semanticTokens from './lib/features/semanticTokens'; -import * as signatureHelp from './lib/features/signatureHelp'; -import * as workspaceSymbols from './lib/features/workspaceSymbol'; -import { getConfigTitle, isJsonDocument, isTsDocument, safeCall } from './lib/shared'; +import { getConfigTitle, isJsonDocument, isTsDocument, notEmpty, safeCall } from './lib/shared'; import type { SharedContext } from './lib/types'; import { convertCallHierarchyIncomingCall, convertCallHierarchyItem, convertCallHierarchyOutgoingCall, convertDefinitionInfoAndBoundSpan, + convertDocumentSpanToLocation, convertDocumentSpantoLocationLink, convertFileTextChanges, + convertHighlightSpan, + convertInlayHint, convertNavTree, + convertNavigateToItem, convertOutliningSpan, convertQuickInfo, convertRenameLocations, + convertSelectionRange, convertTextChange, convertTextSpan, } from './lib/utils/lspConverters'; -import * as path from 'path-browserify'; -import { getUserPreferences } from './lib/configs/getUserPreferences'; export * from '@volar/typescript'; @@ -223,21 +224,12 @@ export function create( throw new Error(`getTextDocument: uri not found: ${uri}`); }, }; - const findReferences = references.register(ctx); - const findFileReferences = fileReferences.register(ctx); - const findImplementations = implementation.register(ctx); - const getEditsForFileRename = fileRename.register(ctx); const getCodeActions = codeActions.register(ctx); const doCodeActionResolve = codeActionResolve.register(ctx); - const getInlayHints = inlayHints.register(ctx); - const findDocumentHighlights = documentHighlight.register(ctx); - const findWorkspaceSymbols = workspaceSymbols.register(ctx); const doComplete = completions.register(ctx); const doCompletionResolve = completionResolve.register(ctx); const doDirectiveCommentComplete = directiveCommentCompletions.register(); const doJsDocComplete = jsDocCompletions.register(ctx); - const getSignatureHelp = signatureHelp.register(ctx); - const getSelectionRanges = selectionRanges.register(ctx); const doValidation = diagnostics.register(ctx); const getDocumentSemanticTokens = semanticTokens.register(ctx); @@ -407,8 +399,20 @@ export function create( if (!isSemanticDocument(document)) return; - return worker(token, () => { - return getInlayHints(document, range); + return worker(token, async () => { + const preferences = await getUserPreferences(ctx, document); + const fileName = ctx.uriToFileName(document.uri); + const start = document.offsetAt(range.start); + const end = document.offsetAt(range.end); + const inlayHints = safeCall(() => + 'provideInlayHints' in ctx.languageService + ? ctx.languageService.provideInlayHints(fileName, { start, length: end - start }, preferences) + : [] + ); + if (!inlayHints) { + return []; + } + return inlayHints.map(hint => convertInlayHint(hint, document)); }); }, @@ -537,7 +541,13 @@ export function create( return; return worker(token, () => { - return findImplementations(document, position); + const fileName = ctx.uriToFileName(document.uri); + const offset = document.offsetAt(position); + const entries = safeCall(() => ctx.languageService.getImplementationAtPosition(fileName, offset)); + if (!entries) { + return []; + } + return entries.map(entry => convertDocumentSpantoLocationLink(entry, ctx)); }); }, @@ -547,7 +557,28 @@ export function create( return; return worker(token, () => { - return findReferences(document, position, referenceContext); + const fileName = ctx.uriToFileName(document.uri); + const offset = document.offsetAt(position); + const references = safeCall(() => ctx.languageService.findReferences(fileName, offset)); + if (!references) { + return []; + } + const result: Location[] = []; + for (const reference of references) { + if (referenceContext.includeDeclaration) { + const definition = convertDocumentSpanToLocation(reference.definition, ctx); + if (definition) { + result.push(definition); + } + } + for (const referenceEntry of reference.references) { + const reference = convertDocumentSpanToLocation(referenceEntry, ctx); + if (reference) { + result.push(reference); + } + } + } + return result; }); }, @@ -557,7 +588,12 @@ export function create( return; return worker(token, () => { - return findFileReferences(document); + const fileName = ctx.uriToFileName(document.uri); + const entries = safeCall(() => ctx.languageService.getFileReferences(fileName)); + if (!entries) { + return []; + } + return entries.map(entry => convertDocumentSpanToLocation(entry, ctx)); }); }, @@ -567,7 +603,19 @@ export function create( return; return worker(token, () => { - return findDocumentHighlights(document, position); + const fileName = ctx.uriToFileName(document.uri); + const offset = document.offsetAt(position); + const highlights = safeCall(() => ctx.languageService.getDocumentHighlights(fileName, offset, [fileName])); + if (!highlights) { + return []; + } + const results: DocumentHighlight[] = []; + for (const highlight of highlights) { + for (const span of highlight.highlightSpans) { + results.push(convertHighlightSpan(span, document)); + } + } + return results; }); }, @@ -583,13 +631,33 @@ export function create( provideWorkspaceSymbols(query, token) { return worker(token, () => { - return findWorkspaceSymbols(query); + const items = safeCall(() => ctx.languageService.getNavigateToItems(query)); + if (!items) { + return []; + } + return items + .filter(item => item.containerName || item.kind !== 'alias') + .map(item => convertNavigateToItem(item, ctx.getTextDocument(ctx.fileNameToUri(item.fileName)))) + .filter(notEmpty); }); }, provideFileRenameEdits(oldUri, newUri, token) { - return worker(token, () => { - return getEditsForFileRename(oldUri, newUri); + return worker(token, async () => { + const document = ctx.getTextDocument(oldUri); + const [formatOptions, preferences] = await Promise.all([ + getFormatCodeSettings(ctx, document), + getUserPreferences(ctx, document), + ]); + + const fileToRename = ctx.uriToFileName(oldUri); + const newFilePath = ctx.uriToFileName(newUri); + const response = safeCall(() => ctx.languageService.getEditsForFileRename(fileToRename, newFilePath, formatOptions, preferences)); + if (!response?.length) { + return; + } + + return convertFileTextChanges(response, ctx.fileNameToUri, ctx.getTextDocument); }); }, @@ -599,7 +667,17 @@ export function create( return; return worker(token, () => { - return getSelectionRanges(document, positions); + return positions + .map(position => { + const fileName = ctx.uriToFileName(document.uri); + const offset = document.offsetAt(position); + const range = safeCall(() => ctx.languageService.getSmartSelectionRange(fileName, offset)); + if (!range) { + return; + } + return convertSelectionRange(range, document); + }) + .filter(notEmpty); }); }, @@ -609,7 +687,58 @@ export function create( return; return worker(token, () => { - return getSignatureHelp(document, position, context); + const options: ts.SignatureHelpItemsOptions = {}; + if (context?.triggerKind === 1 satisfies typeof SignatureHelpTriggerKind.Invoked) { + options.triggerReason = { + kind: 'invoked' + }; + } + else if (context?.triggerKind === 2 satisfies typeof SignatureHelpTriggerKind.TriggerCharacter) { + options.triggerReason = { + kind: 'characterTyped', + triggerCharacter: context.triggerCharacter as ts.SignatureHelpTriggerCharacter, + }; + } + else if (context?.triggerKind === 3 satisfies typeof SignatureHelpTriggerKind.ContentChange) { + options.triggerReason = { + kind: 'retrigger', + triggerCharacter: context.triggerCharacter as ts.SignatureHelpRetriggerCharacter, + }; + } + + const fileName = ctx.uriToFileName(document.uri); + const offset = document.offsetAt(position); + const helpItems = safeCall(() => ctx.languageService.getSignatureHelpItems(fileName, offset, options)); + if (!helpItems) { + return; + } + + return { + activeSignature: helpItems.selectedItemIndex, + activeParameter: helpItems.argumentIndex, + signatures: helpItems.items.map(item => { + const signature: SignatureInformation = { + label: '', + documentation: undefined, + parameters: [] + }; + signature.label += ts.displayPartsToString(item.prefixDisplayParts); + item.parameters.forEach((p, i, a) => { + const label = ts.displayPartsToString(p.displayParts); + const parameter: ParameterInformation = { + label, + documentation: ts.displayPartsToString(p.documentation) + }; + signature.label += label; + signature.parameters!.push(parameter); + if (i < a.length - 1) { + signature.label += ts.displayPartsToString(item.separatorDisplayParts); + } + }); + signature.label += ts.displayPartsToString(item.suffixDisplayParts); + return signature; + }), + }; }); }, }; diff --git a/packages/typescript/lib/features/documentHighlight.ts b/packages/typescript/lib/features/documentHighlight.ts deleted file mode 100644 index ac632be7..00000000 --- a/packages/typescript/lib/features/documentHighlight.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type * as vscode from '@volar/language-service'; -import { safeCall } from '../shared'; -import type { SharedContext } from '../types'; -import type { TextDocument } from 'vscode-languageserver-textdocument'; - -export function register(ctx: SharedContext) { - const { ts } = ctx; - return (document: TextDocument, position: vscode.Position): vscode.DocumentHighlight[] => { - const fileName = ctx.uriToFileName(document.uri); - const offset = document.offsetAt(position); - const highlights = safeCall(() => ctx.languageService.getDocumentHighlights(fileName, offset, [fileName])); - if (!highlights) return []; - - const results: vscode.DocumentHighlight[] = []; - - for (const highlight of highlights) { - for (const span of highlight.highlightSpans) { - results.push({ - kind: span.kind === ts.HighlightSpanKind.writtenReference - ? 3 satisfies typeof vscode.DocumentHighlightKind.Write - : 2 satisfies typeof vscode.DocumentHighlightKind.Read, - range: { - start: document.positionAt(span.textSpan.start), - end: document.positionAt(span.textSpan.start + span.textSpan.length), - }, - }); - } - } - - return results; - }; -} diff --git a/packages/typescript/lib/features/fileReferences.ts b/packages/typescript/lib/features/fileReferences.ts deleted file mode 100644 index ad44652c..00000000 --- a/packages/typescript/lib/features/fileReferences.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type * as vscode from '@volar/language-service'; -import { safeCall } from '../shared'; -import type { SharedContext } from '../types'; -import { convertDocumentSpanToLocation } from '../utils/lspConverters'; -import type { TextDocument } from 'vscode-languageserver-textdocument'; - -export function register(ctx: SharedContext) { - return (document: TextDocument): vscode.Location[] => { - const fileName = ctx.uriToFileName(document.uri); - const entries = safeCall(() => ctx.languageService.getFileReferences(fileName)); - if (!entries) return []; - - return entries.map(entry => convertDocumentSpanToLocation(entry, ctx)); - }; -} diff --git a/packages/typescript/lib/features/fileRename.ts b/packages/typescript/lib/features/fileRename.ts deleted file mode 100644 index 958d1741..00000000 --- a/packages/typescript/lib/features/fileRename.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type * as vscode from 'vscode-languageserver-protocol'; -import { getFormatCodeSettings } from '../configs/getFormatCodeSettings'; -import { getUserPreferences } from '../configs/getUserPreferences'; -import { safeCall } from '../shared'; -import type { SharedContext } from '../types'; -import { convertFileTextChanges } from '../utils/lspConverters'; - -export function register(ctx: SharedContext) { - return async (oldUri: string, newUri: string): Promise => { - - const document = ctx.getTextDocument(oldUri); - const [formatOptions, preferences] = await Promise.all([ - getFormatCodeSettings(ctx, document), - getUserPreferences(ctx, document), - ]); - - const fileToRename = ctx.uriToFileName(oldUri); - const newFilePath = ctx.uriToFileName(newUri); - const response = safeCall(() => ctx.languageService.getEditsForFileRename(fileToRename, newFilePath, formatOptions, preferences)); - if (!response?.length) return; - - const edits = convertFileTextChanges(response, ctx.fileNameToUri, ctx.getTextDocument); - return edits; - }; -} diff --git a/packages/typescript/lib/features/implementation.ts b/packages/typescript/lib/features/implementation.ts deleted file mode 100644 index 868e1367..00000000 --- a/packages/typescript/lib/features/implementation.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type * as vscode from 'vscode-languageserver-protocol'; -import { safeCall } from '../shared'; -import type { SharedContext } from '../types'; -import { convertDocumentSpantoLocationLink } from '../utils/lspConverters'; -import type { TextDocument } from 'vscode-languageserver-textdocument'; - -export function register(ctx: SharedContext) { - return (document: TextDocument, position: vscode.Position) => { - const fileName = ctx.uriToFileName(document.uri); - const offset = document.offsetAt(position); - const entries = safeCall(() => ctx.languageService.getImplementationAtPosition(fileName, offset)); - if (!entries) return []; - - return entries.map(entry => convertDocumentSpantoLocationLink(entry, ctx)); - }; -} diff --git a/packages/typescript/lib/features/inlayHints.ts b/packages/typescript/lib/features/inlayHints.ts deleted file mode 100644 index f723b117..00000000 --- a/packages/typescript/lib/features/inlayHints.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type * as vscode from '@volar/language-service'; -import { getUserPreferences } from '../configs/getUserPreferences'; -import { safeCall } from '../shared'; -import type { SharedContext } from '../types'; -import type { TextDocument } from 'vscode-languageserver-textdocument'; - -export function register(ctx: SharedContext) { - const { ts } = ctx; - return async (document: TextDocument, range: vscode.Range) => { - const preferences = await getUserPreferences(ctx, document); - const fileName = ctx.uriToFileName(document.uri); - const start = document.offsetAt(range.start); - const end = document.offsetAt(range.end); - const inlayHints = safeCall(() => - 'provideInlayHints' in ctx.languageService - ? ctx.languageService.provideInlayHints(fileName, { start, length: end - start }, preferences) - : [] - ) ?? []; - - return inlayHints.map(inlayHint => { - const result: vscode.InlayHint = { - position: document.positionAt(inlayHint.position), - label: inlayHint.text, - kind: inlayHint.kind === ts.InlayHintKind.Type ? 1 satisfies typeof vscode.InlayHintKind.Type - : inlayHint.kind === ts.InlayHintKind.Parameter ? 2 satisfies typeof vscode.InlayHintKind.Parameter - : undefined, - }; - result.paddingLeft = inlayHint.whitespaceBefore; - result.paddingRight = inlayHint.whitespaceAfter; - return result; - }); - }; -} diff --git a/packages/typescript/lib/features/references.ts b/packages/typescript/lib/features/references.ts deleted file mode 100644 index 5bb56d30..00000000 --- a/packages/typescript/lib/features/references.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type * as vscode from '@volar/language-service'; -import type { TextDocument } from 'vscode-languageserver-textdocument'; -import { safeCall } from '../shared'; -import type { SharedContext } from '../types'; -import { convertDocumentSpanToLocation } from '../utils/lspConverters'; - -export function register(ctx: SharedContext) { - return (document: TextDocument, position: vscode.Position, referenceContext: vscode.ReferenceContext): vscode.Location[] => { - const fileName = ctx.uriToFileName(document.uri); - const offset = document.offsetAt(position); - const references = safeCall(() => ctx.languageService.findReferences(fileName, offset)); - if (!references) return []; - - const result: vscode.Location[] = []; - for (const reference of references) { - if (referenceContext.includeDeclaration) { - const definition = convertDocumentSpanToLocation(reference.definition, ctx); - if (definition) { - result.push(definition); - } - } - for (const referenceEntry of reference.references) { - const reference = convertDocumentSpanToLocation(referenceEntry, ctx); - if (reference) { - result.push(reference); - } - } - } - return result; - }; -} diff --git a/packages/typescript/lib/features/selectionRanges.ts b/packages/typescript/lib/features/selectionRanges.ts deleted file mode 100644 index c46c113e..00000000 --- a/packages/typescript/lib/features/selectionRanges.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type * as vscode from '@volar/language-service'; -import type * as ts from 'typescript'; -import type { TextDocument } from 'vscode-languageserver-textdocument'; -import { safeCall } from '../shared'; -import type { SharedContext } from '../types'; - -export function register(ctx: SharedContext) { - return (document: TextDocument, positions: vscode.Position[]): vscode.SelectionRange[] => { - const result: vscode.SelectionRange[] = []; - - for (const position of positions) { - const fileName = ctx.uriToFileName(document.uri); - const offset = document.offsetAt(position); - const range = safeCall(() => ctx.languageService.getSmartSelectionRange(fileName, offset)); - if (!range) continue; - - result.push(transformSelectionRange(range, document)); - } - - return result; - }; -} - -function transformSelectionRange(range: ts.SelectionRange, document: TextDocument): vscode.SelectionRange { - return { - range: { - start: document.positionAt(range.textSpan.start), - end: document.positionAt(range.textSpan.start + range.textSpan.length), - }, - parent: range.parent ? transformSelectionRange(range.parent, document) : undefined, - }; -} diff --git a/packages/typescript/lib/features/signatureHelp.ts b/packages/typescript/lib/features/signatureHelp.ts deleted file mode 100644 index fd85eb37..00000000 --- a/packages/typescript/lib/features/signatureHelp.ts +++ /dev/null @@ -1,62 +0,0 @@ -import type * as vscode from '@volar/language-service'; -import type * as ts from 'typescript'; -import { safeCall } from '../shared'; -import type { SharedContext } from '../types'; -import type { TextDocument } from 'vscode-languageserver-textdocument'; - -export function register(ctx: SharedContext) { - const { ts } = ctx; - - return (document: TextDocument, position: vscode.Position, context?: vscode.SignatureHelpContext): vscode.SignatureHelp | undefined => { - const options: ts.SignatureHelpItemsOptions = {}; - if (context?.triggerKind === 1 satisfies typeof vscode.SignatureHelpTriggerKind.Invoked) { - options.triggerReason = { - kind: 'invoked' - }; - } - else if (context?.triggerKind === 2 satisfies typeof vscode.SignatureHelpTriggerKind.TriggerCharacter) { - options.triggerReason = { - kind: 'characterTyped', - triggerCharacter: context.triggerCharacter as ts.SignatureHelpTriggerCharacter, - }; - } - else if (context?.triggerKind === 3 satisfies typeof vscode.SignatureHelpTriggerKind.ContentChange) { - options.triggerReason = { - kind: 'retrigger', - triggerCharacter: context.triggerCharacter as ts.SignatureHelpRetriggerCharacter, - }; - } - - const fileName = ctx.uriToFileName(document.uri); - const offset = document.offsetAt(position); - const helpItems = safeCall(() => ctx.languageService.getSignatureHelpItems(fileName, offset, options)); - if (!helpItems) return; - - return { - activeSignature: helpItems.selectedItemIndex, - activeParameter: helpItems.argumentIndex, - signatures: helpItems.items.map(item => { - const signature: vscode.SignatureInformation = { - label: '', - documentation: undefined, - parameters: [] - }; - signature.label += ts.displayPartsToString(item.prefixDisplayParts); - item.parameters.forEach((p, i, a) => { - const label = ts.displayPartsToString(p.displayParts); - const parameter: vscode.ParameterInformation = { - label, - documentation: ts.displayPartsToString(p.documentation) - }; - signature.label += label; - signature.parameters!.push(parameter); - if (i < a.length - 1) { - signature.label += ts.displayPartsToString(item.separatorDisplayParts); - } - }); - signature.label += ts.displayPartsToString(item.suffixDisplayParts); - return signature; - }), - }; - }; -} diff --git a/packages/typescript/lib/features/typeDefinition.ts b/packages/typescript/lib/features/typeDefinition.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/typescript/lib/features/workspaceSymbol.ts b/packages/typescript/lib/features/workspaceSymbol.ts deleted file mode 100644 index 3e002a85..00000000 --- a/packages/typescript/lib/features/workspaceSymbol.ts +++ /dev/null @@ -1,64 +0,0 @@ -import type * as vscode from '@volar/language-service'; -import type * as ts from 'typescript'; -import * as PConst from '../protocol.const'; -import { safeCall } from '../shared'; -import type { SharedContext } from '../types'; -import { parseKindModifier } from '../utils/modifiers'; - -function getSymbolKind(item: ts.NavigateToItem): vscode.SymbolKind { - switch (item.kind) { - case PConst.Kind.method: return 6 satisfies typeof vscode.SymbolKind.Method; - case PConst.Kind.enum: return 10 satisfies typeof vscode.SymbolKind.Enum; - case PConst.Kind.enumMember: return 22 satisfies typeof vscode.SymbolKind.EnumMember; - case PConst.Kind.function: return 12 satisfies typeof vscode.SymbolKind.Function; - case PConst.Kind.class: return 5 satisfies typeof vscode.SymbolKind.Class; - case PConst.Kind.interface: return 11 satisfies typeof vscode.SymbolKind.Interface; - case PConst.Kind.type: return 5 satisfies typeof vscode.SymbolKind.Class; - case PConst.Kind.memberVariable: return 8 satisfies typeof vscode.SymbolKind.Field; - case PConst.Kind.memberGetAccessor: return 8 satisfies typeof vscode.SymbolKind.Field; - case PConst.Kind.memberSetAccessor: return 8 satisfies typeof vscode.SymbolKind.Field; - case PConst.Kind.variable: return 13 satisfies typeof vscode.SymbolKind.Variable; - default: return 13 satisfies typeof vscode.SymbolKind.Variable; - } -} - -export function register(ctx: SharedContext) { - return (query: string): vscode.WorkspaceSymbol[] => { - - const items = safeCall(() => ctx.languageService.getNavigateToItems(query)); - if (!items) return []; - - return items - .filter(item => item.containerName || item.kind !== 'alias') - .map(toWorkspaceSymbol) - .filter((v): v is NonNullable => !!v); - - function toWorkspaceSymbol(item: ts.NavigateToItem) { - const label = getLabel(item); - const uri = ctx.fileNameToUri(item.fileName); - const document = ctx.getTextDocument(uri); - const range: vscode.Range = { - start: document.positionAt(item.textSpan.start), - end: document.positionAt(item.textSpan.start + item.textSpan.length), - }; - const info: vscode.WorkspaceSymbol = { - name: label, - kind: getSymbolKind(item), - location: { uri, range }, - }; - const kindModifiers = item.kindModifiers ? parseKindModifier(item.kindModifiers) : undefined; - if (kindModifiers?.has(PConst.KindModifiers.deprecated)) { - info.tags = [1 satisfies typeof vscode.SymbolTag.Deprecated]; - } - return info; - } - - function getLabel(item: ts.NavigateToItem) { - const label = item.name; - if (item.kind === 'method' || item.kind === 'function') { - return label + '()'; - } - return label; - } - }; -} diff --git a/packages/typescript/lib/utils/lspConverters.ts b/packages/typescript/lib/utils/lspConverters.ts index 228a674f..3333af6c 100644 --- a/packages/typescript/lib/utils/lspConverters.ts +++ b/packages/typescript/lib/utils/lspConverters.ts @@ -9,6 +9,90 @@ import { parseKindModifier } from '../utils/modifiers'; import * as previewer from '../utils/previewer'; import * as typeConverters from '../utils/typeConverters'; +// workspaceSymbol + +export function convertNavigateToItem( + item: ts.NavigateToItem, + document: TextDocument, +) { + const info: vscode.WorkspaceSymbol = { + name: getLabel(item), + kind: convertScriptElementKind(item.kind), + location: { + uri: document.uri, + range: convertTextSpan(item.textSpan, document), + }, + }; + const kindModifiers = item.kindModifiers ? parseKindModifier(item.kindModifiers) : undefined; + if (kindModifiers?.has(PConst.KindModifiers.deprecated)) { + info.tags = [1 satisfies typeof vscode.SymbolTag.Deprecated]; + } + return info; +} + +function getLabel(item: ts.NavigateToItem) { + const label = item.name; + if (item.kind === 'method' || item.kind === 'function') { + return label + '()'; + } + return label; +} + +function convertScriptElementKind(kind: ts.ScriptElementKind): vscode.SymbolKind { + switch (kind) { + case PConst.Kind.method: return 6 satisfies typeof vscode.SymbolKind.Method; + case PConst.Kind.enum: return 10 satisfies typeof vscode.SymbolKind.Enum; + case PConst.Kind.enumMember: return 22 satisfies typeof vscode.SymbolKind.EnumMember; + case PConst.Kind.function: return 12 satisfies typeof vscode.SymbolKind.Function; + case PConst.Kind.class: return 5 satisfies typeof vscode.SymbolKind.Class; + case PConst.Kind.interface: return 11 satisfies typeof vscode.SymbolKind.Interface; + case PConst.Kind.type: return 5 satisfies typeof vscode.SymbolKind.Class; + case PConst.Kind.memberVariable: return 8 satisfies typeof vscode.SymbolKind.Field; + case PConst.Kind.memberGetAccessor: return 8 satisfies typeof vscode.SymbolKind.Field; + case PConst.Kind.memberSetAccessor: return 8 satisfies typeof vscode.SymbolKind.Field; + case PConst.Kind.variable: return 13 satisfies typeof vscode.SymbolKind.Variable; + default: return 13 satisfies typeof vscode.SymbolKind.Variable; + } +} + +// inlayHints + +export function convertInlayHint(hint: ts.InlayHint, document: TextDocument): vscode.InlayHint { + const result: vscode.InlayHint = { + position: document.positionAt(hint.position), + label: hint.text, + kind: hint.kind === 'Type' ? 1 satisfies typeof vscode.InlayHintKind.Type + : hint.kind === 'Parameter' ? 2 satisfies typeof vscode.InlayHintKind.Parameter + : undefined, + }; + result.paddingLeft = hint.whitespaceBefore; + result.paddingRight = hint.whitespaceAfter; + return result; +} + +// documentHighlight + +export function convertHighlightSpan(span: ts.HighlightSpan, document: TextDocument): vscode.DocumentHighlight { + return { + kind: span.kind === 'writtenReference' + ? 3 satisfies typeof vscode.DocumentHighlightKind.Write + : 2 satisfies typeof vscode.DocumentHighlightKind.Read, + range: convertTextSpan(span.textSpan, document), + }; +} + +// selectionRanges + +export function convertSelectionRange(range: ts.SelectionRange, document: TextDocument): vscode.SelectionRange { + return { + parent: range.parent + ? convertSelectionRange(range.parent, document) + : undefined, + range: convertTextSpan(range.textSpan, document), + }; +} + + // rename export function convertFileTextChanges( @@ -17,43 +101,25 @@ export function convertFileTextChanges( getTextDocument: (uri: string) => TextDocument, ) { const workspaceEdit: vscode.WorkspaceEdit = {}; - for (const change of changes) { - if (!workspaceEdit.documentChanges) { workspaceEdit.documentChanges = []; } - const uri = fileNameToUri(change.fileName); let doc = getTextDocument(uri); - if (change.isNewFile) { workspaceEdit.documentChanges.push({ kind: 'create', uri }); } - if (!change.isNewFile) continue; - - const docEdit: vscode.TextDocumentEdit = { + workspaceEdit.documentChanges.push({ textDocument: { uri, version: null, // fix https://github.com/johnsoncodehk/volar/issues/2025 }, - edits: [], - }; - - for (const textChange of change.textChanges) { - docEdit.edits.push({ - newText: textChange.newText, - range: { - start: doc.positionAt(textChange.span.start), - end: doc.positionAt(textChange.span.start + textChange.span.length), - }, - }); - } - workspaceEdit.documentChanges.push(docEdit); + edits: change.textChanges.map(edit => convertTextChange(edit, doc)), + }); } - return workspaceEdit; } @@ -66,35 +132,25 @@ export function convertRenameLocations( getTextDocument: (uri: string) => TextDocument, ) { const workspaceEdit: vscode.WorkspaceEdit = {}; - for (const location of locations) { - if (!workspaceEdit.changes) { workspaceEdit.changes = {}; } - const uri = fileNameToUri(location.fileName); const doc = getTextDocument(uri); - if (!workspaceEdit.changes[uri]) { workspaceEdit.changes[uri] = []; } - let _newText = newText; if (location.prefixText) _newText = location.prefixText + _newText; if (location.suffixText) _newText = _newText + location.suffixText; - workspaceEdit.changes[uri].push({ newText: _newText, - range: { - start: doc.positionAt(location.textSpan.start), - end: doc.positionAt(location.textSpan.start + location.textSpan.length), - }, + range: convertTextSpan(location.textSpan, doc), }); } - return workspaceEdit; } @@ -127,10 +183,7 @@ export function convertQuickInfo( }; return { contents: markdown, - range: { - start: document.positionAt(info.textSpan.start), - end: document.positionAt(info.textSpan.start + info.textSpan.length), - }, + range: convertTextSpan(info.textSpan, document), }; }