Skip to content

Commit

Permalink
adopt request delays in code lense, also modernize code lens. fixes #…
Browse files Browse the repository at this point in the history
  • Loading branch information
jrieken committed Sep 8, 2020
1 parent 4a801b9 commit 89a2e78
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 72 deletions.
4 changes: 4 additions & 0 deletions src/vs/editor/common/modes/languageFeatureRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,10 @@ export class LanguageFeatureRegistry<T> {
}


/**
* Keeps moving average per model and set of providers so that requests
* can be debounce according to the provider performance
*/
export class LanguageFeatureRequestDelays {

private readonly _cache = new LRUCache<string, MovingAverage>(50, 0.7);
Expand Down
146 changes: 74 additions & 72 deletions src/vs/editor/contrib/codelens/codelensController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { CancelablePromise, RunOnceScheduler, createCancelablePromise, disposableTimeout } from 'vs/base/common/async';
import { onUnexpectedError, onUnexpectedExternalError } from 'vs/base/common/errors';
import { toDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle';
import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { StableEditorScrollState } from 'vs/editor/browser/core/editorState';
import { ICodeEditor, MouseTargetType, IViewZoneChangeAccessor, IActiveCodeEditor } from 'vs/editor/browser/editorBrowser';
import { registerEditorContribution, ServicesAccessor, registerEditorAction, EditorAction } from 'vs/editor/browser/editorExtensions';
Expand All @@ -23,48 +23,44 @@ import { hash } from 'vs/base/common/hash';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { localize } from 'vs/nls';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { LanguageFeatureRequestDelays } from 'vs/editor/common/modes/languageFeatureRegistry';

export class CodeLensContribution implements IEditorContribution {

public static readonly ID: string = 'css.editor.codeLens';
static readonly ID: string = 'css.editor.codeLens';

private _isEnabled: boolean;

private readonly _globalToDispose = new DisposableStore();
private readonly _disposables = new DisposableStore();
private readonly _localToDispose = new DisposableStore();
private readonly _styleElement: HTMLStyleElement;
private readonly _styleClassName: string;
private _lenses: CodeLensWidget[] = [];
private _currentGetCodeLensModel: CancelablePromise<CodeLensModel> | undefined;
private readonly _lenses: CodeLensWidget[] = [];

private readonly _getCodeLensModelDelays = new LanguageFeatureRequestDelays(CodeLensProviderRegistry, 250, 2500);
private _getCodeLensModelPromise: CancelablePromise<CodeLensModel> | undefined;
private _oldCodeLensModels = new DisposableStore();
private _currentCodeLensModel: CodeLensModel | undefined;
private _modelChangeCounter: number = 0;
private _currentResolveCodeLensSymbolsPromise: CancelablePromise<any> | undefined;
private _detectVisibleLenses: RunOnceScheduler | undefined;
private readonly _resolveCodeLensesDelays = new LanguageFeatureRequestDelays(CodeLensProviderRegistry, 250, 2500);
private readonly _resolveCodeLensesScheduler = new RunOnceScheduler(() => this._resolveCodeLensesInViewport(), this._resolveCodeLensesDelays.min);
private _resolveCodeLensesPromise: CancelablePromise<any> | undefined;

constructor(
private readonly _editor: ICodeEditor,
@ICommandService private readonly _commandService: ICommandService,
@INotificationService private readonly _notificationService: INotificationService,
@ICodeLensCache private readonly _codeLensCache: ICodeLensCache
) {
this._isEnabled = this._editor.getOption(EditorOption.codeLens);

this._globalToDispose.add(this._editor.onDidChangeModel(() => this._onModelChange()));
this._globalToDispose.add(this._editor.onDidChangeModelLanguage(() => this._onModelChange()));
this._globalToDispose.add(this._editor.onDidChangeConfiguration(() => {
const prevIsEnabled = this._isEnabled;
this._isEnabled = this._editor.getOption(EditorOption.codeLens);
if (prevIsEnabled !== this._isEnabled) {
this._onModelChange();
}
}));
this._globalToDispose.add(CodeLensProviderRegistry.onDidChange(this._onModelChange, this));
this._globalToDispose.add(this._editor.onDidChangeConfiguration(e => {

this._disposables.add(this._editor.onDidChangeModel(() => this._onModelChange()));
this._disposables.add(this._editor.onDidChangeModelLanguage(() => this._onModelChange()));
this._disposables.add(this._editor.onDidChangeConfiguration((e) => {
if (e.hasChanged(EditorOption.fontInfo)) {
this._updateLensStyle();
}
if (e.hasChanged(EditorOption.codeLens)) {
this._onModelChange();
}
}));
this._disposables.add(CodeLensProviderRegistry.onDidChange(this._onModelChange, this));
this._onModelChange();

this._styleClassName = '_' + hash(this._editor.getId()).toString(16);
Expand All @@ -78,7 +74,7 @@ export class CodeLensContribution implements IEditorContribution {

dispose(): void {
this._localDispose();
this._globalToDispose.dispose();
this._disposables.dispose();
this._oldCodeLensModels.dispose();
this._currentCodeLensModel?.dispose();
}
Expand All @@ -99,18 +95,13 @@ export class CodeLensContribution implements IEditorContribution {
}

private _localDispose(): void {
if (this._currentGetCodeLensModel) {
this._currentGetCodeLensModel.cancel();
this._currentGetCodeLensModel = undefined;
this._modelChangeCounter++;
}
if (this._currentResolveCodeLensSymbolsPromise) {
this._currentResolveCodeLensSymbolsPromise.cancel();
this._currentResolveCodeLensSymbolsPromise = undefined;
}
this._getCodeLensModelPromise?.cancel();
this._getCodeLensModelPromise = undefined;
this._resolveCodeLensesPromise?.cancel();
this._resolveCodeLensesPromise = undefined;
this._localToDispose.clear();
this._oldCodeLensModels.clear();
dispose(this._currentCodeLensModel);
this._currentCodeLensModel?.dispose();
}

private _onModelChange(): void {
Expand All @@ -122,7 +113,7 @@ export class CodeLensContribution implements IEditorContribution {
return;
}

if (!this._isEnabled) {
if (!this._editor.getOption(EditorOption.codeLens)) {
return;
}

Expand Down Expand Up @@ -153,34 +144,33 @@ export class CodeLensContribution implements IEditorContribution {
}
}

const detectVisibleLenses = this._detectVisibleLenses = new RunOnceScheduler(() => this._onViewportChanged(), 250);

const scheduler = new RunOnceScheduler(() => {
const counterValue = ++this._modelChangeCounter;
if (this._currentGetCodeLensModel) {
this._currentGetCodeLensModel.cancel();
}
const t1 = Date.now();

this._currentGetCodeLensModel = createCancelablePromise(token => getCodeLensModel(model, token));
this._getCodeLensModelPromise?.cancel();
this._getCodeLensModelPromise = createCancelablePromise(token => getCodeLensModel(model, token));

this._currentGetCodeLensModel.then(result => {
if (counterValue === this._modelChangeCounter) { // only the last one wins
if (this._currentCodeLensModel) {
this._oldCodeLensModels.add(this._currentCodeLensModel);
}
this._currentCodeLensModel = result;
this._getCodeLensModelPromise.then(result => {
if (this._currentCodeLensModel) {
this._oldCodeLensModels.add(this._currentCodeLensModel);
}
this._currentCodeLensModel = result;

// cache model to reduce flicker
this._codeLensCache.put(model, result);
// cache model to reduce flicker
this._codeLensCache.put(model, result);

// render lenses
this._renderCodeLensSymbols(result);
detectVisibleLenses.schedule();
}
// update moving average
this._getCodeLensModelDelays.update(model, Date.now() - t1);

// render lenses
this._renderCodeLensSymbols(result);
this._resolveCodeLensesInViewportSoon();
}, onUnexpectedError);
}, 250);

}, this._getCodeLensModelDelays.get(model));

this._localToDispose.add(scheduler);
this._localToDispose.add(detectVisibleLenses);
this._localToDispose.add(toDisposable(() => this._resolveCodeLensesScheduler.cancel()));
this._localToDispose.add(this._editor.onDidChangeModelContent(() => {
this._editor.changeDecorations(decorationsAccessor => {
this._editor.changeViewZones(viewZonesAccessor => {
Expand Down Expand Up @@ -209,17 +199,17 @@ export class CodeLensContribution implements IEditorContribution {
});

// Compute new `visible` code lenses
detectVisibleLenses.schedule();
this._resolveCodeLensesInViewportSoon();
// Ask for all references again
scheduler.schedule();
}));
this._localToDispose.add(this._editor.onDidScrollChange(e => {
if (e.scrollTopChanged && this._lenses.length > 0) {
detectVisibleLenses.schedule();
this._resolveCodeLensesInViewportSoon();
}
}));
this._localToDispose.add(this._editor.onDidLayoutChange(() => {
detectVisibleLenses.schedule();
this._resolveCodeLensesInViewportSoon();
}));
this._localToDispose.add(toDisposable(() => {
if (this._editor.getModel()) {
Expand Down Expand Up @@ -264,7 +254,7 @@ export class CodeLensContribution implements IEditorContribution {
if (decChangeAccessor) {
helper.commit(decChangeAccessor);
}
this._lenses = [];
this._lenses.length = 0;
}

private _renderCodeLensSymbols(symbols: CodeLensModel): void {
Expand Down Expand Up @@ -313,7 +303,7 @@ export class CodeLensContribution implements IEditorContribution {
groupsIndex++;
codeLensIndex++;
} else {
this._lenses.splice(codeLensIndex, 0, new CodeLensWidget(groups[groupsIndex], <IActiveCodeEditor>this._editor, this._styleClassName, helper, viewZoneAccessor, () => this._detectVisibleLenses && this._detectVisibleLenses.schedule()));
this._lenses.splice(codeLensIndex, 0, new CodeLensWidget(groups[groupsIndex], <IActiveCodeEditor>this._editor, this._styleClassName, helper, viewZoneAccessor, () => this._resolveCodeLensesInViewportSoon()));
codeLensIndex++;
groupsIndex++;
}
Expand All @@ -327,7 +317,7 @@ export class CodeLensContribution implements IEditorContribution {

// Create extra symbols
while (groupsIndex < groups.length) {
this._lenses.push(new CodeLensWidget(groups[groupsIndex], <IActiveCodeEditor>this._editor, this._styleClassName, helper, viewZoneAccessor, () => this._detectVisibleLenses && this._detectVisibleLenses.schedule()));
this._lenses.push(new CodeLensWidget(groups[groupsIndex], <IActiveCodeEditor>this._editor, this._styleClassName, helper, viewZoneAccessor, () => this._resolveCodeLensesInViewportSoon()));
groupsIndex++;
}

Expand All @@ -338,11 +328,17 @@ export class CodeLensContribution implements IEditorContribution {
scrollState.restore(this._editor);
}

private _onViewportChanged(): void {
if (this._currentResolveCodeLensSymbolsPromise) {
this._currentResolveCodeLensSymbolsPromise.cancel();
this._currentResolveCodeLensSymbolsPromise = undefined;
private _resolveCodeLensesInViewportSoon(): void {
const model = this._editor.getModel();
if (model) {
this._resolveCodeLensesScheduler.schedule(this._resolveCodeLensesDelays.get(model));
}
}

private _resolveCodeLensesInViewport(): void {

this._resolveCodeLensesPromise?.cancel();
this._resolveCodeLensesPromise = undefined;

const model = this._editor.getModel();
if (!model) {
Expand All @@ -363,6 +359,8 @@ export class CodeLensContribution implements IEditorContribution {
return;
}

const t1 = Date.now();

const resolvePromise = createCancelablePromise(token => {

const promises = toResolve.map((request, i) => {
Expand All @@ -388,20 +386,24 @@ export class CodeLensContribution implements IEditorContribution {

return Promise.all(promises);
});
this._currentResolveCodeLensSymbolsPromise = resolvePromise;
this._resolveCodeLensesPromise = resolvePromise;

this._resolveCodeLensesPromise.then(() => {

// update moving average
this._resolveCodeLensesDelays.update(model, Date.now() - t1);

this._currentResolveCodeLensSymbolsPromise.then(() => {
if (this._currentCodeLensModel) { // update the cached state with new resolved items
this._codeLensCache.put(model, this._currentCodeLensModel);
}
this._oldCodeLensModels.clear(); // dispose old models once we have updated the UI with the current model
if (resolvePromise === this._currentResolveCodeLensSymbolsPromise) {
this._currentResolveCodeLensSymbolsPromise = undefined;
if (resolvePromise === this._resolveCodeLensesPromise) {
this._resolveCodeLensesPromise = undefined;
}
}, err => {
onUnexpectedError(err); // can also be cancellation!
if (resolvePromise === this._currentResolveCodeLensSymbolsPromise) {
this._currentResolveCodeLensSymbolsPromise = undefined;
if (resolvePromise === this._resolveCodeLensesPromise) {
this._resolveCodeLensesPromise = undefined;
}
});
}
Expand Down

0 comments on commit 89a2e78

Please sign in to comment.