Flatten conditional registration

This allows us to pass a precomputed, flat list of requirements when registering a language feature
This commit is contained in:
Matt Bierner 2020-07-16 11:33:44 -07:00
parent 740e2d3602
commit 16c6b81b3e
15 changed files with 161 additions and 130 deletions

View file

@ -3,15 +3,15 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { ITypeScriptServiceClient } from '../typescriptService';
import * as typeConverters from '../utils/typeConverters';
import API from '../utils/api';
import { VersionDependentRegistration } from '../utils/dependentRegistration';
import type * as Proto from '../protocol';
import * as path from 'path';
import * as vscode from 'vscode';
import type * as Proto from '../protocol';
import * as PConst from '../protocol.const';
import { ITypeScriptServiceClient } from '../typescriptService';
import API from '../utils/api';
import { conditionalRegistration, requireMinVersion } from '../utils/dependentRegistration';
import { parseKindModifier } from '../utils/modifiers';
import * as typeConverters from '../utils/typeConverters';
namespace Experimental {
export interface CallHierarchyItem extends Proto.CallHierarchyItem {
@ -120,7 +120,10 @@ export function register(
selector: vscode.DocumentSelector,
client: ITypeScriptServiceClient
) {
return new VersionDependentRegistration(client, TypeScriptCallHierarchySupport.minVersion,
() => vscode.languages.registerCallHierarchyProvider(selector,
new TypeScriptCallHierarchySupport(client)));
return conditionalRegistration([
requireMinVersion(client, TypeScriptCallHierarchySupport.minVersion),
], () => {
return vscode.languages.registerCallHierarchyProvider(selector,
new TypeScriptCallHierarchySupport(client));
});
}

View file

@ -12,14 +12,14 @@ import API from '../utils/api';
import { nulToken } from '../utils/cancellation';
import { applyCodeAction } from '../utils/codeAction';
import { Command, CommandManager } from '../utils/commandManager';
import { ConfigurationDependentRegistration } from '../utils/dependentRegistration';
import { conditionalRegistration, requireConfiguration } from '../utils/dependentRegistration';
import { parseKindModifier } from '../utils/modifiers';
import * as Previewer from '../utils/previewer';
import { snippetForFunctionCall } from '../utils/snippetForFunctionCall';
import { TelemetryReporter } from '../utils/telemetry';
import * as typeConverters from '../utils/typeConverters';
import TypingsStatus from '../utils/typingsStatus';
import FileConfigurationManager from './fileConfigurationManager';
import { parseKindModifier } from '../utils/modifiers';
const localize = nls.loadMessageBundle();
@ -804,8 +804,11 @@ export function register(
telemetryReporter: TelemetryReporter,
onCompletionAccepted: (item: vscode.CompletionItem) => void
) {
return new ConfigurationDependentRegistration(modeId, 'suggest.enabled', () =>
vscode.languages.registerCompletionItemProvider(selector,
return conditionalRegistration([
requireConfiguration(modeId, 'suggest.enabled'),
], () => {
return vscode.languages.registerCompletionItemProvider(selector,
new TypeScriptCompletionItemProvider(client, modeId, typingsStatus, fileConfigurationManager, commandManager, telemetryReporter, onCompletionAccepted),
...TypeScriptCompletionItemProvider.triggerCharacters));
...TypeScriptCompletionItemProvider.triggerCharacters);
});
}

View file

@ -8,7 +8,7 @@ import * as nls from 'vscode-nls';
import type * as Proto from '../protocol';
import { ITypeScriptServiceClient } from '../typescriptService';
import API from '../utils/api';
import { VersionDependentRegistration } from '../utils/dependentRegistration';
import { conditionalRegistration, requireMinVersion } from '../utils/dependentRegistration';
import * as errorCodes from '../utils/errorCodes';
import * as fixNames from '../utils/fixNames';
import * as typeConverters from '../utils/typeConverters';
@ -254,7 +254,9 @@ export function register(
fileConfigurationManager: FileConfigurationManager,
diagnosticsManager: DiagnosticsManager,
) {
return new VersionDependentRegistration(client, API.v300, () => {
return conditionalRegistration([
requireMinVersion(client, API.v300)
], () => {
const provider = new TypeScriptAutoFixProvider(client, fileConfigurationManager, diagnosticsManager);
return vscode.languages.registerCodeActionsProvider(selector, provider, provider.metadata);
});

View file

@ -8,7 +8,7 @@ import type * as Proto from '../protocol';
import { ITypeScriptServiceClient } from '../typescriptService';
import API from '../utils/api';
import { coalesce } from '../utils/arrays';
import { VersionDependentRegistration } from '../utils/dependentRegistration';
import { conditionalRegistration, requireMinVersion } from '../utils/dependentRegistration';
import * as typeConverters from '../utils/typeConverters';
class TypeScriptFoldingProvider implements vscode.FoldingRangeProvider {
@ -76,7 +76,9 @@ export function register(
selector: vscode.DocumentSelector,
client: ITypeScriptServiceClient,
): vscode.Disposable {
return new VersionDependentRegistration(client, TypeScriptFoldingProvider.minVersion, () => {
return conditionalRegistration([
requireMinVersion(client, TypeScriptFoldingProvider.minVersion),
], () => {
return vscode.languages.registerFoldingRangeProvider(selector,
new TypeScriptFoldingProvider(client));
});

View file

@ -6,7 +6,7 @@
import * as vscode from 'vscode';
import type * as Proto from '../protocol';
import { ITypeScriptServiceClient } from '../typescriptService';
import { ConfigurationDependentRegistration } from '../utils/dependentRegistration';
import { conditionalRegistration, requireConfiguration } from '../utils/dependentRegistration';
import * as typeConverters from '../utils/typeConverters';
import FileConfigurationManager from './fileConfigurationManager';
@ -89,7 +89,9 @@ export function register(
client: ITypeScriptServiceClient,
fileConfigurationManager: FileConfigurationManager
) {
return new ConfigurationDependentRegistration(modeId, 'format.enable', () => {
return conditionalRegistration([
requireConfiguration(modeId, 'format.enable'),
], () => {
const formattingProvider = new TypeScriptFormattingProvider(client, fileConfigurationManager);
return vscode.Disposable.from(
vscode.languages.registerOnTypeFormattingEditProvider(selector, formattingProvider, ';', '}', '\n'),

View file

@ -8,7 +8,7 @@ import * as nls from 'vscode-nls';
import type * as Proto from '../protocol';
import * as PConst from '../protocol.const';
import { ITypeScriptServiceClient } from '../typescriptService';
import { ConfigurationDependentRegistration } from '../utils/dependentRegistration';
import { conditionalRegistration, requireConfiguration } from '../utils/dependentRegistration';
import { TypeScriptBaseCodeLensProvider, ReferencesCodeLens, getSymbolRange } from './baseCodeLensProvider';
import { CachedResponse } from '../tsServer/cachedResponse';
import * as typeConverters from '../utils/typeConverters';
@ -94,7 +94,9 @@ export function register(
client: ITypeScriptServiceClient,
cachedResponse: CachedResponse<Proto.NavTreeResponse>,
) {
return new ConfigurationDependentRegistration(modeId, 'implementationsCodeLens.enabled', () => {
return conditionalRegistration([
requireConfiguration(modeId, 'implementationsCodeLens.enabled'),
], () => {
return vscode.languages.registerCodeLensProvider(selector,
new TypeScriptImplementationsCodeLensProvider(client, cachedResponse));
});

View file

@ -6,7 +6,7 @@
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { ITypeScriptServiceClient } from '../typescriptService';
import { ConfigurationDependentRegistration } from '../utils/dependentRegistration';
import { conditionalRegistration, requireConfiguration } from '../utils/dependentRegistration';
import * as typeConverters from '../utils/typeConverters';
@ -114,7 +114,9 @@ export function register(
modeId: string,
client: ITypeScriptServiceClient,
): vscode.Disposable {
return new ConfigurationDependentRegistration(modeId, 'suggest.completeJSDocs', () => {
return conditionalRegistration([
requireConfiguration(modeId, 'suggest.completeJSDocs')
], () => {
return vscode.languages.registerCompletionItemProvider(selector,
new JsDocCompletionProvider(client),
'*');

View file

@ -8,12 +8,12 @@ import * as nls from 'vscode-nls';
import type * as Proto from '../protocol';
import { ITypeScriptServiceClient } from '../typescriptService';
import API from '../utils/api';
import { nulToken } from '../utils/cancellation';
import { Command, CommandManager } from '../utils/commandManager';
import { VersionDependentRegistration } from '../utils/dependentRegistration';
import { conditionalRegistration, requireMinVersion } from '../utils/dependentRegistration';
import { TelemetryReporter } from '../utils/telemetry';
import * as typeconverts from '../utils/typeConverters';
import FileConfigurationManager from './fileConfigurationManager';
import { TelemetryReporter } from '../utils/telemetry';
import { nulToken } from '../utils/cancellation';
const localize = nls.loadMessageBundle();
@ -105,7 +105,9 @@ export function register(
fileConfigurationManager: FileConfigurationManager,
telemetryReporter: TelemetryReporter,
) {
return new VersionDependentRegistration(client, OrganizeImportsCodeActionProvider.minVersion, () => {
return conditionalRegistration([
requireMinVersion(client, OrganizeImportsCodeActionProvider.minVersion)
], () => {
const organizeImportsProvider = new OrganizeImportsCodeActionProvider(client, commandManager, fileConfigurationManager, telemetryReporter);
return vscode.languages.registerCodeActionsProvider(selector,
organizeImportsProvider,

View file

@ -11,7 +11,7 @@ import { ITypeScriptServiceClient } from '../typescriptService';
import API from '../utils/api';
import { nulToken } from '../utils/cancellation';
import { Command, CommandManager } from '../utils/commandManager';
import { VersionDependentRegistration } from '../utils/dependentRegistration';
import { conditionalRegistration, requireMinVersion } from '../utils/dependentRegistration';
import * as fileSchemes from '../utils/fileSchemes';
import { TelemetryReporter } from '../utils/telemetry';
import * as typeConverters from '../utils/typeConverters';
@ -402,7 +402,9 @@ export function register(
commandManager: CommandManager,
telemetryReporter: TelemetryReporter,
) {
return new VersionDependentRegistration(client, TypeScriptRefactorProvider.minVersion, () => {
return conditionalRegistration([
requireMinVersion(client, TypeScriptRefactorProvider.minVersion)
], () => {
return vscode.languages.registerCodeActionsProvider(selector,
new TypeScriptRefactorProvider(client, formattingOptionsManager, commandManager, telemetryReporter),
TypeScriptRefactorProvider.metadata);

View file

@ -9,7 +9,7 @@ import type * as Proto from '../protocol';
import * as PConst from '../protocol.const';
import { CachedResponse } from '../tsServer/cachedResponse';
import { ITypeScriptServiceClient } from '../typescriptService';
import { ConfigurationDependentRegistration } from '../utils/dependentRegistration';
import { conditionalRegistration, requireConfiguration } from '../utils/dependentRegistration';
import * as typeConverters from '../utils/typeConverters';
import { getSymbolRange, ReferencesCodeLens, TypeScriptBaseCodeLensProvider } from './baseCodeLensProvider';
@ -127,7 +127,9 @@ export function register(
client: ITypeScriptServiceClient,
cachedResponse: CachedResponse<Proto.NavTreeResponse>,
) {
return new ConfigurationDependentRegistration(modeId, 'referencesCodeLens.enabled', () => {
return conditionalRegistration([
requireConfiguration(modeId, 'referencesCodeLens.enabled')
], () => {
return vscode.languages.registerCodeLensProvider(selector,
new TypeScriptReferencesCodeLensProvider(client, cachedResponse, modeId));
});

View file

@ -3,14 +3,14 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { ITypeScriptServiceClient, ExecConfig, ServerResponse } from '../typescriptService';
import * as Proto from '../protocol';
import { VersionDependentRegistration } from '../utils/dependentRegistration';
import API from '../utils/api';
// all constants are const
import { TokenType, TokenModifier, TokenEncodingConsts, VersionRequirement } from 'typescript-vscode-sh-plugin/lib/constants';
import { TokenEncodingConsts, TokenModifier, TokenType, VersionRequirement } from 'typescript-vscode-sh-plugin/lib/constants';
import * as vscode from 'vscode';
import * as Proto from '../protocol';
import { ExecConfig, ITypeScriptServiceClient, ServerResponse } from '../typescriptService';
import API from '../utils/api';
import { conditionalRegistration, requireMinVersion } from '../utils/dependentRegistration';
const minTypeScriptVersion = API.fromVersionString(`${VersionRequirement.major}.${VersionRequirement.minor}`);
@ -18,7 +18,9 @@ const minTypeScriptVersion = API.fromVersionString(`${VersionRequirement.major}.
const CONTENT_LENGTH_LIMIT = 100000;
export function register(selector: vscode.DocumentSelector, client: ITypeScriptServiceClient) {
return new VersionDependentRegistration(client, minTypeScriptVersion, () => {
return conditionalRegistration([
requireMinVersion(client, minTypeScriptVersion),
], () => {
const provider = new DocumentSemanticTokensProvider(client);
return vscode.Disposable.from(
// register only as a range provider

View file

@ -7,7 +7,7 @@ import * as vscode from 'vscode';
import type * as Proto from '../protocol';
import { ITypeScriptServiceClient } from '../typescriptService';
import API from '../utils/api';
import { VersionDependentRegistration } from '../utils/dependentRegistration';
import { conditionalRegistration, requireMinVersion } from '../utils/dependentRegistration';
import * as typeConverters from '../utils/typeConverters';
class SmartSelection implements vscode.SelectionRangeProvider {
@ -52,6 +52,9 @@ export function register(
selector: vscode.DocumentSelector,
client: ITypeScriptServiceClient,
) {
return new VersionDependentRegistration(client, SmartSelection.minVersion, () =>
vscode.languages.registerSelectionRangeProvider(selector, new SmartSelection(client)));
return conditionalRegistration([
requireMinVersion(client, SmartSelection.minVersion),
], () => {
return vscode.languages.registerSelectionRangeProvider(selector, new SmartSelection(client));
});
}

View file

@ -7,7 +7,7 @@ import * as vscode from 'vscode';
import type * as Proto from '../protocol';
import { ITypeScriptServiceClient } from '../typescriptService';
import API from '../utils/api';
import { ConditionalRegistration, ConfigurationDependentRegistration, VersionDependentRegistration } from '../utils/dependentRegistration';
import { conditionalRegistration, requireMinVersion, requireConfiguration, Condition } from '../utils/dependentRegistration';
import { Disposable } from '../utils/dispose';
import * as typeConverters from '../utils/typeConverters';
@ -135,32 +135,19 @@ class TagClosing extends Disposable {
}
}
export class ActiveDocumentDependentRegistration extends Disposable {
private readonly _registration: ConditionalRegistration;
constructor(
private readonly selector: vscode.DocumentSelector,
register: () => vscode.Disposable,
) {
super();
this._registration = this._register(new ConditionalRegistration(register));
vscode.window.onDidChangeActiveTextEditor(this.update, this, this._disposables);
vscode.workspace.onDidOpenTextDocument(this.onDidOpenDocument, this, this._disposables);
this.update();
}
private update() {
const editor = vscode.window.activeTextEditor;
const enabled = !!(editor && vscode.languages.match(this.selector, editor.document));
this._registration.update(enabled);
}
private onDidOpenDocument(openedDocument: vscode.TextDocument) {
if (vscode.window.activeTextEditor && vscode.window.activeTextEditor.document === openedDocument) {
// The active document's language may have changed
this.update();
}
}
function requireActiveDocument(
selector: vscode.DocumentSelector
) {
return new Condition(
() => {
const editor = vscode.window.activeTextEditor;
return !!(editor && vscode.languages.match(selector, editor.document));
},
handler => {
return vscode.Disposable.from(
vscode.window.onDidChangeActiveTextEditor(handler),
vscode.workspace.onDidOpenTextDocument(handler));
});
}
export function register(
@ -168,8 +155,9 @@ export function register(
modeId: string,
client: ITypeScriptServiceClient,
) {
return new VersionDependentRegistration(client, TagClosing.minVersion, () =>
new ConfigurationDependentRegistration(modeId, 'autoClosingTags', () =>
new ActiveDocumentDependentRegistration(selector, () =>
new TagClosing(client))));
return conditionalRegistration([
requireMinVersion(client, TagClosing.minVersion),
requireConfiguration(modeId, 'autoClosingTags'),
requireActiveDocument(selector)
], () => new TagClosing(client));
}

View file

@ -11,7 +11,7 @@ import { ITypeScriptServiceClient } from '../typescriptService';
import API from '../utils/api';
import { Delayer } from '../utils/async';
import { nulToken } from '../utils/cancellation';
import { VersionDependentRegistration } from '../utils/dependentRegistration';
import { conditionalRegistration, requireMinVersion } from '../utils/dependentRegistration';
import { Disposable } from '../utils/dispose';
import * as fileSchemes from '../utils/fileSchemes';
import { doesResourceLookLikeATypeScriptFile } from '../utils/languageDescription';
@ -294,6 +294,9 @@ export function register(
fileConfigurationManager: FileConfigurationManager,
handles: (uri: vscode.Uri) => Promise<boolean>,
) {
return new VersionDependentRegistration(client, UpdateImportsOnFileRenameHandler.minVersion, () =>
new UpdateImportsOnFileRenameHandler(client, fileConfigurationManager, handles));
return conditionalRegistration([
requireMinVersion(client, UpdateImportsOnFileRenameHandler.minVersion),
], () => {
return new UpdateImportsOnFileRenameHandler(client, fileConfigurationManager, handles);
});
}

View file

@ -8,24 +8,54 @@ import { ITypeScriptServiceClient } from '../typescriptService';
import API from './api';
import { Disposable } from './dispose';
export class ConditionalRegistration {
export class Condition extends Disposable {
private _value: boolean;
constructor(
private readonly getValue: () => boolean,
onUpdate: (handler: () => void) => void,
) {
super();
this._value = this.getValue();
onUpdate(() => {
const newValue = this.getValue();
if (newValue !== this._value) {
this._value = newValue;
this._onDidChange.fire();
}
});
}
public get value(): boolean { return this._value; }
private readonly _onDidChange = this._register(new vscode.EventEmitter<void>());
public readonly onDidChange = this._onDidChange.event;
}
class ConditionalRegistration {
private registration: vscode.Disposable | undefined = undefined;
public constructor(
private readonly _doRegister: () => vscode.Disposable
) { }
public dispose() {
if (this.registration) {
this.registration.dispose();
this.registration = undefined;
private readonly conditions: readonly Condition[],
private readonly doRegister: () => vscode.Disposable
) {
for (const condition of conditions) {
condition.onDidChange(() => this.update());
}
this.update();
}
public update(enabled: boolean) {
public dispose() {
this.registration?.dispose();
this.registration = undefined;
}
private update() {
const enabled = this.conditions.every(condition => condition.value);
if (enabled) {
if (!this.registration) {
this.registration = this._doRegister();
this.registration = this.doRegister();
}
} else {
if (this.registration) {
@ -36,49 +66,32 @@ export class ConditionalRegistration {
}
}
export class VersionDependentRegistration extends Disposable {
private readonly _registration: ConditionalRegistration;
constructor(
private readonly client: ITypeScriptServiceClient,
private readonly minVersion: API,
register: () => vscode.Disposable,
) {
super();
this._registration = this._register(new ConditionalRegistration(register));
this._register(this.client.onTsServerStarted(() => {
this.update(this.client.apiVersion);
}));
this.update(client.apiVersion);
}
private update(api: API) {
this._registration.update(api.gte(this.minVersion));
}
export function conditionalRegistration(
conditions: readonly Condition[],
doRegister: () => vscode.Disposable,
): vscode.Disposable {
return new ConditionalRegistration(conditions, doRegister);
}
export class ConfigurationDependentRegistration extends Disposable {
private readonly _registration: ConditionalRegistration;
constructor(
private readonly language: string,
private readonly configValue: string,
register: () => vscode.Disposable,
) {
super();
this._registration = this._register(new ConditionalRegistration(register));
this._register(vscode.workspace.onDidChangeConfiguration(this.update, this));
this.update();
}
private update() {
const config = vscode.workspace.getConfiguration(this.language, null);
this._registration.update(!!config.get<boolean>(this.configValue));
}
export function requireMinVersion(
client: ITypeScriptServiceClient,
minVersion: API,
) {
return new Condition(
() => client.apiVersion.gte(minVersion),
client.onTsServerStarted
);
}
export function requireConfiguration(
language: string,
configValue: string,
) {
return new Condition(
() => {
const config = vscode.workspace.getConfiguration(language, null);
return !!config.get<boolean>(configValue);
},
vscode.workspace.onDidChangeConfiguration
);
}