Skip to content

Commit e4b6657

Browse files
authored
Allow editor contributions to mark themselves as instantiated on idle (microsoft#166065)
* Allow editor contributions to mark themselves as instantiated on idle For microsoft#164171 Editor contributions are currently created eagerly when editors are instantiated. In many cases however, the contribution is not needed instantly. A contributed widget for instance may only be shown after a user action, while some info that shows in the editor also does not need to be shown instantly as soon as the editor opens Contributions like those can be lazily created, but actually adopting lazy instantiation for individual contributions is a lot of work Instead this PR lets editor contributions tell VS Code that the entire contribution does not need to be eagerly created and can be instead on idle With `EditorContributionInstantiation.Idle`, the contribution will be created: - Either when the editor is idle - Or when the contribution is explicitly requested. Idle contributions cannot participate in saving and restoring of editor view states This change will make it easier to lazily create editor contributions and also lets us remove existing code that was added to make individual editor contributions lazy * Mark as optional * Addressing comments * IdleValue should throw again if there's ctor error
1 parent 3dd0aea commit e4b6657

File tree

7 files changed

+85
-57
lines changed

7 files changed

+85
-57
lines changed

src/vs/editor/browser/editorExtensions.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,24 @@ export type ServicesAccessor = InstantiationServicesAccessor;
3030
export type IEditorContributionCtor = IConstructorSignature<IEditorContribution, [ICodeEditor]>;
3131
export type IDiffEditorContributionCtor = IConstructorSignature<IDiffEditorContribution, [IDiffEditor]>;
3232

33+
export enum EditorContributionInstantiation {
34+
/**
35+
* The contribution is created eagerly when the {@linkcode ICodeEditor} is instantiated.
36+
*/
37+
Eager,
38+
39+
/**
40+
* The contribution is created on idle (or when explicitly requested).
41+
*
42+
* Idle contributions cannot participate in saving or restoring of view states.
43+
*/
44+
Idle,
45+
}
46+
3347
export interface IEditorContributionDescription {
34-
id: string;
35-
ctor: IEditorContributionCtor;
48+
readonly id: string;
49+
readonly ctor: IEditorContributionCtor;
50+
readonly instantiation: EditorContributionInstantiation;
3651
}
3752

3853
export interface IDiffEditorContributionDescription {
@@ -481,8 +496,8 @@ export function registerInstantiatedEditorAction(editorAction: EditorAction): vo
481496
EditorContributionRegistry.INSTANCE.registerEditorAction(editorAction);
482497
}
483498

484-
export function registerEditorContribution<Services extends BrandedService[]>(id: string, ctor: { new(editor: ICodeEditor, ...services: Services): IEditorContribution }): void {
485-
EditorContributionRegistry.INSTANCE.registerEditorContribution(id, ctor);
499+
export function registerEditorContribution<Services extends BrandedService[]>(id: string, ctor: { new(editor: ICodeEditor, ...services: Services): IEditorContribution }, instantiation = EditorContributionInstantiation.Eager): void {
500+
EditorContributionRegistry.INSTANCE.registerEditorContribution(id, ctor, instantiation);
486501
}
487502

488503
export function registerDiffEditorContribution<Services extends BrandedService[]>(id: string, ctor: { new(editor: IDiffEditor, ...services: Services): IEditorContribution }): void {
@@ -533,8 +548,8 @@ class EditorContributionRegistry {
533548
this.editorCommands = Object.create(null);
534549
}
535550

536-
public registerEditorContribution<Services extends BrandedService[]>(id: string, ctor: { new(editor: ICodeEditor, ...services: Services): IEditorContribution }): void {
537-
this.editorContributions.push({ id, ctor: ctor as IEditorContributionCtor });
551+
public registerEditorContribution<Services extends BrandedService[]>(id: string, ctor: { new(editor: ICodeEditor, ...services: Services): IEditorContribution }, instantiation = EditorContributionInstantiation.Eager): void {
552+
this.editorContributions.push({ id, ctor: ctor as IEditorContributionCtor, instantiation });
538553
}
539554

540555
public getEditorContributions(): IEditorContributionDescription[] {

src/vs/editor/browser/widget/codeEditorWidget.ts

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { Disposable, IDisposable, dispose, DisposableStore, DisposableMap } from
1818
import { Schemas } from 'vs/base/common/network';
1919
import { EditorConfiguration, IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration';
2020
import * as editorBrowser from 'vs/editor/browser/editorBrowser';
21-
import { EditorExtensionsRegistry, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions';
21+
import { EditorContributionInstantiation, EditorExtensionsRegistry, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions';
2222
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
2323
import { ICommandDelegate } from 'vs/editor/browser/view/viewController';
2424
import { IContentWidgetData, IOverlayWidgetData, View } from 'vs/editor/browser/view';
@@ -60,6 +60,7 @@ import { applyFontInfo } from 'vs/editor/browser/config/domFontInfo';
6060
import { IEditorConfiguration } from 'vs/editor/common/config/editorConfiguration';
6161
import { IDimension } from 'vs/editor/common/core/dimension';
6262
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
63+
import { IdleValue } from 'vs/base/common/async';
6364

6465
let EDITOR_ID = 0;
6566

@@ -241,7 +242,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
241242
private readonly _id: number;
242243
private readonly _configuration: IEditorConfiguration;
243244

244-
protected readonly _contributions = new DisposableMap<string, editorCommon.IEditorContribution>();
245+
protected readonly _contributions = new DisposableMap<string, editorCommon.IEditorContribution | IdleValue<editorCommon.IEditorContribution | null>>();
245246
protected readonly _actions = new Map<string, editorCommon.IEditorAction>();
246247

247248
// --- Members logically associated to a model
@@ -338,8 +339,25 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
338339
continue;
339340
}
340341
try {
341-
const contribution = this._instantiationService.createInstance(desc.ctor, this);
342-
this._contributions.set(desc.id, contribution);
342+
if (desc.instantiation === EditorContributionInstantiation.Idle) {
343+
const contribution = new IdleValue(() => {
344+
try {
345+
// Replace the original entry in _contributions with the resolved contribution
346+
const instance = this._instantiationService.createInstance(desc.ctor, this);
347+
this._contributions.set(desc.id, instance);
348+
return instance;
349+
} catch (err) {
350+
// In case of an exception, we delete the idle value from _contributions
351+
onUnexpectedError(err);
352+
this._contributions.deleteAndDispose(desc.id);
353+
return null;
354+
}
355+
});
356+
this._contributions.set(desc.id, contribution);
357+
} else {
358+
const contribution = this._instantiationService.createInstance(desc.ctor, this);
359+
this._contributions.set(desc.id, contribution);
360+
}
343361
} catch (err) {
344362
onUnexpectedError(err);
345363
}
@@ -993,7 +1011,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
9931011
const contributionsState: { [key: string]: any } = {};
9941012

9951013
for (const [id, contribution] of this._contributions) {
996-
if (typeof contribution.saveViewState === 'function') {
1014+
if (!(contribution instanceof IdleValue) && typeof contribution.saveViewState === 'function') {
9971015
contributionsState[id] = contribution.saveViewState();
9981016
}
9991017
}
@@ -1025,7 +1043,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
10251043

10261044
const contributionsState = codeEditorState.contributionsState || {};
10271045
for (const [id, contribution] of this._contributions) {
1028-
if (typeof contribution.restoreViewState === 'function') {
1046+
if (!(contribution instanceof IdleValue) && typeof contribution.restoreViewState === 'function') {
10291047
contribution.restoreViewState(contributionsState[id]);
10301048
}
10311049
}
@@ -1045,7 +1063,12 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
10451063
}
10461064

10471065
public getContribution<T extends editorCommon.IEditorContribution>(id: string): T | null {
1048-
return <T>(this._contributions.get(id) || null);
1066+
const entry = this._contributions.get(id);
1067+
if (!entry) {
1068+
return null;
1069+
}
1070+
1071+
return (entry instanceof IdleValue ? entry.value : entry) as T | null;
10491072
}
10501073

10511074
public getActions(): editorCommon.IEditorAction[] {

src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import { IMarkerService } from 'vs/platform/markers/common/markers';
2727
import { IEditorProgressService } from 'vs/platform/progress/common/progress';
2828
import { CodeActionModel, CodeActionsState, SUPPORTED_CODE_ACTIONS } from './codeActionModel';
2929
import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionFilter, CodeActionItem, CodeActionKind, CodeActionSet, CodeActionTrigger, CodeActionTriggerSource } from '../common/types';
30-
import { IdleValue } from 'vs/base/common/async';
3130

3231
function contextKeyForSupportedActions(kind: CodeActionKind) {
3332
return ContextKeyExpr.regex(
@@ -91,7 +90,7 @@ export class CodeActionController extends Disposable implements IEditorContribut
9190
}
9291

9392
private readonly _editor: ICodeEditor;
94-
private readonly _model: IdleValue<CodeActionModel>;
93+
private readonly _model: CodeActionModel;
9594
private readonly _ui: Lazy<CodeActionUi>;
9695

9796
constructor(
@@ -106,13 +105,9 @@ export class CodeActionController extends Disposable implements IEditorContribut
106105

107106
this._editor = editor;
108107

109-
this._model = this._register(new IdleValue(() => {
110-
const model = this._register(new CodeActionModel(this._editor, languageFeaturesService.codeActionProvider, markerService, contextKeyService, progressService));
108+
this._model = this._register(new CodeActionModel(this._editor, languageFeaturesService.codeActionProvider, markerService, contextKeyService, progressService));
111109

112-
this._register(model.onDidChangeState(newState => this.update(newState)));
113-
114-
return model;
115-
}));
110+
this._register(this._model.onDidChangeState(newState => this.update(newState)));
116111

117112
this._ui = new Lazy(() =>
118113
this._register(_instantiationService.createInstance(CodeActionUi, editor, QuickFixAction.Id, AutoFixAction.Id, {
@@ -154,7 +149,7 @@ export class CodeActionController extends Disposable implements IEditorContribut
154149
}
155150

156151
private _trigger(trigger: CodeActionTrigger) {
157-
return this._model.value.trigger(trigger);
152+
return this._model.trigger(trigger);
158153
}
159154

160155
private _applyCodeAction(action: CodeActionItem, preview: boolean): Promise<void> {

src/vs/editor/contrib/codeAction/browser/codeActionContributions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { registerEditorAction, registerEditorCommand, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
6+
import { EditorContributionInstantiation, registerEditorAction, registerEditorCommand, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
77
import { editorConfigurationBaseNode } from 'vs/editor/common/config/editorConfigurationSchema';
88
import { AutoFixAction, CodeActionCommand, CodeActionController, FixAllAction, OrganizeImportsAction, QuickFixAction, RefactorAction, RefactorPreview, SourceAction } from 'vs/editor/contrib/codeAction/browser/codeActionCommands';
99
import * as nls from 'vs/nls';
1010
import { ConfigurationScope, Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
1111
import { Registry } from 'vs/platform/registry/common/platform';
1212

13-
registerEditorContribution(CodeActionController.ID, CodeActionController);
13+
registerEditorContribution(CodeActionController.ID, CodeActionController, EditorContributionInstantiation.Idle);
1414
registerEditorAction(QuickFixAction);
1515
registerEditorAction(RefactorAction);
1616
registerEditorAction(RefactorPreview);

src/vs/editor/contrib/parameterHints/browser/parameterHints.ts

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { IdleValue } from 'vs/base/common/async';
76
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
87
import { Lazy } from 'vs/base/common/lazy';
98
import { Disposable } from 'vs/base/common/lifecycle';
109
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
11-
import { EditorAction, EditorCommand, registerEditorAction, registerEditorCommand, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
10+
import { EditorAction, EditorCommand, EditorContributionInstantiation, registerEditorAction, registerEditorCommand, registerEditorContribution, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
1211
import { IEditorContribution } from 'vs/editor/common/editorCommon';
1312
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
1413
import * as languages from 'vs/editor/common/languages';
@@ -30,7 +29,7 @@ class ParameterHintsController extends Disposable implements IEditorContribution
3029
}
3130

3231
private readonly editor: ICodeEditor;
33-
private readonly model: IdleValue<ParameterHintsModel>;
32+
private readonly model: ParameterHintsModel;
3433
private readonly widget: Lazy<ParameterHintsWidget>;
3534

3635
constructor(
@@ -42,26 +41,22 @@ class ParameterHintsController extends Disposable implements IEditorContribution
4241

4342
this.editor = editor;
4443

45-
this.model = this._register(new IdleValue(() => {
46-
const model = this._register(new ParameterHintsModel(editor, languageFeaturesService.signatureHelpProvider));
44+
this.model = this._register(new ParameterHintsModel(editor, languageFeaturesService.signatureHelpProvider));
4745

48-
this._register(model.onChangedHints(newParameterHints => {
49-
if (newParameterHints) {
50-
this.widget.getValue().show();
51-
this.widget.getValue().render(newParameterHints);
52-
} else {
53-
this.widget.rawValue?.hide();
54-
}
55-
}));
56-
57-
return model;
46+
this._register(this.model.onChangedHints(newParameterHints => {
47+
if (newParameterHints) {
48+
this.widget.getValue().show();
49+
this.widget.getValue().render(newParameterHints);
50+
} else {
51+
this.widget.rawValue?.hide();
52+
}
5853
}));
5954

60-
this.widget = new Lazy(() => this._register(instantiationService.createInstance(ParameterHintsWidget, this.editor, this.model.value)));
55+
this.widget = new Lazy(() => this._register(instantiationService.createInstance(ParameterHintsWidget, this.editor, this.model)));
6156
}
6257

6358
cancel(): void {
64-
this.model.value.cancel();
59+
this.model.cancel();
6560
}
6661

6762
previous(): void {
@@ -73,7 +68,7 @@ class ParameterHintsController extends Disposable implements IEditorContribution
7368
}
7469

7570
trigger(context: TriggerContext): void {
76-
this.model.value.trigger(context, 0);
71+
this.model.trigger(context, 0);
7772
}
7873
}
7974

@@ -101,7 +96,7 @@ export class TriggerParameterHintsAction extends EditorAction {
10196
}
10297
}
10398

104-
registerEditorContribution(ParameterHintsController.ID, ParameterHintsController);
99+
registerEditorContribution(ParameterHintsController.ID, ParameterHintsController, EditorContributionInstantiation.Idle);
105100
registerEditorAction(TriggerParameterHintsAction);
106101

107102
const weight = KeybindingWeight.EditorContrib + 75;

src/vs/editor/contrib/rename/browser/rename.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { alert } from 'vs/base/browser/ui/aria/aria';
7-
import { IdleValue, raceCancellation } from 'vs/base/common/async';
7+
import { raceCancellation } from 'vs/base/common/async';
88
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
99
import { onUnexpectedError } from 'vs/base/common/errors';
1010
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
@@ -13,7 +13,7 @@ import { assertType } from 'vs/base/common/types';
1313
import { URI } from 'vs/base/common/uri';
1414
import { CodeEditorStateFlag, EditorStateCancellationTokenSource } from 'vs/editor/contrib/editorState/browser/editorState';
1515
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
16-
import { EditorAction, EditorCommand, registerEditorAction, registerEditorCommand, registerEditorContribution, registerModelAndPositionCommand, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
16+
import { EditorAction, EditorCommand, EditorContributionInstantiation, registerEditorAction, registerEditorCommand, registerEditorContribution, registerModelAndPositionCommand, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
1717
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
1818
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
1919
import { IPosition, Position } from 'vs/editor/common/core/position';
@@ -131,7 +131,7 @@ class RenameController implements IEditorContribution {
131131
return editor.getContribution<RenameController>(RenameController.ID);
132132
}
133133

134-
private readonly _renameInputField: IdleValue<RenameInputField>;
134+
private readonly _renameInputField: RenameInputField;
135135
private readonly _disposableStore = new DisposableStore();
136136
private _cts: CancellationTokenSource = new CancellationTokenSource();
137137

@@ -145,7 +145,7 @@ class RenameController implements IEditorContribution {
145145
@ITextResourceConfigurationService private readonly _configService: ITextResourceConfigurationService,
146146
@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
147147
) {
148-
this._renameInputField = this._disposableStore.add(new IdleValue(() => this._disposableStore.add(this._instaService.createInstance(RenameInputField, this.editor, ['acceptRenameInput', 'acceptRenameInputWithPreview']))));
148+
this._renameInputField = this._disposableStore.add(this._instaService.createInstance(RenameInputField, this.editor, ['acceptRenameInput', 'acceptRenameInputWithPreview']));
149149
}
150150

151151
dispose(): void {
@@ -207,7 +207,7 @@ class RenameController implements IEditorContribution {
207207
}
208208

209209
const supportPreview = this._bulkEditService.hasPreviewHandler() && this._configService.getValue<boolean>(this.editor.getModel().uri, 'editor.rename.enablePreview');
210-
const inputFieldResult = await this._renameInputField.value.getInput(loc.range, loc.text, selectionStart, selectionEnd, supportPreview, this._cts.token);
210+
const inputFieldResult = await this._renameInputField.getInput(loc.range, loc.text, selectionStart, selectionEnd, supportPreview, this._cts.token);
211211

212212
// no result, only hint to focus the editor or not
213213
if (typeof inputFieldResult === 'boolean') {
@@ -260,11 +260,11 @@ class RenameController implements IEditorContribution {
260260
}
261261

262262
acceptRenameInput(wantsPreview: boolean): void {
263-
this._renameInputField.value.acceptInput(wantsPreview);
263+
this._renameInputField.acceptInput(wantsPreview);
264264
}
265265

266266
cancelRenameInput(): void {
267-
this._renameInputField.value.cancelInput(true);
267+
this._renameInputField.cancelInput(true);
268268
}
269269
}
270270

@@ -319,7 +319,7 @@ export class RenameAction extends EditorAction {
319319
}
320320
}
321321

322-
registerEditorContribution(RenameController.ID, RenameController);
322+
registerEditorContribution(RenameController.ID, RenameController, EditorContributionInstantiation.Idle);
323323
registerEditorAction(RenameAction);
324324

325325
const RenameCommand = EditorCommand.bindToContribution<RenameController>(RenameController.get);

src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
7-
import { EditorAction, EditorExtensionsRegistry, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions';
7+
import { EditorAction, EditorContributionInstantiation, EditorExtensionsRegistry, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions';
88
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
99
import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget';
1010
import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
@@ -51,11 +51,11 @@ export class SimpleCommentEditor extends CodeEditorWidget {
5151
const codeEditorWidgetOptions: ICodeEditorWidgetOptions = {
5252
isSimpleWidget: true,
5353
contributions: <IEditorContributionDescription[]>[
54-
{ id: MenuPreventer.ID, ctor: MenuPreventer },
55-
{ id: ContextMenuController.ID, ctor: ContextMenuController },
56-
{ id: SuggestController.ID, ctor: SuggestController },
57-
{ id: SnippetController2.ID, ctor: SnippetController2 },
58-
{ id: TabCompletionController.ID, ctor: TabCompletionController },
54+
{ id: MenuPreventer.ID, ctor: MenuPreventer, instantiation: EditorContributionInstantiation.Eager },
55+
{ id: ContextMenuController.ID, ctor: ContextMenuController, instantiation: EditorContributionInstantiation.Eager },
56+
{ id: SuggestController.ID, ctor: SuggestController, instantiation: EditorContributionInstantiation.Eager },
57+
{ id: SnippetController2.ID, ctor: SnippetController2, instantiation: EditorContributionInstantiation.Eager },
58+
{ id: TabCompletionController.ID, ctor: TabCompletionController, instantiation: EditorContributionInstantiation.Eager },
5959
]
6060
};
6161

0 commit comments

Comments
 (0)