Path: blob/main/extensions/extension-editing/src/packageDocumentL10nSupport.ts
13383 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as vscode from 'vscode';6import { getLocation, getNodeValue, parseTree, findNodeAtLocation, visit } from 'jsonc-parser';789const packageJsonSelector: vscode.DocumentSelector = { language: 'json', pattern: '**/package.json' };10const packageNlsJsonSelector: vscode.DocumentSelector = { language: 'json', pattern: '**/package.nls.json' };1112export class PackageDocumentL10nSupport implements vscode.DefinitionProvider, vscode.ReferenceProvider, vscode.Disposable {1314private readonly _disposables: vscode.Disposable[] = [];1516constructor() {17this._disposables.push(vscode.languages.registerDefinitionProvider(packageJsonSelector, this));18this._disposables.push(vscode.languages.registerDefinitionProvider(packageNlsJsonSelector, this));1920this._disposables.push(vscode.languages.registerReferenceProvider(packageNlsJsonSelector, this));21this._disposables.push(vscode.languages.registerReferenceProvider(packageJsonSelector, this));22}2324dispose(): void {25for (const d of this._disposables) {26d.dispose();27}28}2930public async provideDefinition(document: vscode.TextDocument, position: vscode.Position, _token: vscode.CancellationToken): Promise<vscode.DefinitionLink[] | undefined> {31const basename = document.uri.path.split('/').pop()?.toLowerCase();32if (basename === 'package.json') {33return this.provideNlsValueDefinition(document, position);34}3536if (basename === 'package.nls.json') {37return this.provideNlsKeyDefinition(document, position);38}3940return undefined;41}4243private async provideNlsValueDefinition(packageJsonDoc: vscode.TextDocument, position: vscode.Position): Promise<vscode.DefinitionLink[] | undefined> {44const nlsRef = this.getNlsReferenceAtPosition(packageJsonDoc, position);45if (!nlsRef) {46return undefined;47}4849const nlsUri = vscode.Uri.joinPath(packageJsonDoc.uri, '..', 'package.nls.json');50return this.resolveNlsDefinition(nlsRef, nlsUri);51}5253private async provideNlsKeyDefinition(nlsDoc: vscode.TextDocument, position: vscode.Position): Promise<vscode.DefinitionLink[] | undefined> {54const nlsKey = this.getNlsKeyDefinitionAtPosition(nlsDoc, position);55if (!nlsKey) {56return undefined;57}58return this.resolveNlsDefinition(nlsKey, nlsDoc.uri);59}6061private async resolveNlsDefinition(origin: { key: string; range: vscode.Range }, nlsUri: vscode.Uri): Promise<vscode.DefinitionLink[] | undefined> {62const target = await this.findNlsKeyDeclaration(origin.key, nlsUri);63if (!target) {64return undefined;65}6667return [{68originSelectionRange: origin.range,69targetUri: target.uri,70targetRange: target.range,71}];72}7374private getNlsReferenceAtPosition(packageJsonDoc: vscode.TextDocument, position: vscode.Position): { key: string; range: vscode.Range } | undefined {75const location = getLocation(packageJsonDoc.getText(), packageJsonDoc.offsetAt(position));76if (!location.previousNode || location.previousNode.type !== 'string') {77return undefined;78}7980const value = getNodeValue(location.previousNode);81if (typeof value !== 'string') {82return undefined;83}8485const match = value.match(/^%(.+)%$/);86if (!match) {87return undefined;88}8990const nodeStart = packageJsonDoc.positionAt(location.previousNode.offset);91const nodeEnd = packageJsonDoc.positionAt(location.previousNode.offset + location.previousNode.length);92return { key: match[1], range: new vscode.Range(nodeStart, nodeEnd) };93}9495public async provideReferences(document: vscode.TextDocument, position: vscode.Position, context: vscode.ReferenceContext, _token: vscode.CancellationToken): Promise<vscode.Location[] | undefined> {96const basename = document.uri.path.split('/').pop()?.toLowerCase();97if (basename === 'package.nls.json') {98return this.provideNlsKeyReferences(document, position, context);99}100if (basename === 'package.json') {101return this.provideNlsValueReferences(document, position, context);102}103return undefined;104}105106private async provideNlsKeyReferences(nlsDoc: vscode.TextDocument, position: vscode.Position, context: vscode.ReferenceContext): Promise<vscode.Location[] | undefined> {107const nlsKey = this.getNlsKeyDefinitionAtPosition(nlsDoc, position);108if (!nlsKey) {109return undefined;110}111112const packageJsonUri = vscode.Uri.joinPath(nlsDoc.uri, '..', 'package.json');113return this.findAllNlsReferences(nlsKey.key, packageJsonUri, nlsDoc.uri, context);114}115116private async provideNlsValueReferences(packageJsonDoc: vscode.TextDocument, position: vscode.Position, context: vscode.ReferenceContext): Promise<vscode.Location[] | undefined> {117const nlsRef = this.getNlsReferenceAtPosition(packageJsonDoc, position);118if (!nlsRef) {119return undefined;120}121122const nlsUri = vscode.Uri.joinPath(packageJsonDoc.uri, '..', 'package.nls.json');123return this.findAllNlsReferences(nlsRef.key, packageJsonDoc.uri, nlsUri, context);124}125126private async findAllNlsReferences(nlsKey: string, packageJsonUri: vscode.Uri, nlsUri: vscode.Uri, context: vscode.ReferenceContext): Promise<vscode.Location[]> {127const locations = await this.findNlsReferencesInPackageJson(nlsKey, packageJsonUri);128129if (context.includeDeclaration) {130const decl = await this.findNlsKeyDeclaration(nlsKey, nlsUri);131if (decl) {132locations.push(decl);133}134}135136return locations;137}138139private async findNlsKeyDeclaration(nlsKey: string, nlsUri: vscode.Uri): Promise<vscode.Location | undefined> {140try {141const nlsDoc = await vscode.workspace.openTextDocument(nlsUri);142const nlsTree = parseTree(nlsDoc.getText());143if (!nlsTree) {144return undefined;145}146147const node = findNodeAtLocation(nlsTree, [nlsKey]);148if (!node?.parent) {149return undefined;150}151152const keyNode = node.parent.children?.[0];153if (!keyNode) {154return undefined;155}156157const start = nlsDoc.positionAt(keyNode.offset);158const end = nlsDoc.positionAt(keyNode.offset + keyNode.length);159return new vscode.Location(nlsUri, new vscode.Range(start, end));160} catch {161return undefined;162}163}164165private async findNlsReferencesInPackageJson(nlsKey: string, packageJsonUri: vscode.Uri): Promise<vscode.Location[]> {166let packageJsonDoc: vscode.TextDocument;167try {168packageJsonDoc = await vscode.workspace.openTextDocument(packageJsonUri);169} catch {170return [];171}172173const text = packageJsonDoc.getText();174const needle = `%${nlsKey}%`;175const locations: vscode.Location[] = [];176177visit(text, {178onLiteralValue(value, offset, length) {179if (value === needle) {180const start = packageJsonDoc.positionAt(offset);181const end = packageJsonDoc.positionAt(offset + length);182locations.push(new vscode.Location(packageJsonUri, new vscode.Range(start, end)));183}184}185});186187return locations;188}189190private getNlsKeyDefinitionAtPosition(nlsDoc: vscode.TextDocument, position: vscode.Position): { key: string; range: vscode.Range } | undefined {191const location = getLocation(nlsDoc.getText(), nlsDoc.offsetAt(position));192193// Must be on a top-level property key194if (location.path.length !== 1 || !location.isAtPropertyKey || !location.previousNode) {195return undefined;196}197198const key = location.path[0] as string;199const start = nlsDoc.positionAt(location.previousNode.offset);200const end = nlsDoc.positionAt(location.previousNode.offset + location.previousNode.length);201return { key, range: new vscode.Range(start, end) };202}203}204205206