mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-24 18:49:00 +01:00
Adopting langauge status api for JS/TS versions (#132015)
* Adopting language status api for JS/TS versions * Polish
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user