Adopting langauge status api for JS/TS versions (#132015)

* Adopting language status api for JS/TS versions

* Polish
This commit is contained in:
Matt Bierner
2021-09-14 08:35:12 -07:00
committed by GitHub
parent ae0ec56d90
commit fc5e2f515c
4 changed files with 189 additions and 189 deletions

View File

@@ -8,7 +8,8 @@ import { Lazy } from '../utils/lazy';
import { Command } from './commandManager';
export class SelectTypeScriptVersionCommand implements Command {
public readonly id = 'typescript.selectTypeScriptVersion';
public static readonly id = 'typescript.selectTypeScriptVersion';
public readonly id = SelectTypeScriptVersionCommand.id;
public constructor(
private readonly lazyClientHost: Lazy<TypeScriptServiceClientHost>

View File

@@ -0,0 +1,163 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { CommandManager } from '../commands/commandManager';
import { ITypeScriptServiceClient } from '../typescriptService';
import { ActiveJsTsEditorTracker } from '../utils/activeJsTsEditorTracker';
import { Disposable } from '../utils/dispose';
import * as languageModeIds from '../utils/languageModeIds';
import { isImplicitProjectConfigFile, openOrCreateConfig, openProjectConfigForFile, openProjectConfigOrPromptToCreate, ProjectType } from '../utils/tsconfig';
const localize = nls.loadMessageBundle();
namespace ProjectInfoState {
export const enum Type { None, Pending, Resolved }
export const None = Object.freeze({ type: Type.None } as const);
export class Pending {
public readonly type = Type.Pending;
public readonly cancellation = new vscode.CancellationTokenSource();
constructor(
public readonly resource: vscode.Uri,
) { }
}
export class Resolved {
public readonly type = Type.Resolved;
constructor(
public readonly resource: vscode.Uri,
public readonly configFile: string,
) { }
}
export type State = typeof None | Pending | Resolved;
}
export class ProjectStatus extends Disposable {
public readonly openOpenConfigCommandId = '_typescript.openConfig';
public readonly createConfigCommandId = '_typescript.createConfig';
private readonly _statusItem: vscode.LanguageStatusItem;
private _ready = false;
private _state: ProjectInfoState.State = ProjectInfoState.None;
constructor(
private readonly _client: ITypeScriptServiceClient,
commandManager: CommandManager,
private readonly _activeTextEditorManager: ActiveJsTsEditorTracker,
) {
super();
this._statusItem = this._register(vscode.languages.createLanguageStatusItem('typescript.projectStatus', [
languageModeIds.javascript,
languageModeIds.javascriptreact,
languageModeIds.typescript,
languageModeIds.typescriptreact,
]));
this._statusItem.name = localize('statusItem.name', "Project config");
this._statusItem.text = 'TSConfig';
commandManager.register({
id: this.openOpenConfigCommandId,
execute: async (rootPath: string) => {
if (this._state.type === ProjectInfoState.Type.Resolved) {
await openProjectConfigOrPromptToCreate(ProjectType.TypeScript, this._client, rootPath, this._state.configFile);
} else if (this._state.type === ProjectInfoState.Type.Pending) {
await openProjectConfigForFile(ProjectType.TypeScript, this._client, this._state.resource);
}
},
});
commandManager.register({
id: this.createConfigCommandId,
execute: async (rootPath: string) => {
await openOrCreateConfig(ProjectType.TypeScript, rootPath, this._client.configuration);
},
});
_activeTextEditorManager.onDidChangeActiveJsTsEditor(this.updateStatus, this, this._disposables);
this._client.onReady(() => {
this._ready = true;
this.updateStatus();
});
}
private async updateStatus() {
const editor = this._activeTextEditorManager.activeJsTsEditor;
if (!editor) {
this.updateState(ProjectInfoState.None);
return;
}
const doc = editor.document;
if (languageModeIds.isSupportedLanguageMode(doc)) {
const file = this._client.toOpenedFilePath(doc, { suppressAlertOnFailure: true });
if (file) {
if (!this._ready) {
return;
}
const pendingState = new ProjectInfoState.Pending(doc.uri);
this.updateState(pendingState);
const response = await this._client.execute('projectInfo', { file, needFileNameList: false }, pendingState.cancellation.token);
if (response.type === 'response' && response.body) {
if (this._state === pendingState) {
this.updateState(new ProjectInfoState.Resolved(doc.uri, response.body.configFileName));
}
}
return;
}
}
this.updateState(ProjectInfoState.None);
}
private updateState(newState: ProjectInfoState.State): void {
if (this._state === newState) {
return;
}
if (this._state.type === ProjectInfoState.Type.Pending) {
this._state.cancellation.cancel();
this._state.cancellation.dispose();
}
this._state = newState;
const rootPath = this._state.type === ProjectInfoState.Type.Resolved ? this._client.getWorkspaceRootForResource(this._state.resource) : undefined;
if (!rootPath) {
return;
}
if (this._state.type === ProjectInfoState.Type.Resolved) {
if (isImplicitProjectConfigFile(this._state.configFile)) {
this._statusItem.detail = localize('item.noTsConfig.detail', "None");
this._statusItem.command = {
command: this.createConfigCommandId,
title: localize('create.command', "Create tsconfig"),
arguments: [rootPath],
};
return;
}
}
this._statusItem.detail = this._state.type === ProjectInfoState.Type.Resolved ? vscode.workspace.asRelativePath(this._state.configFile) : '';
this._statusItem.command = {
command: this.openOpenConfigCommandId,
title: localize('item.command', "Open config file"),
arguments: [rootPath],
};
}
}

View File

@@ -5,208 +5,42 @@
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { Command, CommandManager } from '../commands/commandManager';
import { SelectTypeScriptVersionCommand } from '../commands/selectTypeScriptVersion';
import { ITypeScriptServiceClient } from '../typescriptService';
import { ActiveJsTsEditorTracker } from '../utils/activeJsTsEditorTracker';
import { coalesce } from '../utils/arrays';
import { Disposable } from '../utils/dispose';
import { isTypeScriptDocument } from '../utils/languageModeIds';
import { isImplicitProjectConfigFile, openOrCreateConfig, openProjectConfigForFile, openProjectConfigOrPromptToCreate, ProjectType } from '../utils/tsconfig';
import * as languageModeIds from '../utils/languageModeIds';
import { TypeScriptVersion } from './versionProvider';
const localize = nls.loadMessageBundle();
export class VersionStatus extends Disposable {
namespace ProjectInfoState {
export const enum Type { None, Pending, Resolved }
export const None = Object.freeze({ type: Type.None } as const);
export class Pending {
public readonly type = Type.Pending;
public readonly cancellation = new vscode.CancellationTokenSource();
constructor(
public readonly resource: vscode.Uri,
) { }
}
export class Resolved {
public readonly type = Type.Resolved;
constructor(
public readonly resource: vscode.Uri,
public readonly configFile: string,
) { }
}
export type State = typeof None | Pending | Resolved;
}
interface QuickPickItem extends vscode.QuickPickItem {
run(): void;
}
class ProjectStatusCommand implements Command {
public readonly id = '_typescript.projectStatus';
public constructor(
private readonly _client: ITypeScriptServiceClient,
private readonly _delegate: () => ProjectInfoState.State,
) { }
public async execute(): Promise<void> {
const info = this._delegate();
const result = await vscode.window.showQuickPick<QuickPickItem>(coalesce([
this.getProjectItem(info),
this.getVersionItem(),
this.getHelpItem(),
]), {
placeHolder: localize('projectQuickPick.placeholder', "TypeScript Project Info"),
});
return result?.run();
}
private getVersionItem(): QuickPickItem {
return {
label: localize('projectQuickPick.version.label', "Select TypeScript Version..."),
description: localize('projectQuickPick.version.description', "[current = {0}]", this._client.apiVersion.displayName),
run: () => {
this._client.showVersionPicker();
}
};
}
private getProjectItem(info: ProjectInfoState.State): QuickPickItem | undefined {
const rootPath = info.type === ProjectInfoState.Type.Resolved ? this._client.getWorkspaceRootForResource(info.resource) : undefined;
if (!rootPath) {
return undefined;
}
if (info.type === ProjectInfoState.Type.Resolved) {
if (isImplicitProjectConfigFile(info.configFile)) {
return {
label: localize('projectQuickPick.project.create', "Create tsconfig"),
detail: localize('projectQuickPick.project.create.description', "This file is currently not part of a tsconfig/jsconfig project"),
run: () => {
openOrCreateConfig(ProjectType.TypeScript, rootPath, this._client.configuration);
}
};
}
}
return {
label: localize('projectQuickPick.version.goProjectConfig', "Open tsconfig"),
description: info.type === ProjectInfoState.Type.Resolved ? vscode.workspace.asRelativePath(info.configFile) : undefined,
run: () => {
if (info.type === ProjectInfoState.Type.Resolved) {
openProjectConfigOrPromptToCreate(ProjectType.TypeScript, this._client, rootPath, info.configFile);
} else if (info.type === ProjectInfoState.Type.Pending) {
openProjectConfigForFile(ProjectType.TypeScript, this._client, info.resource);
}
}
};
}
private getHelpItem(): QuickPickItem {
return {
label: localize('projectQuickPick.help', "TypeScript help"),
run: () => {
vscode.env.openExternal(vscode.Uri.parse('https://go.microsoft.com/fwlink/?linkid=839919')); // TODO:
}
};
}
}
export default class VersionStatus extends Disposable {
private readonly _statusBarEntry: vscode.StatusBarItem;
private _ready = false;
private _state: ProjectInfoState.State = ProjectInfoState.None;
private readonly _statusItem: vscode.LanguageStatusItem;
constructor(
private readonly _client: ITypeScriptServiceClient,
commandManager: CommandManager,
private readonly _activeTextEditorManager: ActiveJsTsEditorTracker,
) {
super();
this._statusBarEntry = this._register(vscode.window.createStatusBarItem('status.typescript', vscode.StatusBarAlignment.Right, 99 /* to the right of editor status (100) */));
this._statusBarEntry.name = localize('projectInfo.name', "TypeScript: Project Info");
this._statusItem = this._register(vscode.languages.createLanguageStatusItem('typescript.version', [
languageModeIds.javascript,
languageModeIds.javascriptreact,
languageModeIds.typescript,
languageModeIds.typescriptreact,
]));
const command = new ProjectStatusCommand(this._client, () => this._state);
commandManager.register(command);
this._statusBarEntry.command = command.id;
_activeTextEditorManager.onDidChangeActiveJsTsEditor(this.updateStatus, this, this._disposables);
this._client.onReady(() => {
this._ready = true;
this.updateStatus();
});
this._statusItem.name = localize('versionStatus.name', "TypeScript Version");
this._statusItem.detail = localize('versionStatus.detail', "TypeScript Version");
this._register(this._client.onTsServerStarted(({ version }) => this.onDidChangeTypeScriptVersion(version)));
}
private onDidChangeTypeScriptVersion(version: TypeScriptVersion) {
this._statusBarEntry.text = version.displayName;
this._statusBarEntry.tooltip = version.path;
this.updateStatus();
}
private async updateStatus() {
const editor = this._activeTextEditorManager.activeJsTsEditor;
if (!editor) {
this.hide();
return;
}
const doc = editor.document;
if (isTypeScriptDocument(doc)) {
const file = this._client.toOpenedFilePath(doc, { suppressAlertOnFailure: true });
if (file) {
this._statusBarEntry.show();
if (!this._ready) {
return;
}
const pendingState = new ProjectInfoState.Pending(doc.uri);
this.updateState(pendingState);
const response = await this._client.execute('projectInfo', { file, needFileNameList: false }, pendingState.cancellation.token);
if (response.type === 'response' && response.body) {
if (this._state === pendingState) {
this.updateState(new ProjectInfoState.Resolved(doc.uri, response.body.configFileName));
this._statusBarEntry.show();
}
}
return;
}
}
this.hide();
}
private hide(): void {
this._statusBarEntry.hide();
this.updateState(ProjectInfoState.None);
}
private updateState(newState: ProjectInfoState.State): void {
if (this._state === newState) {
return;
}
if (this._state.type === ProjectInfoState.Type.Pending) {
this._state.cancellation.cancel();
this._state.cancellation.dispose();
}
this._state = newState;
this._statusItem.text = version.displayName;
this._statusItem.command = {
command: SelectTypeScriptVersionCommand.id,
title: localize('versionStatus.command', "Select Version"),
tooltip: version.path
};
}
}

View File

@@ -17,9 +17,10 @@ import * as Proto from './protocol';
import * as PConst from './protocol.const';
import { OngoingRequestCancellerFactory } from './tsServer/cancellation';
import { ILogDirectoryProvider } from './tsServer/logDirectoryProvider';
import { ProjectStatus } from './tsServer/projectStatus';
import { TsServerProcessFactory } from './tsServer/server';
import { ITypeScriptVersionProvider } from './tsServer/versionProvider';
import VersionStatus from './tsServer/versionStatus';
import { VersionStatus } from './tsServer/versionStatus';
import TypeScriptServiceClient from './typescriptServiceClient';
import { ActiveJsTsEditorTracker } from './utils/activeJsTsEditorTracker';
import { coalesce, flatten } from './utils/arrays';
@@ -27,7 +28,7 @@ import { ServiceConfigurationProvider } from './utils/configuration';
import { Disposable } from './utils/dispose';
import * as errorCodes from './utils/errorCodes';
import { DiagnosticLanguage, LanguageDescription } from './utils/languageDescription';
import * as ProjectStatus from './utils/largeProjectStatus';
import * as LargeProjectStatus from './utils/largeProjectStatus';
import { LogLevelMonitor } from './utils/logLevelMonitor';
import { PluginManager } from './utils/plugins';
import * as typeConverters from './utils/typeConverters';
@@ -92,10 +93,11 @@ export default class TypeScriptServiceClientHost extends Disposable {
this.client.onConfigDiagnosticsReceived(diag => this.configFileDiagnosticsReceived(diag), null, this._disposables);
this.client.onResendModelsRequested(() => this.populateService(), null, this._disposables);
this._register(new VersionStatus(this.client, services.commandManager, services.activeJsTsEditorTracker));
this._register(new VersionStatus(this.client));
this._register(new ProjectStatus(this.client, services.commandManager, services.activeJsTsEditorTracker));
this._register(new AtaProgressReporter(this.client));
this.typingsStatus = this._register(new TypingsStatus(this.client));
this._register(ProjectStatus.create(this.client));
this._register(LargeProjectStatus.create(this.client));
this.fileConfigurationManager = this._register(new FileConfigurationManager(this.client, onCaseInsenitiveFileSystem));