Add consolidated IntelliSense status item

Fixes #133731
This commit is contained in:
Matt Bierner
2021-09-28 18:30:30 -07:00
parent 74d4b33482
commit c03da8b023
3 changed files with 63 additions and 93 deletions

View File

@@ -20,8 +20,7 @@ import { ILogDirectoryProvider } from './tsServer/logDirectoryProvider';
import { TsServerProcessFactory } from './tsServer/server';
import { ITypeScriptVersionProvider } from './tsServer/versionProvider';
import TypeScriptServiceClient from './typescriptServiceClient';
import { CapabilitiesStatus } from './ui/capabilitiesStatus';
import { ProjectStatus } from './ui/projectStatus';
import { IntellisenseStatus } from './ui/intellisenseStatus';
import { VersionStatus } from './ui/versionStatus';
import { ActiveJsTsEditorTracker } from './utils/activeJsTsEditorTracker';
import { coalesce, flatten } from './utils/arrays';
@@ -94,9 +93,8 @@ 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 CapabilitiesStatus(this.client));
this._register(new VersionStatus(this.client));
this._register(new ProjectStatus(this.client, services.commandManager, services.activeJsTsEditorTracker));
this._register(new IntellisenseStatus(this.client, services.commandManager, services.activeJsTsEditorTracker));
this._register(new AtaProgressReporter(this.client));
this.typingsStatus = this._register(new TypingsStatus(this.client));
this._register(LargeProjectStatus.create(this.client));

View File

@@ -1,46 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 { ClientCapability, ITypeScriptServiceClient } from '../typescriptService';
import { Disposable } from '../utils/dispose';
import { jsTsLanguageModes } from '../utils/languageModeIds';
const localize = nls.loadMessageBundle();
export class CapabilitiesStatus extends Disposable {
private _statusItem?: vscode.LanguageStatusItem;
constructor(
private readonly _client: ITypeScriptServiceClient,
) {
super();
this._register(this._client.onTsServerStarted(() => this.update()));
this._register(this._client.onDidChangeCapabilities(() => this.update()));
this.update();
}
public override dispose() {
super.dispose();
this._statusItem?.dispose();
}
private update() {
if (this._client.capabilities.has(ClientCapability.Semantic)) {
this._statusItem?.dispose();
} else {
if (!this._statusItem) {
this._statusItem = this._register(vscode.languages.createLanguageStatusItem('typescript.capabilities', jsTsLanguageModes));
}
this._statusItem.name = localize('capabilitiesStatus.name', "IntelliSense Status");
this._statusItem.text = localize('capabilitiesStatus.detail.syntaxOnly', "Using single file IntelliSense");
}
}
}

View File

@@ -6,7 +6,7 @@
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { CommandManager } from '../commands/commandManager';
import { ITypeScriptServiceClient } from '../typescriptService';
import { ClientCapability, ITypeScriptServiceClient } from '../typescriptService';
import { ActiveJsTsEditorTracker } from '../utils/activeJsTsEditorTracker';
import { Disposable } from '../utils/dispose';
import { jsTsLanguageModes, isSupportedLanguageMode } from '../utils/languageModeIds';
@@ -15,11 +15,13 @@ import { isImplicitProjectConfigFile, openOrCreateConfig, openProjectConfigForFi
const localize = nls.loadMessageBundle();
namespace ProjectInfoState {
export const enum Type { None, Pending, Resolved }
namespace IntellisenseState {
export const enum Type { None, Pending, Resolved, SyntaxOnly }
export const None = Object.freeze({ type: Type.None } as const);
export const SyntaxOnly = Object.freeze({ type: Type.SyntaxOnly } as const);
export class Pending {
public readonly type = Type.Pending;
@@ -39,10 +41,10 @@ namespace ProjectInfoState {
) { }
}
export type State = typeof None | Pending | Resolved;
export type State = typeof None | Pending | Resolved | typeof SyntaxOnly;
}
export class ProjectStatus extends Disposable {
export class IntellisenseStatus extends Disposable {
public readonly openOpenConfigCommandId = '_typescript.openConfig';
public readonly createConfigCommandId = '_typescript.createConfig';
@@ -50,7 +52,7 @@ export class ProjectStatus extends Disposable {
private readonly _statusItem: vscode.LanguageStatusItem;
private _ready = false;
private _state: ProjectInfoState.State = ProjectInfoState.None;
private _state: IntellisenseState.State = IntellisenseState.None;
constructor(
private readonly _client: ITypeScriptServiceClient,
@@ -60,15 +62,14 @@ export class ProjectStatus extends Disposable {
super();
this._statusItem = this._register(vscode.languages.createLanguageStatusItem('typescript.projectStatus', jsTsLanguageModes));
this._statusItem.name = localize('statusItem.name', "Project config");
this._statusItem.text = 'TSConfig';
this._statusItem.name = localize('statusItem.name', "JS/TS IntelliSense Status");
commandManager.register({
id: this.openOpenConfigCommandId,
execute: async (rootPath: string) => {
if (this._state.type === ProjectInfoState.Type.Resolved) {
if (this._state.type === IntellisenseState.Type.Resolved) {
await openProjectConfigOrPromptToCreate(ProjectType.TypeScript, this._client, rootPath, this._state.configFile);
} else if (this._state.type === ProjectInfoState.Type.Pending) {
} else if (this._state.type === IntellisenseState.Type.Pending) {
await openProjectConfigForFile(ProjectType.TypeScript, this._client, this._state.resource);
}
},
@@ -89,42 +90,44 @@ export class ProjectStatus extends Disposable {
}
private async updateStatus() {
const editor = this._activeTextEditorManager.activeJsTsEditor;
if (!editor) {
this.updateState(ProjectInfoState.None);
const doc = this._activeTextEditorManager.activeJsTsEditor?.document;
if (!doc || !isSupportedLanguageMode(doc)) {
this.updateState(IntellisenseState.None);
return;
}
const doc = editor.document;
if (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;
}
if (!this._client.hasCapabilityForResource(doc.uri, ClientCapability.Semantic)) {
this.updateState(IntellisenseState.SyntaxOnly);
return;
}
this.updateState(ProjectInfoState.None);
const file = this._client.toOpenedFilePath(doc, { suppressAlertOnFailure: true });
if (!file) {
this.updateState(IntellisenseState.None);
return;
}
if (!this._ready) {
return;
}
const pendingState = new IntellisenseState.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 IntellisenseState.Resolved(doc.uri, response.body.configFileName));
}
}
}
private updateState(newState: ProjectInfoState.State): void {
private updateState(newState: IntellisenseState.State): void {
if (this._state === newState) {
return;
}
if (this._state.type === ProjectInfoState.Type.Pending) {
if (this._state.type === IntellisenseState.Type.Pending) {
this._state.cancellation.cancel();
this._state.cancellation.dispose();
}
@@ -132,36 +135,51 @@ export class ProjectStatus extends Disposable {
this._state = newState;
switch (this._state.type) {
case ProjectInfoState.Type.None:
case IntellisenseState.Type.None:
break;
case ProjectInfoState.Type.Pending:
this._statusItem.detail = '$(loading~spin)';
case IntellisenseState.Type.Pending:
this._statusItem.text = '$(loading~spin)';
this._statusItem.detail = localize('pending.detail', 'Loading IntelliSense status');
this._statusItem.command = undefined;
break;
case ProjectInfoState.Type.Resolved:
case IntellisenseState.Type.Resolved:
const rootPath = this._client.getWorkspaceRootForResource(this._state.resource);
if (!rootPath) {
return;
}
if (isImplicitProjectConfigFile(this._state.configFile)) {
this._statusItem.detail = localize('item.noTsConfig.detail', "None");
this._statusItem.text = localize('resolved.detail.noTsConfig', "No tsconfig");
this._statusItem.detail = undefined;
this._statusItem.command = {
command: this.createConfigCommandId,
title: localize('create.command', "Create tsconfig"),
title: localize('resolved.command.title.create', "Create tsconfig"),
arguments: [rootPath],
};
} else {
this._statusItem.detail = vscode.workspace.asRelativePath(this._state.configFile);
this._statusItem.text = vscode.workspace.asRelativePath(this._state.configFile);
this._statusItem.detail = undefined;
this._statusItem.command = {
command: this.openOpenConfigCommandId,
title: localize('item.command', "Open config file"),
title: localize('resolved.command.title.open', "Open config file"),
arguments: [rootPath],
};
}
break;
case IntellisenseState.Type.SyntaxOnly:
this._statusItem.text = localize('syntaxOnly.text', 'Partial Mode');
this._statusItem.detail = localize('syntaxOnly.detail', 'Project Wide IntelliSense not available');
this._statusItem.command = {
title: localize('syntaxOnly.command.title.learnMore', "Learn More"),
command: 'vscode.open',
arguments: [
vscode.Uri.parse('https://go.microsoft.com/fwlink/?linkid=2114477'), // TODO: add proper link once published
]
};
break;
}
}
}