diff --git a/_extension/package.json b/_extension/package.json index 95ddbbf5b66..4b271436f62 100644 --- a/_extension/package.json +++ b/_extension/package.json @@ -12,8 +12,11 @@ "type": "git", "url": "https://github.com/microsoft/typescript-go" }, + "enabledApiProposals": [ + "multiDocumentHighlightProvider" + ], "engines": { - "vscode": "^1.106.0" + "vscode": "^1.110.0" }, "capabilities": { "untrustedWorkspaces": { @@ -194,10 +197,11 @@ }, "dependencies": { "@vscode/extension-telemetry": "^1.5.1", - "vscode-languageclient": "^10.0.0-next.21" + "vscode-languageclient": "10.0.0-next.21", + "vscode-languageserver-protocol": "3.17.6-next.17" }, "devDependencies": { - "@types/vscode": "~1.106.1", + "@types/vscode": "~1.110.0", "@vscode/vsce": "^3.7.1", "esbuild": "^0.27.4" } diff --git a/_extension/src/client.ts b/_extension/src/client.ts index 561143f372a..c8db81bc72f 100644 --- a/_extension/src/client.ts +++ b/_extension/src/client.ts @@ -20,6 +20,7 @@ import { configurationMiddleware, sendNotificationMiddleware, } from "./configurationMiddleware"; +import { registerMultiDocumentHighlightFeature } from "./languageFeatures/documentHighlight"; import { registerSourceDefinitionFeature } from "./languageFeatures/sourceDefinition"; import { registerTagClosingFeature } from "./languageFeatures/tagClosing"; import * as tr from "./telemetryReporting"; @@ -207,6 +208,7 @@ export class Client implements vscode.Disposable { this.disposables.push( serverTelemetryListener, + registerMultiDocumentHighlightFeature(this.documentSelector, this.client), registerSourceDefinitionFeature(this.client), registerTagClosingFeature("typescript", this.documentSelector, this.client), registerTagClosingFeature("javascript", this.documentSelector, this.client), diff --git a/_extension/src/languageFeatures/documentHighlight.ts b/_extension/src/languageFeatures/documentHighlight.ts new file mode 100644 index 00000000000..ba9c58f9fc6 --- /dev/null +++ b/_extension/src/languageFeatures/documentHighlight.ts @@ -0,0 +1,78 @@ +import * as vscode from "vscode"; +import { LanguageClient } from "vscode-languageclient/node"; +import type { DocumentHighlight } from "vscode-languageserver-protocol"; + +const multiDocumentHighlightMethod = "custom/textDocument/multiDocumentHighlight"; + +interface MultiDocumentHighlightParams { + textDocument: { uri: string; }; + position: { line: number; character: number; }; + filesToSearch: string[]; +} + +interface MultiDocumentHighlightItem { + uri: string; + highlights: DocumentHighlight[]; +} + +class MultiDocumentHighlightProvider implements vscode.MultiDocumentHighlightProvider { + constructor(private readonly client: LanguageClient) {} + + async provideMultiDocumentHighlights( + document: vscode.TextDocument, + position: vscode.Position, + otherDocuments: vscode.TextDocument[], + token: vscode.CancellationToken, + ): Promise { + const allFiles = [document, ...otherDocuments] + .map(doc => this.client.code2ProtocolConverter.asUri(doc.uri)) + .filter(file => !!file); + + if (allFiles.length === 0) { + return []; + } + + const params: MultiDocumentHighlightParams = { + textDocument: this.client.code2ProtocolConverter.asTextDocumentIdentifier(document), + position: this.client.code2ProtocolConverter.asPosition(position), + filesToSearch: allFiles, + }; + + let response: MultiDocumentHighlightItem[] | null; + try { + response = await this.client.sendRequest(multiDocumentHighlightMethod, params, token); + } + catch (error) { + return []; + } + + if (!response || token.isCancellationRequested) { + return []; + } + + // MultiDocumentHighlight is proposed API; guard against missing or changed constructor. + try { + return response.map(item => + new vscode.MultiDocumentHighlight( + vscode.Uri.parse(item.uri), + item.highlights.map(h => this.client.protocol2CodeConverter.asDocumentHighlight(h)), + ) + ); + } + catch { + return []; + } + } +} + +export function registerMultiDocumentHighlightFeature( + selector: vscode.DocumentSelector, + client: LanguageClient, +): vscode.Disposable { + const capabilities = client.initializeResult?.capabilities as { customMultiDocumentHighlightProvider?: boolean; } | undefined; + // registerMultiDocumentHighlightProvider is proposed API; guard against it not being available. + if (!capabilities?.customMultiDocumentHighlightProvider || typeof vscode.languages.registerMultiDocumentHighlightProvider !== "function") { + return { dispose() {} }; + } + return vscode.languages.registerMultiDocumentHighlightProvider(selector, new MultiDocumentHighlightProvider(client)); +} diff --git a/_extension/src/vscode.proposed.multiDocumentHighlightProvider.d.ts b/_extension/src/vscode.proposed.multiDocumentHighlightProvider.d.ts new file mode 100644 index 00000000000..1a95e49b4a9 --- /dev/null +++ b/_extension/src/vscode.proposed.multiDocumentHighlightProvider.d.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module "vscode" { + /** + * Represents a collection of document highlights from multiple documents. + */ + export class MultiDocumentHighlight { + /** + * The URI of the document containing the highlights. + */ + uri: Uri; + + /** + * The highlights for the document. + */ + highlights: DocumentHighlight[]; + + /** + * Creates a new instance of MultiDocumentHighlight. + * @param uri The URI of the document containing the highlights. + * @param highlights The highlights for the document. + */ + constructor(uri: Uri, highlights: DocumentHighlight[]); + } + + export interface MultiDocumentHighlightProvider { + /** + * Provide a set of document highlights, like all occurrences of a variable or + * all exit-points of a function. + * + * @param document The document in which the command was invoked. + * @param position The position at which the command was invoked. + * @param otherDocuments An array of additional valid documents for which highlights should be provided. + * @param token A cancellation token. + * @returns A Map containing a mapping of the Uri of a document to the document highlights or a thenable that resolves to such. The lack of a result can be + * signaled by returning `undefined`, `null`, or an empty map. + */ + provideMultiDocumentHighlights(document: TextDocument, position: Position, otherDocuments: TextDocument[], token: CancellationToken): ProviderResult; + } + + namespace languages { + /** + * Register a multi document highlight provider. + * + * Multiple providers can be registered for a language. In that case providers are sorted + * by their {@link languages.match score} and groups sequentially asked for document highlights. + * The process stops when a provider returns a `non-falsy` or `non-failure` result. + * + * @param selector A selector that defines the documents this provider is applicable to. + * @param provider A multi-document highlight provider. + * @returns A {@link Disposable} that unregisters this provider when being disposed. + */ + export function registerMultiDocumentHighlightProvider(selector: DocumentSelector, provider: MultiDocumentHighlightProvider): Disposable; + } +} diff --git a/internal/fourslash/_scripts/convertFourslash.mts b/internal/fourslash/_scripts/convertFourslash.mts index b8e399f57f9..02ed28f703b 100755 --- a/internal/fourslash/_scripts/convertFourslash.mts +++ b/internal/fourslash/_scripts/convertFourslash.mts @@ -1262,6 +1262,7 @@ function parseBaselineFindAllReferencesArgs(args: readonly ts.Expression[]): [Ve function parseBaselineDocumentHighlightsArgs(args: readonly ts.Expression[]): [VerifyBaselineDocumentHighlightsCmd] { const newArgs: string[] = []; let preferences: string | undefined; + let filesToSearch: string[] | undefined; for (const arg of args) { let strArg; if (strArg = getArrayLiteralExpression(arg)) { @@ -1270,8 +1271,47 @@ function parseBaselineDocumentHighlightsArgs(args: readonly ts.Expression[]): [V newArgs.push(newArg); } } + else if (ts.isCallExpression(arg) && arg.getText().includes("test.ranges()")) { + newArgs.push("ToAny(f.Ranges())..."); + } else if (ts.isObjectLiteralExpression(arg)) { - // !!! todo when multiple files supported in lsp + for (const prop of arg.properties) { + if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === "filesToSearch" && ts.isArrayLiteralExpression(prop.initializer)) { + filesToSearch = []; + for (const e of prop.initializer.elements) { + if (ts.isStringLiteral(e)) { + filesToSearch.push(JSON.stringify(e.text)); + } + else if (ts.isPropertyAccessExpression(e) && e.name.text === "fileName") { + // e.g. test.ranges()[0].fileName -> f.Ranges()[0].FileName() + const obj = e.expression; + if (ts.isElementAccessExpression(obj) && ts.isCallExpression(obj.expression) && obj.expression.getText().includes("ranges")) { + const index = obj.argumentExpression?.getText(); + if (index !== undefined) { + filesToSearch.push(`f.Ranges()[${index}].FileName()`); + continue; + } + } + // e.g. range.fileName where `const range = test.ranges()[0]` + if (ts.isIdentifier(obj)) { + const resolved = parseRangeVariable(obj); + if (resolved) { + filesToSearch.push(`${resolved}.FileName()`); + continue; + } + } + // Fallback: skip filesToSearch entirely + filesToSearch = undefined; + break; + } + else { + // Unsupported expression; skip filesToSearch + filesToSearch = undefined; + break; + } + } + } + } } else { newArgs.push(parseBaselineMarkerOrRangeArg(arg)); @@ -1286,6 +1326,7 @@ function parseBaselineDocumentHighlightsArgs(args: readonly ts.Expression[]): [V kind: "verifyBaselineDocumentHighlights", args: newArgs, preferences: preferences ? preferences : "nil /*preferences*/", + filesToSearch, }]; } @@ -1841,6 +1882,10 @@ function parseRangeVariable(arg: ts.Identifier | ts.ElementAccessExpression): st if (ts.isElementAccessExpression(arg)) { return `f.Ranges()[${arg.argumentExpression!.getText()}]`; } + // `const range = test.ranges()[0]` used directly as `range` + if (ts.isIdentifier(arg) && ts.isElementAccessExpression(decl.initializer) && ts.isCallExpression(decl.initializer.expression) && decl.initializer.argumentExpression) { + return `f.Ranges()[${decl.initializer.argumentExpression.getText()}]`; + } } // `const cRanges = ranges.get("C")` or `const cRanges = test.rangesByText().get("C")` if (ts.isIdentifier(decl.name) && decl.name.text === argName && decl.initializer && ts.isCallExpression(decl.initializer)) { @@ -3022,6 +3067,7 @@ interface VerifyBaselineDocumentHighlightsCmd { kind: "verifyBaselineDocumentHighlights"; args: string[]; preferences: string; + filesToSearch?: string[]; } interface VerifyBaselineInlayHintsCmd { @@ -3292,7 +3338,11 @@ function generateBaselineFindAllReferences({ markers, ranges }: VerifyBaselineFi return `f.VerifyBaselineFindAllReferences(t, ${markers.join(", ")})`; } -function generateBaselineDocumentHighlights({ args, preferences }: VerifyBaselineDocumentHighlightsCmd): string { +function generateBaselineDocumentHighlights({ args, preferences, filesToSearch }: VerifyBaselineDocumentHighlightsCmd): string { + if (filesToSearch) { + const filesGo = `[]string{${filesToSearch.join(", ")}}`; + return `f.VerifyBaselineDocumentHighlightsWithOptions(t, ${preferences}, ${filesGo}, ${args.join(", ")})`; + } return `f.VerifyBaselineDocumentHighlights(t, ${preferences}, ${args.join(", ")})`; } diff --git a/internal/fourslash/_scripts/unparsedTests.txt b/internal/fourslash/_scripts/unparsedTests.txt index 5213974a4f6..69d8ce7c67a 100644 --- a/internal/fourslash/_scripts/unparsedTests.txt +++ b/internal/fourslash/_scripts/unparsedTests.txt @@ -1460,10 +1460,6 @@ docCommentTemplateWithMultipleJSDoc1.ts parse error: "Unrecognized fourslash sta docCommentTemplateWithMultipleJSDoc2.ts parse error: "Unrecognized fourslash statement: verify.docCommentTemplateAt(...)" docCommentTemplateWithMultipleJSDoc3.ts parse error: "Unrecognized fourslash statement: verify.docCommentTemplateAt(...)" docCommentTemplateWithMultipleJSDocAndParameters.ts parse error: "Unrecognized fourslash statement: verify.docCommentTemplateAt(...)" -documentHighlights_moduleImport_filesToSearch.ts parse error: "Unrecognized marker or range argument: test.ranges()" -documentHighlights_moduleImport_filesToSearchWithInvalidFile.ts parse error: "Unrecognized marker or range argument: test.ranges()" -documentHighlights_windowsPath.ts parse error: "Unrecognized marker or range argument: range" -documentHighlights02.ts parse error: "Unrecognized marker or range argument: test.ranges()" duplicateClassModuleError0.ts parse error: "Unrecognized edit function: disableFormatting" editClearsJsDocCache.ts parse error: "Unrecognized edit function: replace" eval.ts parse error: "Unrecognized fourslash statement: verify.eval(...)" diff --git a/internal/fourslash/fourslash.go b/internal/fourslash/fourslash.go index 89402a4d01a..4dca1bc2715 100644 --- a/internal/fourslash/fourslash.go +++ b/internal/fourslash/fourslash.go @@ -2695,6 +2695,15 @@ func (f *FourslashTest) VerifyBaselineDocumentHighlights( t *testing.T, preferences *lsutil.UserPreferences, markerOrRangeOrNames ...MarkerOrRangeOrName, +) { + f.VerifyBaselineDocumentHighlightsWithOptions(t, preferences, nil /*filesToSearch*/, markerOrRangeOrNames...) +} + +func (f *FourslashTest) VerifyBaselineDocumentHighlightsWithOptions( + t *testing.T, + preferences *lsutil.UserPreferences, + filesToSearch []string, + markerOrRangeOrNames ...MarkerOrRangeOrName, ) { var markerOrRanges []MarkerOrRange for _, markerOrRangeOrName := range markerOrRangeOrNames { @@ -2714,39 +2723,81 @@ func (f *FourslashTest) VerifyBaselineDocumentHighlights( } } - f.verifyBaselineDocumentHighlights(t, preferences, markerOrRanges) + f.verifyBaselineDocumentHighlights(t, preferences, filesToSearch, markerOrRanges) } func (f *FourslashTest) verifyBaselineDocumentHighlights( t *testing.T, preferences *lsutil.UserPreferences, + filesToSearch []string, markerOrRanges []MarkerOrRange, ) { for _, markerOrRange := range markerOrRanges { f.goToMarker(t, markerOrRange) - params := &lsproto.DocumentHighlightParams{ - TextDocument: lsproto.TextDocumentIdentifier{ - Uri: lsconv.FileNameToDocumentURI(f.activeFilename), - }, - Position: f.currentCaretPosition, - } - result := sendRequest(t, f, lsproto.TextDocumentDocumentHighlightInfo, params) - highlights := result.DocumentHighlights - if highlights == nil { - highlights = &[]*lsproto.DocumentHighlight{} - } - var spans []lsproto.Location - for _, h := range *highlights { - spans = append(spans, lsproto.Location{ - Uri: lsconv.FileNameToDocumentURI(f.activeFilename), - Range: h.Range, - }) + var header string + + if len(filesToSearch) > 0 { + // Multi-file: use the custom method. + var searchURIs []lsproto.DocumentUri + for _, file := range filesToSearch { + searchURIs = append(searchURIs, lsconv.FileNameToDocumentURI(file)) + } + + params := &lsproto.MultiDocumentHighlightParams{ + TextDocument: lsproto.TextDocumentIdentifier{ + Uri: lsconv.FileNameToDocumentURI(f.activeFilename), + }, + Position: f.currentCaretPosition, + FilesToSearch: searchURIs, + } + result := sendRequest(t, f, lsproto.CustomTextDocumentMultiDocumentHighlightInfo, params) + multiHighlights := result.MultiDocumentHighlights + if multiHighlights == nil { + multiHighlights = &[]*lsproto.MultiDocumentHighlight{} + } + + for _, mh := range *multiHighlights { + for _, h := range mh.Highlights { + spans = append(spans, lsproto.Location{ + Uri: mh.Uri, + Range: h.Range, + }) + } + } + + var sb strings.Builder + sb.WriteString("// filesToSearch:\n") + for _, file := range filesToSearch { + fmt.Fprintf(&sb, "// %s\n", file) + } + sb.WriteString("\n") + header = sb.String() + } else { + // Single-file: use the standard LSP method. + params := &lsproto.DocumentHighlightParams{ + TextDocument: lsproto.TextDocumentIdentifier{ + Uri: lsconv.FileNameToDocumentURI(f.activeFilename), + }, + Position: f.currentCaretPosition, + } + result := sendRequest(t, f, lsproto.TextDocumentDocumentHighlightInfo, params) + highlights := result.DocumentHighlights + if highlights == nil { + highlights = &[]*lsproto.DocumentHighlight{} + } + + for _, h := range *highlights { + spans = append(spans, lsproto.Location{ + Uri: lsconv.FileNameToDocumentURI(f.activeFilename), + Range: h.Range, + }) + } } // Add result to baseline - f.addResultToBaseline(t, documentHighlightsCmd, f.getBaselineForLocationsWithFileContents(spans, baselineFourslashLocationsOptions{ + f.addResultToBaseline(t, documentHighlightsCmd, header+f.getBaselineForLocationsWithFileContents(spans, baselineFourslashLocationsOptions{ marker: markerOrRange, markerName: "/*HIGHLIGHTS*/", })) diff --git a/internal/fourslash/tests/gen/documentHighlights02_test.go b/internal/fourslash/tests/gen/documentHighlights02_test.go new file mode 100644 index 00000000000..460c753a5f7 --- /dev/null +++ b/internal/fourslash/tests/gen/documentHighlights02_test.go @@ -0,0 +1,33 @@ +// Code generated by convertFourslash; DO NOT EDIT. +// To modify this test, run "npm run makemanual documentHighlights02" + +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + . "github.com/microsoft/typescript-go/internal/fourslash/tests/util" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestDocumentHighlights02(t *testing.T) { + fourslash.SkipIfFailing(t) + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @lib: es5 +// @Filename: a.ts +function [|foo|] () { + return 1; +} +[|foo|](); +// @Filename: b.ts +/// +[|foo|]();` + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + f.MarkTestAsStradaServer() + f.GoToFile(t, "a.ts") + f.GoToFile(t, "b.ts") + f.VerifyBaselineDocumentHighlightsWithOptions(t, nil /*preferences*/, []string{"a.ts", "b.ts"}, ToAny(f.Ranges())...) +} diff --git a/internal/fourslash/tests/gen/documentHighlights_33722_test.go b/internal/fourslash/tests/gen/documentHighlights_33722_test.go index cec87f5e4f6..bb5fa4220cd 100644 --- a/internal/fourslash/tests/gen/documentHighlights_33722_test.go +++ b/internal/fourslash/tests/gen/documentHighlights_33722_test.go @@ -27,5 +27,5 @@ import y from "./y"; y().[|foo|]();` f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) defer done() - f.VerifyBaselineDocumentHighlights(t, nil /*preferences*/, f.Ranges()[0]) + f.VerifyBaselineDocumentHighlightsWithOptions(t, nil /*preferences*/, []string{"/x.ts"}, f.Ranges()[0]) } diff --git a/internal/fourslash/tests/gen/documentHighlights_moduleImport_filesToSearchWithInvalidFile_test.go b/internal/fourslash/tests/gen/documentHighlights_moduleImport_filesToSearchWithInvalidFile_test.go new file mode 100644 index 00000000000..0f555dce6e8 --- /dev/null +++ b/internal/fourslash/tests/gen/documentHighlights_moduleImport_filesToSearchWithInvalidFile_test.go @@ -0,0 +1,30 @@ +// Code generated by convertFourslash; DO NOT EDIT. +// To modify this test, run "npm run makemanual documentHighlights_moduleImport_filesToSearchWithInvalidFile" + +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + . "github.com/microsoft/typescript-go/internal/fourslash/tests/util" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestDocumentHighlights_moduleImport_filesToSearchWithInvalidFile(t *testing.T) { + fourslash.SkipIfFailing(t) + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /node_modules/@types/foo/index.d.ts +export const x: number; +// @Filename: /a.ts +import * as foo from "foo"; +foo.[|x|]; +// @Filename: /b.ts +import { [|x|] } from "foo"; +// @Filename: /c.ts +import { x } from "foo";` + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + f.VerifyBaselineDocumentHighlightsWithOptions(t, nil /*preferences*/, []string{"/a.ts", "/b.ts", "/unknown.ts"}, ToAny(f.Ranges())...) +} diff --git a/internal/fourslash/tests/gen/documentHighlights_moduleImport_filesToSearch_test.go b/internal/fourslash/tests/gen/documentHighlights_moduleImport_filesToSearch_test.go new file mode 100644 index 00000000000..53fac38c8b5 --- /dev/null +++ b/internal/fourslash/tests/gen/documentHighlights_moduleImport_filesToSearch_test.go @@ -0,0 +1,30 @@ +// Code generated by convertFourslash; DO NOT EDIT. +// To modify this test, run "npm run makemanual documentHighlights_moduleImport_filesToSearch" + +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + . "github.com/microsoft/typescript-go/internal/fourslash/tests/util" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestDocumentHighlights_moduleImport_filesToSearch(t *testing.T) { + fourslash.SkipIfFailing(t) + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @Filename: /node_modules/@types/foo/index.d.ts +export const x: number; +// @Filename: /a.ts +import * as foo from "foo"; +foo.[|x|]; +// @Filename: /b.ts +import { [|x|] } from "foo"; +// @Filename: /c.ts +import { x } from "foo";` + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + f.VerifyBaselineDocumentHighlightsWithOptions(t, nil /*preferences*/, []string{"/a.ts", "/b.ts"}, ToAny(f.Ranges())...) +} diff --git a/internal/fourslash/tests/gen/documentHighlights_windowsPath_test.go b/internal/fourslash/tests/gen/documentHighlights_windowsPath_test.go new file mode 100644 index 00000000000..f8b1cc793a7 --- /dev/null +++ b/internal/fourslash/tests/gen/documentHighlights_windowsPath_test.go @@ -0,0 +1,22 @@ +// Code generated by convertFourslash; DO NOT EDIT. +// To modify this test, run "npm run makemanual documentHighlights_windowsPath" + +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestDocumentHighlights_windowsPath(t *testing.T) { + fourslash.SkipIfFailing(t) + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `//@Filename: C:\a\b\c.ts +var /*1*/[|x|] = 1;` + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + f.VerifyBaselineDocumentHighlightsWithOptions(t, nil /*preferences*/, []string{f.Ranges()[0].FileName()}, f.Ranges()[0]) +} diff --git a/internal/fourslash/tests/gen/exportInLabeledStatement_test.go b/internal/fourslash/tests/gen/exportInLabeledStatement_test.go index c2cbed5afc9..da57a252984 100644 --- a/internal/fourslash/tests/gen/exportInLabeledStatement_test.go +++ b/internal/fourslash/tests/gen/exportInLabeledStatement_test.go @@ -19,5 +19,5 @@ subTitle: [|export|] const title: string` f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) defer done() - f.VerifyBaselineDocumentHighlights(t, nil /*preferences*/, f.Ranges()[0]) + f.VerifyBaselineDocumentHighlightsWithOptions(t, nil /*preferences*/, []string{f.Ranges()[0].FileName()}, f.Ranges()[0]) } diff --git a/internal/fourslash/tests/gen/exportInObjectLiteral_test.go b/internal/fourslash/tests/gen/exportInObjectLiteral_test.go index 320b51a8776..e77ed18c737 100644 --- a/internal/fourslash/tests/gen/exportInObjectLiteral_test.go +++ b/internal/fourslash/tests/gen/exportInObjectLiteral_test.go @@ -20,5 +20,5 @@ const k = { }` f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) defer done() - f.VerifyBaselineDocumentHighlights(t, nil /*preferences*/, f.Ranges()[0]) + f.VerifyBaselineDocumentHighlightsWithOptions(t, nil /*preferences*/, []string{f.Ranges()[0].FileName()}, f.Ranges()[0]) } diff --git a/internal/fourslash/tests/gen/findAllRefsForModule_test.go b/internal/fourslash/tests/gen/findAllRefsForModule_test.go index ee2e9fcab3d..1df0989f145 100644 --- a/internal/fourslash/tests/gen/findAllRefsForModule_test.go +++ b/internal/fourslash/tests/gen/findAllRefsForModule_test.go @@ -26,5 +26,5 @@ export const x = 0; f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) defer done() f.VerifyBaselineFindAllReferences(t, "0", "1", "2") - f.VerifyBaselineDocumentHighlights(t, nil /*preferences*/, f.Ranges()[1], f.Ranges()[3], f.Ranges()[4]) + f.VerifyBaselineDocumentHighlightsWithOptions(t, nil /*preferences*/, []string{"/b.ts", "/c/sub.js", "/d.ts"}, f.Ranges()[1], f.Ranges()[3], f.Ranges()[4]) } diff --git a/internal/ls/documenthighlights.go b/internal/ls/documenthighlights.go index 9d0ac40558c..7494ee707ea 100644 --- a/internal/ls/documenthighlights.go +++ b/internal/ls/documenthighlights.go @@ -5,7 +5,9 @@ import ( "github.com/microsoft/typescript-go/internal/ast" "github.com/microsoft/typescript-go/internal/astnav" + "github.com/microsoft/typescript-go/internal/collections" "github.com/microsoft/typescript-go/internal/compiler" + "github.com/microsoft/typescript-go/internal/ls/lsconv" "github.com/microsoft/typescript-go/internal/ls/lsutil" "github.com/microsoft/typescript-go/internal/scanner" "github.com/microsoft/typescript-go/internal/stringutil" @@ -14,58 +16,115 @@ import ( ) func (l *LanguageService) ProvideDocumentHighlights(ctx context.Context, documentUri lsproto.DocumentUri, documentPosition lsproto.Position) (lsproto.DocumentHighlightResponse, error) { + result, err := l.provideDocumentHighlightsWorker(ctx, documentUri, documentPosition, nil) + if err != nil { + return lsproto.DocumentHighlightsOrNull{}, err + } + // Extract highlights for the current file only. + var documentHighlights []*lsproto.DocumentHighlight + if result.MultiDocumentHighlights != nil { + for _, mh := range *result.MultiDocumentHighlights { + if mh.Uri == documentUri { + documentHighlights = append(documentHighlights, mh.Highlights...) + } + } + } + return lsproto.DocumentHighlightsOrNull{DocumentHighlights: &documentHighlights}, nil +} + +func (l *LanguageService) ProvideMultiDocumentHighlights(ctx context.Context, documentUri lsproto.DocumentUri, documentPosition lsproto.Position, filesToSearch []lsproto.DocumentUri) (lsproto.CustomMultiDocumentHighlightResponse, error) { + return l.provideDocumentHighlightsWorker(ctx, documentUri, documentPosition, filesToSearch) +} + +func (l *LanguageService) provideDocumentHighlightsWorker(ctx context.Context, documentUri lsproto.DocumentUri, documentPosition lsproto.Position, filesToSearch []lsproto.DocumentUri) (lsproto.MultiDocumentHighlightsOrNull, error) { program, sourceFile := l.getProgramAndFile(documentUri) position := int(l.converters.LineAndCharacterToPosition(sourceFile, documentPosition)) node := astnav.GetTouchingPropertyName(sourceFile, position) + + // Cheap JSX check before resolving files to search. if node.Parent != nil && (node.Parent.Kind == ast.KindJsxClosingElement || (node.Parent.Kind == ast.KindJsxOpeningElement && node.Parent.TagName() == node)) { var openingElement, closingElement *ast.Node if ast.IsJsxElement(node.Parent.Parent) { openingElement = node.Parent.Parent.AsJsxElement().OpeningElement closingElement = node.Parent.Parent.AsJsxElement().ClosingElement } - var documentHighlights []*lsproto.DocumentHighlight + var highlights []*lsproto.DocumentHighlight kind := lsproto.DocumentHighlightKindRead if openingElement != nil { - documentHighlights = append(documentHighlights, &lsproto.DocumentHighlight{ + highlights = append(highlights, &lsproto.DocumentHighlight{ Range: l.createLspRangeFromNode(openingElement, sourceFile), Kind: &kind, }) } if closingElement != nil { - documentHighlights = append(documentHighlights, &lsproto.DocumentHighlight{ + highlights = append(highlights, &lsproto.DocumentHighlight{ Range: l.createLspRangeFromNode(closingElement, sourceFile), Kind: &kind, }) } - return lsproto.DocumentHighlightsOrNull{ - DocumentHighlights: &documentHighlights, + multiHighlights := []*lsproto.MultiDocumentHighlight{ + {Uri: documentUri, Highlights: highlights}, + } + return lsproto.MultiDocumentHighlightsOrNull{ + MultiDocumentHighlights: &multiHighlights, }, nil } - documentHighlights := l.getSemanticDocumentHighlights(ctx, position, node, program, sourceFile) - if len(documentHighlights) == 0 { - documentHighlights = l.getSyntacticDocumentHighlights(node, sourceFile) + + // Resolve the source files to search, deduplicating by file name. + var sourceFiles []*ast.SourceFile + seenFiles := collections.NewSetWithSizeHint[string](len(filesToSearch)) + for _, uri := range filesToSearch { + fileName := uri.FileName() + if !seenFiles.AddIfAbsent(fileName) { + continue + } + if sf := program.GetSourceFile(fileName); sf != nil { + sourceFiles = append(sourceFiles, sf) + } } - // if nil is passed here we never generate an error, just pass an empty highlight - return lsproto.DocumentHighlightsOrNull{DocumentHighlights: &documentHighlights}, nil + if len(sourceFiles) == 0 { + sourceFiles = []*ast.SourceFile{sourceFile} + } + + multiHighlights := l.getSemanticDocumentHighlights(ctx, position, node, program, sourceFiles) + if len(multiHighlights) == 0 { + // Fall back to syntactic highlights for the current file only. + syntacticHighlights := l.getSyntacticDocumentHighlights(node, sourceFile) + if len(syntacticHighlights) > 0 { + multiHighlights = []*lsproto.MultiDocumentHighlight{ + {Uri: documentUri, Highlights: syntacticHighlights}, + } + } + } + return lsproto.MultiDocumentHighlightsOrNull{MultiDocumentHighlights: &multiHighlights}, nil } -func (l *LanguageService) getSemanticDocumentHighlights(ctx context.Context, position int, node *ast.Node, program *compiler.Program, sourceFile *ast.SourceFile) []*lsproto.DocumentHighlight { +func (l *LanguageService) getSemanticDocumentHighlights(ctx context.Context, position int, node *ast.Node, program *compiler.Program, sourceFiles []*ast.SourceFile) []*lsproto.MultiDocumentHighlight { options := refOptions{use: referenceUseNone} - referenceEntries := l.getReferencedSymbolsForNode(ctx, position, node, program, []*ast.SourceFile{sourceFile}, options) + referenceEntries := l.getReferencedSymbolsForNode(ctx, position, node, program, sourceFiles, options) if referenceEntries == nil { return nil } - var highlights []*lsproto.DocumentHighlight + // Group highlights by file + fileHighlights := make(map[string][]*lsproto.DocumentHighlight) for _, entry := range referenceEntries { for _, ref := range entry.references { fileName, highlight := l.toDocumentHighlight(ref) - if fileName == sourceFile.FileName() { - highlights = append(highlights, highlight) - } + fileHighlights[fileName] = append(fileHighlights[fileName], highlight) } } - return highlights + + var result []*lsproto.MultiDocumentHighlight + for _, sf := range sourceFiles { + if highlights, ok := fileHighlights[sf.FileName()]; ok { + result = append(result, &lsproto.MultiDocumentHighlight{ + Uri: lsconv.FileNameToDocumentURI(sf.FileName()), + Highlights: highlights, + }) + } + } + return result } func (l *LanguageService) toDocumentHighlight(entry *ReferenceEntry) (string, *lsproto.DocumentHighlight) { diff --git a/internal/lsp/lsproto/_generate/generate.mts b/internal/lsp/lsproto/_generate/generate.mts index 7fd0a816685..62576de82a5 100755 --- a/internal/lsp/lsproto/_generate/generate.mts +++ b/internal/lsp/lsproto/_generate/generate.mts @@ -391,6 +391,43 @@ const customStructures: Structure[] = [ ], documentation: "Numeric measurements for ProjectInfoTelemetryEvent.", }, + { + name: "MultiDocumentHighlight", + properties: [ + { + name: "uri", + type: { kind: "base", name: "DocumentUri" }, + documentation: "The URI of the document containing the highlights.", + }, + { + name: "highlights", + type: { kind: "array", element: { kind: "reference", name: "DocumentHighlight" } }, + documentation: "The highlights for the document.", + }, + ], + documentation: "Represents a collection of document highlights from a single document, used in multi-document highlight responses.", + }, + { + name: "MultiDocumentHighlightParams", + properties: [ + { + name: "textDocument", + type: { kind: "reference", name: "TextDocumentIdentifier" }, + documentation: "The text document.", + }, + { + name: "position", + type: { kind: "reference", name: "Position" }, + documentation: "The position inside the text document.", + }, + { + name: "filesToSearch", + type: { kind: "array", element: { kind: "base", name: "DocumentUri" } }, + documentation: "The list of file URIs to search for highlights across.", + }, + ], + documentation: "Parameters for the custom/textDocument/multiDocumentHighlight request.", + }, ]; const customEnumerations: Enumeration[] = [ @@ -521,6 +558,20 @@ const customRequests: Request[] = [ messageDirection: "clientToServer", documentation: "Request to get source definitions for a position.", }, + { + method: "custom/textDocument/multiDocumentHighlight", + typeName: "CustomMultiDocumentHighlightRequest", + params: { kind: "reference", name: "MultiDocumentHighlightParams" }, + result: { + kind: "or", + items: [ + { kind: "array", element: { kind: "reference", name: "MultiDocumentHighlight" } }, + { kind: "base", name: "null" }, + ], + }, + messageDirection: "clientToServer", + documentation: "Request to get document highlights across multiple files.", + }, ]; const customTypeAliases: TypeAlias[] = [ @@ -617,6 +668,16 @@ function patchAndPreprocessModel() { }); } + // Patch ServerCapabilities to add custom tsgo capability flags + if (structure.name === "ServerCapabilities") { + structure.properties.push({ + name: "customMultiDocumentHighlightProvider", + type: { kind: "base", name: "boolean" }, + optional: true, + documentation: "The server provides multi-document highlight support via custom/textDocument/multiDocumentHighlight.", + }); + } + for (const prop of structure.properties) { // Replace initializationOptions type with custom InitializationOptions if (prop.name === "initializationOptions" && prop.type.kind === "reference" && prop.type.name === "LSPAny") { diff --git a/internal/lsp/lsproto/lsp_generated.go b/internal/lsp/lsproto/lsp_generated.go index e87568aa258..6c1294f0e2a 100644 --- a/internal/lsp/lsproto/lsp_generated.go +++ b/internal/lsp/lsproto/lsp_generated.go @@ -17143,6 +17143,9 @@ type ServerCapabilities struct { // The server provides source definition support via custom/textDocument/sourceDefinition. CustomSourceDefinitionProvider *bool `json:"customSourceDefinitionProvider,omitzero"` + + // The server provides multi-document highlight support via custom/textDocument/multiDocumentHighlight. + CustomMultiDocumentHighlightProvider *bool `json:"customMultiDocumentHighlightProvider,omitzero"` } var _ json.UnmarshalerFrom = (*ServerCapabilities)(nil) @@ -17406,6 +17409,13 @@ func (s *ServerCapabilities) UnmarshalJSONFrom(dec *json.Decoder) error { if err := json.UnmarshalDecode(dec, &s.CustomSourceDefinitionProvider); err != nil { return err } + case `"customMultiDocumentHighlightProvider"`: + if dec.PeekKind() == 'n' { + return errNull("customMultiDocumentHighlightProvider") + } + if err := json.UnmarshalDecode(dec, &s.CustomMultiDocumentHighlightProvider); err != nil { + return err + } default: if err := dec.SkipValue(); err != nil { return err @@ -29134,6 +29144,166 @@ type ProjectInfoTelemetryMeasurements struct { DtsFileSize float64 `json:"dtsFileSize,omitzero"` } +// Represents a collection of document highlights from a single document, used in multi-document highlight responses. +type MultiDocumentHighlight struct { + // The URI of the document containing the highlights. + Uri DocumentUri `json:"uri"` + + // The highlights for the document. + Highlights []*DocumentHighlight `json:"highlights"` +} + +var _ json.UnmarshalerFrom = (*MultiDocumentHighlight)(nil) + +func (s *MultiDocumentHighlight) UnmarshalJSONFrom(dec *json.Decoder) error { + const ( + missingUri uint = 1 << iota + missingHighlights + _missingLast + ) + missing := _missingLast - 1 + + if k := dec.PeekKind(); k != '{' { + return errNotObject(k) + } + if _, err := dec.ReadToken(); err != nil { + return err + } + + for dec.PeekKind() != '}' { + name, err := dec.ReadValue() + if err != nil { + return err + } + switch string(name) { + case `"uri"`: + missing &^= missingUri + if err := json.UnmarshalDecode(dec, &s.Uri); err != nil { + return err + } + case `"highlights"`: + missing &^= missingHighlights + if dec.PeekKind() == 'n' { + return errNull("highlights") + } + if err := json.UnmarshalDecode(dec, &s.Highlights); err != nil { + return err + } + default: + if err := dec.SkipValue(); err != nil { + return err + } + } + } + + if _, err := dec.ReadToken(); err != nil { + return err + } + + if missing != 0 { + var missingProps []string + if missing&missingUri != 0 { + missingProps = append(missingProps, "uri") + } + if missing&missingHighlights != 0 { + missingProps = append(missingProps, "highlights") + } + return errMissing(missingProps) + } + + return nil +} + +// Parameters for the custom/textDocument/multiDocumentHighlight request. +type MultiDocumentHighlightParams struct { + // The text document. + TextDocument TextDocumentIdentifier `json:"textDocument"` + + // The position inside the text document. + Position Position `json:"position"` + + // The list of file URIs to search for highlights across. + FilesToSearch []DocumentUri `json:"filesToSearch"` +} + +func (s *MultiDocumentHighlightParams) TextDocumentURI() DocumentUri { + return s.TextDocument.Uri +} + +func (s *MultiDocumentHighlightParams) TextDocumentPosition() Position { + return s.Position +} + +var _ json.UnmarshalerFrom = (*MultiDocumentHighlightParams)(nil) + +func (s *MultiDocumentHighlightParams) UnmarshalJSONFrom(dec *json.Decoder) error { + const ( + missingTextDocument uint = 1 << iota + missingPosition + missingFilesToSearch + _missingLast + ) + missing := _missingLast - 1 + + if k := dec.PeekKind(); k != '{' { + return errNotObject(k) + } + if _, err := dec.ReadToken(); err != nil { + return err + } + + for dec.PeekKind() != '}' { + name, err := dec.ReadValue() + if err != nil { + return err + } + switch string(name) { + case `"textDocument"`: + missing &^= missingTextDocument + if err := json.UnmarshalDecode(dec, &s.TextDocument); err != nil { + return err + } + case `"position"`: + missing &^= missingPosition + if err := json.UnmarshalDecode(dec, &s.Position); err != nil { + return err + } + case `"filesToSearch"`: + missing &^= missingFilesToSearch + if dec.PeekKind() == 'n' { + return errNull("filesToSearch") + } + if err := json.UnmarshalDecode(dec, &s.FilesToSearch); err != nil { + return err + } + default: + if err := dec.SkipValue(); err != nil { + return err + } + } + } + + if _, err := dec.ReadToken(); err != nil { + return err + } + + if missing != 0 { + var missingProps []string + if missing&missingTextDocument != 0 { + missingProps = append(missingProps, "textDocument") + } + if missing&missingPosition != 0 { + missingProps = append(missingProps, "position") + } + if missing&missingFilesToSearch != 0 { + missingProps = append(missingProps, "filesToSearch") + } + return errMissing(missingProps) + } + + return nil +} + // CallHierarchyItemData is a placeholder for custom data preserved on a CallHierarchyItem. type CallHierarchyItemData struct{} @@ -30520,6 +30690,8 @@ func unmarshalParams(method Method, data []byte) (any, error) { return unmarshalPtrTo[ProjectInfoParams](data) case MethodCustomTextDocumentSourceDefinition: return unmarshalPtrTo[TextDocumentPositionParams](data) + case MethodCustomTextDocumentMultiDocumentHighlight: + return unmarshalPtrTo[MultiDocumentHighlightParams](data) case MethodWorkspaceDidChangeWorkspaceFolders: return unmarshalPtrTo[DidChangeWorkspaceFoldersParams](data) case MethodWindowWorkDoneProgressCancel: @@ -30727,6 +30899,8 @@ func unmarshalResult(method Method, data []byte) (any, error) { return unmarshalValue[CustomProjectInfoResponse](data) case MethodCustomTextDocumentSourceDefinition: return unmarshalValue[CustomTextDocumentSourceDefinitionResponse](data) + case MethodCustomTextDocumentMultiDocumentHighlight: + return unmarshalValue[CustomMultiDocumentHighlightResponse](data) default: return unmarshalAny(data) } @@ -31049,6 +31223,8 @@ const ( MethodCustomProjectInfo Method = "custom/projectInfo" // Request to get source definitions for a position. MethodCustomTextDocumentSourceDefinition Method = "custom/textDocument/sourceDefinition" + // Request to get document highlights across multiple files. + MethodCustomTextDocumentMultiDocumentHighlight Method = "custom/textDocument/multiDocumentHighlight" // The `workspace/didChangeWorkspaceFolders` notification is sent from the client to the server when the workspace // folder configuration changes. MethodWorkspaceDidChangeWorkspaceFolders Method = "workspace/didChangeWorkspaceFolders" @@ -31600,6 +31776,12 @@ type CustomTextDocumentSourceDefinitionResponse = *LocationOrLocationsOrDefiniti // Type mapping info for `custom/textDocument/sourceDefinition` var CustomTextDocumentSourceDefinitionInfo = RequestInfo[*TextDocumentPositionParams, CustomTextDocumentSourceDefinitionResponse]{Method: MethodCustomTextDocumentSourceDefinition} +// Response type for `custom/textDocument/multiDocumentHighlight` +type CustomMultiDocumentHighlightResponse = MultiDocumentHighlightsOrNull + +// Type mapping info for `custom/textDocument/multiDocumentHighlight` +var CustomTextDocumentMultiDocumentHighlightInfo = RequestInfo[*MultiDocumentHighlightParams, CustomMultiDocumentHighlightResponse]{Method: MethodCustomTextDocumentMultiDocumentHighlight} + // Type mapping info for `workspace/didChangeWorkspaceFolders` var WorkspaceDidChangeWorkspaceFoldersInfo = NotificationInfo[*DidChangeWorkspaceFoldersParams]{Method: MethodWorkspaceDidChangeWorkspaceFolders} @@ -35111,6 +35293,36 @@ func (o *CustomClosingTagCompletionOrNull) UnmarshalJSONFrom(dec *json.Decoder) } } +type MultiDocumentHighlightsOrNull struct { + MultiDocumentHighlights *[]*MultiDocumentHighlight +} + +var _ json.MarshalerTo = (*MultiDocumentHighlightsOrNull)(nil) + +func (o *MultiDocumentHighlightsOrNull) MarshalJSONTo(enc *json.Encoder) error { + if o.MultiDocumentHighlights != nil { + return json.MarshalEncode(enc, o.MultiDocumentHighlights) + } + return enc.WriteToken(json.Null) +} + +var _ json.UnmarshalerFrom = (*MultiDocumentHighlightsOrNull)(nil) + +func (o *MultiDocumentHighlightsOrNull) UnmarshalJSONFrom(dec *json.Decoder) error { + *o = MultiDocumentHighlightsOrNull{} + + switch dec.PeekKind() { + case 'n': + _, err := dec.ReadToken() + return err + case '[': + o.MultiDocumentHighlights = new([]*MultiDocumentHighlight) + return json.UnmarshalDecode(dec, o.MultiDocumentHighlights) + default: + return errInvalidKind("MultiDocumentHighlightsOrNull", dec.PeekKind()) + } +} + type RequestFailureTelemetryEventOrPerformanceStatsTelemetryEventOrProjectInfoTelemetryEventOrNull struct { RequestFailureTelemetryEvent *RequestFailureTelemetryEvent PerformanceStatsTelemetryEvent *PerformanceStatsTelemetryEvent diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 872e4ced7cd..2b970f997c8 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -703,6 +703,7 @@ var handlers = sync.OnceValue(func() handlerMap { registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentOnTypeFormattingInfo, (*Server).handleDocumentOnTypeFormat) registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentDocumentSymbolInfo, (*Server).handleDocumentSymbol) registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentDocumentHighlightInfo, (*Server).handleDocumentHighlight) + registerLanguageServiceDocumentRequestHandler(handlers, lsproto.CustomTextDocumentMultiDocumentHighlightInfo, (*Server).handleMultiDocumentHighlight) registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentSelectionRangeInfo, (*Server).handleSelectionRange) registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentInlayHintInfo, (*Server).handleInlayHint) registerLanguageServiceDocumentRequestHandler(handlers, lsproto.TextDocumentCodeLensInfo, (*Server).handleCodeLens) @@ -1080,7 +1081,8 @@ func (s *Server) handleInitialize(ctx context.Context, params *lsproto.Initializ CallHierarchyProvider: &lsproto.BooleanOrCallHierarchyOptionsOrCallHierarchyRegistrationOptions{ Boolean: new(true), }, - CustomSourceDefinitionProvider: new(true), + CustomSourceDefinitionProvider: new(true), + CustomMultiDocumentHighlightProvider: new(true), }, } @@ -1367,6 +1369,10 @@ func (s *Server) handleDocumentHighlight(ctx context.Context, ls *ls.LanguageSer return ls.ProvideDocumentHighlights(ctx, params.TextDocument.Uri, params.Position) } +func (s *Server) handleMultiDocumentHighlight(ctx context.Context, ls *ls.LanguageService, params *lsproto.MultiDocumentHighlightParams) (lsproto.CustomMultiDocumentHighlightResponse, error) { + return ls.ProvideMultiDocumentHighlights(ctx, params.TextDocument.Uri, params.Position, params.FilesToSearch) +} + func (s *Server) handleSelectionRange(ctx context.Context, ls *ls.LanguageService, params *lsproto.SelectionRangeParams) (lsproto.SelectionRangeResponse, error) { return ls.ProvideSelectionRanges(ctx, params) } diff --git a/package-lock.json b/package-lock.json index 4200a9a8cde..50a854ffd98 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,15 +38,16 @@ "version": "0.0.0", "dependencies": { "@vscode/extension-telemetry": "^1.5.1", - "vscode-languageclient": "^10.0.0-next.21" + "vscode-languageclient": "10.0.0-next.21", + "vscode-languageserver-protocol": "3.17.6-next.17" }, "devDependencies": { - "@types/vscode": "~1.106.1", + "@types/vscode": "~1.110.0", "@vscode/vsce": "^3.7.1", "esbuild": "^0.27.4" }, "engines": { - "vscode": "^1.106.0" + "vscode": "^1.110.0" } }, "_packages/api": { @@ -1401,9 +1402,9 @@ "license": "MIT" }, "node_modules/@types/vscode": { - "version": "1.106.1", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.106.1.tgz", - "integrity": "sha512-R/HV8u2h8CAddSbX8cjpdd7B8/GnE4UjgjpuGuHcbp1xV6yh4OeqU4L1pKjlwujCrSFS0MOpwJAIs/NexMB1fQ==", + "version": "1.110.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.110.0.tgz", + "integrity": "sha512-AGuxUEpU4F4mfuQjxPPaQVyuOMhs+VT/xRok1jiHVBubHK7lBRvCuOMZG0LKUwxncrPorJ5qq/uil3IdZBd5lA==", "dev": true, "license": "MIT" }, diff --git a/testdata/baselines/reference/fourslash/documentHighlights/documentHighlights02.baseline.jsonc b/testdata/baselines/reference/fourslash/documentHighlights/documentHighlights02.baseline.jsonc new file mode 100644 index 00000000000..d53d228bb5a --- /dev/null +++ b/testdata/baselines/reference/fourslash/documentHighlights/documentHighlights02.baseline.jsonc @@ -0,0 +1,34 @@ +// === documentHighlights === +// filesToSearch: +// a.ts +// b.ts + +// === /a.ts === +// function /*HIGHLIGHTS*/[|foo|] () { +// return 1; +// } +// [|foo|](); + + + +// === documentHighlights === +// filesToSearch: +// a.ts +// b.ts + +// === /a.ts === +// function [|foo|] () { +// return 1; +// } +// /*HIGHLIGHTS*/[|foo|](); + + + +// === documentHighlights === +// filesToSearch: +// a.ts +// b.ts + +// === /b.ts === +// /// +// /*HIGHLIGHTS*/[|foo|](); \ No newline at end of file diff --git a/testdata/baselines/reference/fourslash/documentHighlights/documentHighlights_33722.baseline.jsonc b/testdata/baselines/reference/fourslash/documentHighlights/documentHighlights_33722.baseline.jsonc index a789840493b..c0a4b45e140 100644 --- a/testdata/baselines/reference/fourslash/documentHighlights/documentHighlights_33722.baseline.jsonc +++ b/testdata/baselines/reference/fourslash/documentHighlights/documentHighlights_33722.baseline.jsonc @@ -1,4 +1,7 @@ // === documentHighlights === +// filesToSearch: +// /x.ts + // === /x.ts === // import y from "./y"; // diff --git a/testdata/baselines/reference/fourslash/documentHighlights/documentHighlights_moduleImport_filesToSearch.baseline.jsonc b/testdata/baselines/reference/fourslash/documentHighlights/documentHighlights_moduleImport_filesToSearch.baseline.jsonc new file mode 100644 index 00000000000..9453b9f6b2c --- /dev/null +++ b/testdata/baselines/reference/fourslash/documentHighlights/documentHighlights_moduleImport_filesToSearch.baseline.jsonc @@ -0,0 +1,25 @@ +// === documentHighlights === +// filesToSearch: +// /a.ts +// /b.ts + +// === /a.ts === +// import * as foo from "foo"; +// foo./*HIGHLIGHTS*/[|x|]; + +// === /b.ts === +// import { [|x|] } from "foo"; + + + +// === documentHighlights === +// filesToSearch: +// /a.ts +// /b.ts + +// === /a.ts === +// import * as foo from "foo"; +// foo.[|x|]; + +// === /b.ts === +// import { /*HIGHLIGHTS*/[|x|] } from "foo"; \ No newline at end of file diff --git a/testdata/baselines/reference/fourslash/documentHighlights/documentHighlights_moduleImport_filesToSearchWithInvalidFile.baseline.jsonc b/testdata/baselines/reference/fourslash/documentHighlights/documentHighlights_moduleImport_filesToSearchWithInvalidFile.baseline.jsonc new file mode 100644 index 00000000000..c9fa5a6dd2d --- /dev/null +++ b/testdata/baselines/reference/fourslash/documentHighlights/documentHighlights_moduleImport_filesToSearchWithInvalidFile.baseline.jsonc @@ -0,0 +1,27 @@ +// === documentHighlights === +// filesToSearch: +// /a.ts +// /b.ts +// /unknown.ts + +// === /a.ts === +// import * as foo from "foo"; +// foo./*HIGHLIGHTS*/[|x|]; + +// === /b.ts === +// import { [|x|] } from "foo"; + + + +// === documentHighlights === +// filesToSearch: +// /a.ts +// /b.ts +// /unknown.ts + +// === /a.ts === +// import * as foo from "foo"; +// foo.[|x|]; + +// === /b.ts === +// import { /*HIGHLIGHTS*/[|x|] } from "foo"; \ No newline at end of file diff --git a/testdata/baselines/reference/fourslash/documentHighlights/documentHighlights_windowsPath.baseline.jsonc b/testdata/baselines/reference/fourslash/documentHighlights/documentHighlights_windowsPath.baseline.jsonc new file mode 100644 index 00000000000..93a220cd7d1 --- /dev/null +++ b/testdata/baselines/reference/fourslash/documentHighlights/documentHighlights_windowsPath.baseline.jsonc @@ -0,0 +1,6 @@ +// === documentHighlights === +// filesToSearch: +// C:/a/b/c.ts + +// === C:/a/b/c.ts === +// var /*HIGHLIGHTS*/x = 1; \ No newline at end of file diff --git a/testdata/baselines/reference/fourslash/documentHighlights/exportInLabeledStatement.baseline.jsonc b/testdata/baselines/reference/fourslash/documentHighlights/exportInLabeledStatement.baseline.jsonc index c36893dcbf3..c43b2bad5ed 100644 --- a/testdata/baselines/reference/fourslash/documentHighlights/exportInLabeledStatement.baseline.jsonc +++ b/testdata/baselines/reference/fourslash/documentHighlights/exportInLabeledStatement.baseline.jsonc @@ -1,4 +1,7 @@ // === documentHighlights === +// filesToSearch: +// /a.ts + // === /a.ts === // subTitle: // /*HIGHLIGHTS*/export const title: string \ No newline at end of file diff --git a/testdata/baselines/reference/fourslash/documentHighlights/exportInObjectLiteral.baseline.jsonc b/testdata/baselines/reference/fourslash/documentHighlights/exportInObjectLiteral.baseline.jsonc index da96d37cf57..32990928486 100644 --- a/testdata/baselines/reference/fourslash/documentHighlights/exportInObjectLiteral.baseline.jsonc +++ b/testdata/baselines/reference/fourslash/documentHighlights/exportInObjectLiteral.baseline.jsonc @@ -1,4 +1,7 @@ // === documentHighlights === +// filesToSearch: +// /a.ts + // === /a.ts === // const k = { // /*HIGHLIGHTS*/export f() { } diff --git a/testdata/baselines/reference/fourslash/documentHighlights/findAllRefsForModule.baseline.jsonc b/testdata/baselines/reference/fourslash/documentHighlights/findAllRefsForModule.baseline.jsonc index c3259d6a17a..282b3c9e0e9 100644 --- a/testdata/baselines/reference/fourslash/documentHighlights/findAllRefsForModule.baseline.jsonc +++ b/testdata/baselines/reference/fourslash/documentHighlights/findAllRefsForModule.baseline.jsonc @@ -1,15 +1,48 @@ // === documentHighlights === +// filesToSearch: +// /b.ts +// /c/sub.js +// /d.ts + // === /b.ts === // import { x } from "/*HIGHLIGHTS*/[|./a|]"; +// === /c/sub.js === +// const a = require("[|../a|]"); + +// === /d.ts === +// /// + // === documentHighlights === +// filesToSearch: +// /b.ts +// /c/sub.js +// /d.ts + +// === /b.ts === +// import { x } from "[|./a|]"; + // === /c/sub.js === // const a = require("/*HIGHLIGHTS*/[|../a|]"); +// === /d.ts === +// /// + // === documentHighlights === +// filesToSearch: +// /b.ts +// /c/sub.js +// /d.ts + +// === /b.ts === +// import { x } from "[|./a|]"; + +// === /c/sub.js === +// const a = require("[|../a|]"); + // === /d.ts === // /// \ No newline at end of file