Make JS/TS go to configuration commands work on non-file: file systems (#183688)

Make `go to project` commands work on non-`file:` file systems

Fixes #183685
This commit is contained in:
Matt Bierner 2023-05-30 16:07:14 -07:00 committed by GitHub
parent 9f3c499c33
commit d1ae1fff18
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 37 additions and 35 deletions

View file

@ -239,7 +239,7 @@ class UpdateImportsOnFileRenameHandler extends Disposable {
for (const rename of renames) {
// Group renames by type (js/ts) and by workspace.
const key = `${this.client.getWorkspaceRootForResource(rename.jsTsFileThatIsBeingMoved)}@@@${doesResourceLookLikeATypeScriptFile(rename.jsTsFileThatIsBeingMoved)}`;
const key = `${this.client.getWorkspaceRootForResource(rename.jsTsFileThatIsBeingMoved)?.fsPath}@@@${doesResourceLookLikeATypeScriptFile(rename.jsTsFileThatIsBeingMoved)}`;
if (!groups.has(key)) {
groups.set(key, new Set());
}

View file

@ -4,10 +4,11 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { vscodeNotebookCell } from '../configuration/fileSchemes';
import { officeScript, vscodeNotebookCell } from '../configuration/fileSchemes';
import * as languageModeIds from '../configuration/languageIds';
import * as typeConverters from '../typeConverters';
import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService';
import { inMemoryResourcePrefix } from '../typescriptServiceClient';
import { coalesce } from '../utils/arrays';
import { Delayer, setImmediate } from '../utils/async';
import { nulToken } from '../utils/cancellation';
@ -200,7 +201,7 @@ class SyncedBuffer {
const args: Proto.OpenRequestArgs = {
file: this.filepath,
fileContent: this.document.getText(),
projectRootPath: this.client.getWorkspaceRootForResource(this.document.uri),
projectRootPath: this.getProjectRootPath(this.document.uri),
};
const scriptKind = mode2ScriptKind(this.document.languageId);
@ -219,6 +220,16 @@ class SyncedBuffer {
this.state = BufferState.Open;
}
private getProjectRootPath(resource: vscode.Uri): string | undefined {
const workspaceRoot = this.client.getWorkspaceRootForResource(resource);
if (workspaceRoot) {
const tsRoot = this.client.toTsFilePath(workspaceRoot);
return tsRoot?.startsWith(inMemoryResourcePrefix) ? undefined : tsRoot;
}
return resource.scheme === officeScript ? '/' : undefined;
}
public get resource(): vscode.Uri {
return this.document.uri;
}

View file

@ -3,7 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import * as vscode from 'vscode';
import type * as Proto from './tsServer/protocol/protocol';
import { ITypeScriptServiceClient, ServerResponse } from './typescriptService';
@ -87,10 +86,10 @@ function inferredProjectConfigSnippet(
export async function openOrCreateConfig(
projectType: ProjectType,
rootPath: string,
rootPath: vscode.Uri,
configuration: TypeScriptServiceConfiguration,
): Promise<vscode.TextEditor | null> {
const configFile = vscode.Uri.file(path.join(rootPath, projectType === ProjectType.TypeScript ? 'tsconfig.json' : 'jsconfig.json'));
const configFile = vscode.Uri.joinPath(rootPath, projectType === ProjectType.TypeScript ? 'tsconfig.json' : 'jsconfig.json');
const col = vscode.window.activeTextEditor?.viewColumn;
try {
const doc = await vscode.workspace.openTextDocument(configFile);
@ -108,11 +107,11 @@ export async function openOrCreateConfig(
export async function openProjectConfigOrPromptToCreate(
projectType: ProjectType,
client: ITypeScriptServiceClient,
rootPath: string,
configFileName: string,
rootPath: vscode.Uri,
configFilePath: string,
): Promise<void> {
if (!isImplicitProjectConfigFile(configFileName)) {
const doc = await vscode.workspace.openTextDocument(configFileName);
if (!isImplicitProjectConfigFile(configFilePath)) {
const doc = await vscode.workspace.openTextDocument(client.toResource(configFilePath));
vscode.window.showTextDocument(doc, vscode.window.activeTextEditor?.viewColumn);
return;
}

View file

@ -156,7 +156,7 @@ export interface ITypeScriptServiceClient {
*/
hasCapabilityForResource(resource: vscode.Uri, capability: ClientCapability): boolean;
getWorkspaceRootForResource(resource: vscode.Uri): string | undefined;
getWorkspaceRootForResource(resource: vscode.Uri): vscode.Uri | undefined;
readonly onTsServerStarted: vscode.Event<{ version: TypeScriptVersion; usedApiVersion: API }>;
readonly onProjectLanguageServiceStateChanged: vscode.Event<Proto.ProjectLanguageServiceStateEventBody>;

View file

@ -91,10 +91,12 @@ namespace ServerState {
export type State = typeof None | Running | Errored;
}
export const emptyAuthority = 'ts-nul-authority';
export const inMemoryResourcePrefix = '^';
export default class TypeScriptServiceClient extends Disposable implements ITypeScriptServiceClient {
private readonly emptyAuthority = 'ts-nul-authority';
private readonly inMemoryResourcePrefix = '^';
private readonly _onReady?: { promise: Promise<void>; resolve: () => void; reject: () => void };
private _configuration: TypeScriptServiceConfiguration;
@ -695,9 +697,9 @@ export default class TypeScriptServiceClient extends Disposable implements IType
return resource.fsPath;
}
return (this.isProjectWideIntellisenseOnWebEnabled() ? '' : this.inMemoryResourcePrefix)
return (this.isProjectWideIntellisenseOnWebEnabled() ? '' : inMemoryResourcePrefix)
+ '/' + resource.scheme
+ '/' + (resource.authority || this.emptyAuthority)
+ '/' + (resource.authority || emptyAuthority)
+ (resource.path.startsWith('/') ? resource.path : '/' + resource.path)
+ (resource.fragment ? '#' + resource.fragment : '');
}
@ -739,46 +741,36 @@ export default class TypeScriptServiceClient extends Disposable implements IType
}
const parts = filepath.match(/^\/([^\/]+)\/([^\/]*)\/(.+)$/);
if (parts) {
const resource = vscode.Uri.parse(parts[1] + '://' + (parts[2] === this.emptyAuthority ? '' : parts[2]) + '/' + parts[3]);
const resource = vscode.Uri.parse(parts[1] + '://' + (parts[2] === emptyAuthority ? '' : parts[2]) + '/' + parts[3]);
return this.bufferSyncSupport.toVsCodeResource(resource);
}
}
if (filepath.startsWith(this.inMemoryResourcePrefix)) {
if (filepath.startsWith(inMemoryResourcePrefix)) {
const parts = filepath.match(/^\^\/([^\/]+)\/([^\/]*)\/(.+)$/);
if (parts) {
const resource = vscode.Uri.parse(parts[1] + '://' + (parts[2] === this.emptyAuthority ? '' : parts[2]) + '/' + parts[3]);
const resource = vscode.Uri.parse(parts[1] + '://' + (parts[2] === emptyAuthority ? '' : parts[2]) + '/' + parts[3]);
return this.bufferSyncSupport.toVsCodeResource(resource);
}
}
return this.bufferSyncSupport.toResource(filepath);
}
public getWorkspaceRootForResource(resource: vscode.Uri): string | undefined {
public getWorkspaceRootForResource(resource: vscode.Uri): vscode.Uri | undefined {
const roots = vscode.workspace.workspaceFolders ? Array.from(vscode.workspace.workspaceFolders) : undefined;
if (!roots?.length) {
if (resource.scheme === fileSchemes.officeScript) {
return '/';
}
return undefined;
}
let tsRootPath: string | undefined;
for (const root of roots.sort((a, b) => a.uri.fsPath.length - b.uri.fsPath.length)) {
if (root.uri.scheme === resource.scheme && root.uri.authority === resource.authority) {
if (resource.fsPath.startsWith(root.uri.fsPath + path.sep)) {
tsRootPath = this.toTsFilePath(root.uri);
break;
return root.uri;
}
}
}
tsRootPath ??= this.toTsFilePath(roots[0].uri);
if (!tsRootPath || tsRootPath.startsWith(this.inMemoryResourcePrefix)) {
return undefined;
}
return tsRootPath;
return undefined;
}
public execute(command: keyof TypeScriptRequests, args: any, token: vscode.CancellationToken, config?: ExecConfig): Promise<ServerResponse.Response<Proto.Response>> {

View file

@ -62,9 +62,9 @@ export class IntellisenseStatus extends Disposable {
commandManager.register({
id: this.openOpenConfigCommandId,
execute: async (rootPath: string, projectType: ProjectType) => {
execute: async (root: vscode.Uri, projectType: ProjectType) => {
if (this._state.type === IntellisenseState.Type.Resolved) {
await openProjectConfigOrPromptToCreate(projectType, this._client, rootPath, this._state.configFile);
await openProjectConfigOrPromptToCreate(projectType, this._client, root, this._state.configFile);
} else if (this._state.type === IntellisenseState.Type.Pending) {
await openProjectConfigForFile(projectType, this._client, this._state.resource);
}
@ -72,8 +72,8 @@ export class IntellisenseStatus extends Disposable {
});
commandManager.register({
id: this.createOrOpenConfigCommandId,
execute: async (rootPath: string, projectType: ProjectType) => {
await openOrCreateConfig(projectType, rootPath, this._client.configuration);
execute: async (root: vscode.Uri, projectType: ProjectType) => {
await openOrCreateConfig(projectType, root, this._client.configuration);
},
});