提交 b4a00ca3 编写于 作者: M Matt Bierner

Replacing heapservice with lifecycle for code actions

Part of #74846

Code Actions can use commands internally, which must be disposed of. We were previously using the heap service for this but this will not work for the web. Add a custom lifecycle instead
上级 0aaf00bb
...@@ -550,6 +550,10 @@ export interface CodeActionContext { ...@@ -550,6 +550,10 @@ export interface CodeActionContext {
trigger: CodeActionTrigger; trigger: CodeActionTrigger;
} }
export interface CodeActionList extends IDisposable {
readonly actions: ReadonlyArray<CodeAction>;
}
/** /**
* The code action interface defines the contract between extensions and * The code action interface defines the contract between extensions and
* the [light bulb](https://code.visualstudio.com/docs/editor/editingevolved#_code-action) feature. * the [light bulb](https://code.visualstudio.com/docs/editor/editingevolved#_code-action) feature.
...@@ -559,7 +563,7 @@ export interface CodeActionProvider { ...@@ -559,7 +563,7 @@ export interface CodeActionProvider {
/** /**
* Provide commands for the given document and range. * Provide commands for the given document and range.
*/ */
provideCodeActions(model: model.ITextModel, range: Range | Selection, context: CodeActionContext, token: CancellationToken): ProviderResult<CodeAction[]>; provideCodeActions(model: model.ITextModel, range: Range | Selection, context: CodeActionContext, token: CancellationToken): ProviderResult<CodeActionList>;
/** /**
* Optional list of CodeActionKinds that this provider returns. * Optional list of CodeActionKinds that this provider returns.
......
...@@ -15,8 +15,9 @@ import { CodeAction, CodeActionContext, CodeActionProviderRegistry, CodeActionTr ...@@ -15,8 +15,9 @@ import { CodeAction, CodeActionContext, CodeActionProviderRegistry, CodeActionTr
import { IModelService } from 'vs/editor/common/services/modelService'; import { IModelService } from 'vs/editor/common/services/modelService';
import { CodeActionFilter, CodeActionKind, CodeActionTrigger, filtersAction, mayIncludeActionsOfKind } from './codeActionTrigger'; import { CodeActionFilter, CodeActionKind, CodeActionTrigger, filtersAction, mayIncludeActionsOfKind } from './codeActionTrigger';
import { TextModelCancellationTokenSource } from 'vs/editor/browser/core/editorState'; import { TextModelCancellationTokenSource } from 'vs/editor/browser/core/editorState';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
export class CodeActionSet { export class CodeActionSet extends Disposable {
private static codeActionsComparator(a: CodeAction, b: CodeAction): number { private static codeActionsComparator(a: CodeAction, b: CodeAction): number {
if (isNonEmptyArray(a.diagnostics)) { if (isNonEmptyArray(a.diagnostics)) {
...@@ -34,7 +35,9 @@ export class CodeActionSet { ...@@ -34,7 +35,9 @@ export class CodeActionSet {
public readonly actions: readonly CodeAction[]; public readonly actions: readonly CodeAction[];
public constructor(actions: readonly CodeAction[]) { public constructor(actions: readonly CodeAction[], disposables: DisposableStore) {
super();
this._register(disposables);
this.actions = mergeSort([...actions], CodeActionSet.codeActionsComparator); this.actions = mergeSort([...actions], CodeActionSet.codeActionsComparator);
} }
...@@ -59,12 +62,14 @@ export function getCodeActions( ...@@ -59,12 +62,14 @@ export function getCodeActions(
const cts = new TextModelCancellationTokenSource(model, token); const cts = new TextModelCancellationTokenSource(model, token);
const providers = getCodeActionProviders(model, filter); const providers = getCodeActionProviders(model, filter);
const disposables = new DisposableStore();
const promises = providers.map(provider => { const promises = providers.map(provider => {
return Promise.resolve(provider.provideCodeActions(model, rangeOrSelection, codeActionContext, cts.token)).then(providedCodeActions => { return Promise.resolve(provider.provideCodeActions(model, rangeOrSelection, codeActionContext, cts.token)).then(providedCodeActions => {
if (cts.token.isCancellationRequested || !Array.isArray(providedCodeActions)) { if (cts.token.isCancellationRequested || !providedCodeActions) {
return []; return [];
} }
return providedCodeActions.filter(action => action && filtersAction(filter, action)); disposables.add(providedCodeActions);
return providedCodeActions.actions.filter(action => action && filtersAction(filter, action));
}, (err): CodeAction[] => { }, (err): CodeAction[] => {
if (isPromiseCanceledError(err)) { if (isPromiseCanceledError(err)) {
throw err; throw err;
...@@ -84,7 +89,7 @@ export function getCodeActions( ...@@ -84,7 +89,7 @@ export function getCodeActions(
return Promise.all(promises) return Promise.all(promises)
.then(flatten) .then(flatten)
.then(actions => new CodeActionSet(actions)) .then(actions => new CodeActionSet(actions, disposables))
.finally(() => { .finally(() => {
listener.dispose(); listener.dispose();
cts.dispose(); cts.dispose();
...@@ -106,7 +111,7 @@ function getCodeActionProviders( ...@@ -106,7 +111,7 @@ function getCodeActionProviders(
}); });
} }
registerLanguageCommand('_executeCodeActionProvider', function (accessor, args): Promise<ReadonlyArray<CodeAction>> { registerLanguageCommand('_executeCodeActionProvider', async function (accessor, args): Promise<ReadonlyArray<CodeAction>> {
const { resource, range, kind } = args; const { resource, range, kind } = args;
if (!(resource instanceof URI) || !Range.isIRange(range)) { if (!(resource instanceof URI) || !Range.isIRange(range)) {
throw illegalArgument(); throw illegalArgument();
...@@ -117,9 +122,11 @@ registerLanguageCommand('_executeCodeActionProvider', function (accessor, args): ...@@ -117,9 +122,11 @@ registerLanguageCommand('_executeCodeActionProvider', function (accessor, args):
throw illegalArgument(); throw illegalArgument();
} }
return getCodeActions( const codeActionSet = await getCodeActions(
model, model,
model.validateRange(range), model.validateRange(range),
{ type: 'manual', filter: { includeSourceActions: true, kind: kind && kind.value ? new CodeActionKind(kind.value) : undefined } }, { type: 'manual', filter: { includeSourceActions: true, kind: kind && kind.value ? new CodeActionKind(kind.value) : undefined } },
CancellationToken.None).then(actions => actions.actions); CancellationToken.None);
codeActionSet.dispose();
return codeActionSet.actions;
}); });
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
import { CancelablePromise } from 'vs/base/common/async'; import { CancelablePromise } from 'vs/base/common/async';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { Disposable } from 'vs/base/common/lifecycle'; import { Disposable, dispose } from 'vs/base/common/lifecycle';
import { escapeRegExpCharacters } from 'vs/base/common/strings'; import { escapeRegExpCharacters } from 'vs/base/common/strings';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorAction, EditorCommand, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { EditorAction, EditorCommand, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
...@@ -47,6 +47,7 @@ export class QuickFixController extends Disposable implements IEditorContributio ...@@ -47,6 +47,7 @@ export class QuickFixController extends Disposable implements IEditorContributio
private readonly _model: CodeActionModel; private readonly _model: CodeActionModel;
private readonly _codeActionContextMenu: CodeActionContextMenu; private readonly _codeActionContextMenu: CodeActionContextMenu;
private readonly _lightBulbWidget: LightBulbWidget; private readonly _lightBulbWidget: LightBulbWidget;
private _currentCodeActions: CodeActionSet | undefined;
private _activeRequest: CancelablePromise<CodeActionSet> | undefined; private _activeRequest: CancelablePromise<CodeActionSet> | undefined;
...@@ -63,7 +64,7 @@ export class QuickFixController extends Disposable implements IEditorContributio ...@@ -63,7 +64,7 @@ export class QuickFixController extends Disposable implements IEditorContributio
super(); super();
this._editor = editor; this._editor = editor;
this._model = new CodeActionModel(this._editor, markerService, contextKeyService, progressService); this._model = this._register(new CodeActionModel(this._editor, markerService, contextKeyService, progressService));
this._codeActionContextMenu = this._register(new CodeActionContextMenu(editor, contextMenuService, action => this._onApplyCodeAction(action))); this._codeActionContextMenu = this._register(new CodeActionContextMenu(editor, contextMenuService, action => this._onApplyCodeAction(action)));
this._lightBulbWidget = this._register(new LightBulbWidget(editor)); this._lightBulbWidget = this._register(new LightBulbWidget(editor));
...@@ -75,9 +76,9 @@ export class QuickFixController extends Disposable implements IEditorContributio ...@@ -75,9 +76,9 @@ export class QuickFixController extends Disposable implements IEditorContributio
this._register(this._keybindingService.onDidUpdateKeybindings(this._updateLightBulbTitle, this)); this._register(this._keybindingService.onDidUpdateKeybindings(this._updateLightBulbTitle, this));
} }
public dispose(): void { dipose() {
super.dispose(); super.dispose();
this._model.dispose(); dispose(this._currentCodeActions);
} }
private _onDidChangeCodeActionsState(newState: CodeActionsState.State): void { private _onDidChangeCodeActionsState(newState: CodeActionsState.State): void {
...@@ -89,6 +90,11 @@ export class QuickFixController extends Disposable implements IEditorContributio ...@@ -89,6 +90,11 @@ export class QuickFixController extends Disposable implements IEditorContributio
if (newState.type === CodeActionsState.Type.Triggered) { if (newState.type === CodeActionsState.Type.Triggered) {
this._activeRequest = newState.actions; this._activeRequest = newState.actions;
newState.actions.then(actions => {
dispose(this._currentCodeActions);
this._currentCodeActions = actions;
});
if (newState.trigger.filter && newState.trigger.filter.kind) { if (newState.trigger.filter && newState.trigger.filter.kind) {
// Triggered for specific scope // Triggered for specific scope
newState.actions.then(fixes => { newState.actions.then(fixes => {
...@@ -115,6 +121,8 @@ export class QuickFixController extends Disposable implements IEditorContributio ...@@ -115,6 +121,8 @@ export class QuickFixController extends Disposable implements IEditorContributio
} }
} }
} else { } else {
dispose(this._currentCodeActions);
this._currentCodeActions = undefined;
this._lightBulbWidget.hide(); this._lightBulbWidget.hide();
} }
} }
......
...@@ -7,15 +7,27 @@ import { DisposableStore } from 'vs/base/common/lifecycle'; ...@@ -7,15 +7,27 @@ import { DisposableStore } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { Range } from 'vs/editor/common/core/range'; import { Range } from 'vs/editor/common/core/range';
import { TextModel } from 'vs/editor/common/model/textModel'; import { TextModel } from 'vs/editor/common/model/textModel';
import { CodeAction, CodeActionContext, CodeActionProvider, CodeActionProviderRegistry, Command, LanguageIdentifier, ResourceTextEdit, WorkspaceEdit } from 'vs/editor/common/modes'; import * as modes from 'vs/editor/common/modes';
import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction'; import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction';
import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger'; import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger';
import { IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers';
import { CancellationToken } from 'vs/base/common/cancellation'; import { CancellationToken } from 'vs/base/common/cancellation';
function staticCodeActionProvider(...actions: modes.CodeAction[]): modes.CodeActionProvider {
return new class implements modes.CodeActionProvider {
provideCodeActions(): modes.CodeActionList {
return {
actions: actions,
dispose: () => { }
};
}
};
}
suite('CodeAction', () => { suite('CodeAction', () => {
let langId = new LanguageIdentifier('fooLang', 17); let langId = new modes.LanguageIdentifier('fooLang', 17);
let uri = URI.parse('untitled:path'); let uri = URI.parse('untitled:path');
let model: TextModel; let model: TextModel;
const disposables = new DisposableStore(); const disposables = new DisposableStore();
...@@ -46,7 +58,7 @@ suite('CodeAction', () => { ...@@ -46,7 +58,7 @@ suite('CodeAction', () => {
}, },
command: { command: {
abc: { abc: {
command: new class implements Command { command: new class implements modes.Command {
id: '1'; id: '1';
title: 'abc'; title: 'abc';
}, },
...@@ -56,8 +68,8 @@ suite('CodeAction', () => { ...@@ -56,8 +68,8 @@ suite('CodeAction', () => {
spelling: { spelling: {
bcd: { bcd: {
diagnostics: <IMarkerData[]>[], diagnostics: <IMarkerData[]>[],
edit: new class implements WorkspaceEdit { edit: new class implements modes.WorkspaceEdit {
edits: ResourceTextEdit[]; edits: modes.ResourceTextEdit[];
}, },
title: 'abc' title: 'abc'
} }
...@@ -90,20 +102,16 @@ suite('CodeAction', () => { ...@@ -90,20 +102,16 @@ suite('CodeAction', () => {
test('CodeActions are sorted by type, #38623', async function () { test('CodeActions are sorted by type, #38623', async function () {
const provider = new class implements CodeActionProvider { const provider = staticCodeActionProvider(
provideCodeActions() { testData.command.abc,
return [ testData.diagnostics.bcd,
testData.command.abc, testData.spelling.bcd,
testData.diagnostics.bcd, testData.tsLint.bcd,
testData.spelling.bcd, testData.tsLint.abc,
testData.tsLint.bcd, testData.diagnostics.abc
testData.tsLint.abc, );
testData.diagnostics.abc
];
}
};
disposables.add(CodeActionProviderRegistry.register('fooLang', provider)); disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider));
const expected = [ const expected = [
// CodeActions with a diagnostics array are shown first ordered by diagnostics.message // CodeActions with a diagnostics array are shown first ordered by diagnostics.message
...@@ -123,17 +131,13 @@ suite('CodeAction', () => { ...@@ -123,17 +131,13 @@ suite('CodeAction', () => {
}); });
test('getCodeActions should filter by scope', async function () { test('getCodeActions should filter by scope', async function () {
const provider = new class implements CodeActionProvider { const provider = staticCodeActionProvider(
provideCodeActions(): CodeAction[] { { title: 'a', kind: 'a' },
return [ { title: 'b', kind: 'b' },
{ title: 'a', kind: 'a' }, { title: 'a.b', kind: 'a.b' }
{ title: 'b', kind: 'b' }, );
{ title: 'a.b', kind: 'a.b' }
];
}
};
disposables.add(CodeActionProviderRegistry.register('fooLang', provider)); disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider));
{ {
const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a') } }, CancellationToken.None); const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a') } }, CancellationToken.None);
...@@ -155,15 +159,18 @@ suite('CodeAction', () => { ...@@ -155,15 +159,18 @@ suite('CodeAction', () => {
}); });
test('getCodeActions should forward requested scope to providers', async function () { test('getCodeActions should forward requested scope to providers', async function () {
const provider = new class implements CodeActionProvider { const provider = new class implements modes.CodeActionProvider {
provideCodeActions(_model: any, _range: Range, context: CodeActionContext, _token: any): CodeAction[] { provideCodeActions(_model: any, _range: Range, context: modes.CodeActionContext, _token: any): modes.CodeActionList {
return [ return {
{ title: context.only || '', kind: context.only } actions: [
]; { title: context.only || '', kind: context.only }
],
dispose: () => { }
};
} }
}; };
disposables.add(CodeActionProviderRegistry.register('fooLang', provider)); disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider));
const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a') } }, CancellationToken.None); const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a') } }, CancellationToken.None);
assert.equal(actions.length, 1); assert.equal(actions.length, 1);
...@@ -171,16 +178,12 @@ suite('CodeAction', () => { ...@@ -171,16 +178,12 @@ suite('CodeAction', () => {
}); });
test('getCodeActions should not return source code action by default', async function () { test('getCodeActions should not return source code action by default', async function () {
const provider = new class implements CodeActionProvider { const provider = staticCodeActionProvider(
provideCodeActions(): CodeAction[] { { title: 'a', kind: CodeActionKind.Source.value },
return [ { title: 'b', kind: 'b' }
{ title: 'a', kind: CodeActionKind.Source.value }, );
{ title: 'b', kind: 'b' }
];
}
};
disposables.add(CodeActionProviderRegistry.register('fooLang', provider)); disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider));
{ {
const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto' }, CancellationToken.None); const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto' }, CancellationToken.None);
...@@ -197,16 +200,16 @@ suite('CodeAction', () => { ...@@ -197,16 +200,16 @@ suite('CodeAction', () => {
test('getCodeActions should not invoke code action providers filtered out by providedCodeActionKinds', async function () { test('getCodeActions should not invoke code action providers filtered out by providedCodeActionKinds', async function () {
let wasInvoked = false; let wasInvoked = false;
const provider = new class implements CodeActionProvider { const provider = new class implements modes.CodeActionProvider {
provideCodeActions() { provideCodeActions(): modes.CodeActionList {
wasInvoked = true; wasInvoked = true;
return []; return { actions: [], dispose: () => { } };
} }
providedCodeActionKinds = [CodeActionKind.Refactor.value]; providedCodeActionKinds = [CodeActionKind.Refactor.value];
}; };
disposables.add(CodeActionProviderRegistry.register('fooLang', provider)); disposables.add(modes.CodeActionProviderRegistry.register('fooLang', provider));
const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), {
type: 'auto', type: 'auto',
......
...@@ -9,19 +9,24 @@ import { URI } from 'vs/base/common/uri'; ...@@ -9,19 +9,24 @@ import { URI } from 'vs/base/common/uri';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { Selection } from 'vs/editor/common/core/selection'; import { Selection } from 'vs/editor/common/core/selection';
import { TextModel } from 'vs/editor/common/model/textModel'; import { TextModel } from 'vs/editor/common/model/textModel';
import { CodeActionProviderRegistry, LanguageIdentifier } from 'vs/editor/common/modes'; import * as modes from 'vs/editor/common/modes';
import { CodeActionOracle, CodeActionsState } from 'vs/editor/contrib/codeAction/codeActionModel'; import { CodeActionOracle, CodeActionsState } from 'vs/editor/contrib/codeAction/codeActionModel';
import { createTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { createTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
import { MarkerService } from 'vs/platform/markers/common/markerService'; import { MarkerService } from 'vs/platform/markers/common/markerService';
const testProvider = { const testProvider = {
provideCodeActions() { provideCodeActions(): modes.CodeActionList {
return [{ id: 'test-command', title: 'test', arguments: [] }]; return {
actions: [
{ title: 'test', command: { id: 'test-command', title: 'test', arguments: [] } }
],
dispose() { /* noop*/ }
};
} }
}; };
suite('CodeAction', () => { suite('CodeAction', () => {
const languageIdentifier = new LanguageIdentifier('foo-lang', 3); const languageIdentifier = new modes.LanguageIdentifier('foo-lang', 3);
let uri = URI.parse('untitled:path'); let uri = URI.parse('untitled:path');
let model: TextModel; let model: TextModel;
let markerService: MarkerService; let markerService: MarkerService;
...@@ -44,7 +49,7 @@ suite('CodeAction', () => { ...@@ -44,7 +49,7 @@ suite('CodeAction', () => {
}); });
test('Orcale -> marker added', done => { test('Orcale -> marker added', done => {
const reg = CodeActionProviderRegistry.register(languageIdentifier.language, testProvider); const reg = modes.CodeActionProviderRegistry.register(languageIdentifier.language, testProvider);
disposables.add(reg); disposables.add(reg);
const oracle = new CodeActionOracle(editor, markerService, (e: CodeActionsState.Triggered) => { const oracle = new CodeActionOracle(editor, markerService, (e: CodeActionsState.Triggered) => {
...@@ -70,7 +75,7 @@ suite('CodeAction', () => { ...@@ -70,7 +75,7 @@ suite('CodeAction', () => {
}); });
test('Orcale -> position changed', () => { test('Orcale -> position changed', () => {
const reg = CodeActionProviderRegistry.register(languageIdentifier.language, testProvider); const reg = modes.CodeActionProviderRegistry.register(languageIdentifier.language, testProvider);
disposables.add(reg); disposables.add(reg);
markerService.changeOne('fake', uri, [{ markerService.changeOne('fake', uri, [{
...@@ -100,9 +105,9 @@ suite('CodeAction', () => { ...@@ -100,9 +105,9 @@ suite('CodeAction', () => {
}); });
test('Lightbulb is in the wrong place, #29933', async function () { test('Lightbulb is in the wrong place, #29933', async function () {
const reg = CodeActionProviderRegistry.register(languageIdentifier.language, { const reg = modes.CodeActionProviderRegistry.register(languageIdentifier.language, {
provideCodeActions(_doc, _range) { provideCodeActions(_doc, _range): modes.CodeActionList {
return []; return { actions: [], dispose() { /* noop*/ } };
} }
}); });
disposables.add(reg); disposables.add(reg);
...@@ -138,7 +143,7 @@ suite('CodeAction', () => { ...@@ -138,7 +143,7 @@ suite('CodeAction', () => {
}); });
test('Orcale -> should only auto trigger once for cursor and marker update right after each other', done => { test('Orcale -> should only auto trigger once for cursor and marker update right after each other', done => {
const reg = CodeActionProviderRegistry.register(languageIdentifier.language, testProvider); const reg = modes.CodeActionProviderRegistry.register(languageIdentifier.language, testProvider);
disposables.add(reg); disposables.add(reg);
let triggerCount = 0; let triggerCount = 0;
......
...@@ -187,6 +187,10 @@ class ModesContentComputer implements IHoverComputer<HoverPart[]> { ...@@ -187,6 +187,10 @@ class ModesContentComputer implements IHoverComputer<HoverPart[]> {
} }
} }
interface ActionSet extends IDisposable {
readonly actions: Action[];
}
export class ModesContentHoverWidget extends ContentHoverWidget { export class ModesContentHoverWidget extends ContentHoverWidget {
static readonly ID = 'editor.contrib.modesContentHoverWidget'; static readonly ID = 'editor.contrib.modesContentHoverWidget';
...@@ -535,10 +539,11 @@ export class ModesContentHoverWidget extends ContentHoverWidget { ...@@ -535,10 +539,11 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
const codeActionsPromise = this.getCodeActions(markerHover.marker); const codeActionsPromise = this.getCodeActions(markerHover.marker);
disposables.add(toDisposable(() => codeActionsPromise.cancel())); disposables.add(toDisposable(() => codeActionsPromise.cancel()));
const actions = await codeActionsPromise; const actions = await codeActionsPromise;
disposables.add(actions);
const elementPosition = dom.getDomNodePagePosition(target); const elementPosition = dom.getDomNodePagePosition(target);
this._contextMenuService.showContextMenu({ this._contextMenuService.showContextMenu({
getAnchor: () => ({ x: elementPosition.left + 6, y: elementPosition.top + elementPosition.height + 6 }), getAnchor: () => ({ x: elementPosition.left + 6, y: elementPosition.top + elementPosition.height + 6 }),
getActions: () => actions getActions: () => actions.actions
}); });
} }
})); }));
...@@ -557,20 +562,33 @@ export class ModesContentHoverWidget extends ContentHoverWidget { ...@@ -557,20 +562,33 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
return hoverElement; return hoverElement;
} }
private getCodeActions(marker: IMarker): CancelablePromise<Action[]> { private getCodeActions(marker: IMarker): CancelablePromise<ActionSet> {
return createCancelablePromise(async cancellationToken => { return createCancelablePromise(async cancellationToken => {
const codeActions = await getCodeActions(this._editor.getModel()!, new Range(marker.startLineNumber, marker.startColumn, marker.endLineNumber, marker.endColumn), { type: 'manual', filter: { kind: CodeActionKind.QuickFix } }, cancellationToken); const codeActions = await getCodeActions(this._editor.getModel()!, new Range(marker.startLineNumber, marker.startColumn, marker.endLineNumber, marker.endColumn), { type: 'manual', filter: { kind: CodeActionKind.QuickFix } }, cancellationToken);
if (codeActions.actions.length) { if (codeActions.actions.length) {
return codeActions.actions.map(codeAction => new Action( const disposables = new DisposableStore();
codeAction.command ? codeAction.command.id : codeAction.title, const actions: Action[] = [];
codeAction.title, for (const codeAction of codeActions.actions) {
undefined, disposables.add(disposables);
true, actions.push(new Action(
() => applyCodeAction(codeAction, this._bulkEditService, this._commandService))); codeAction.command ? codeAction.command.id : codeAction.title,
codeAction.title,
undefined,
true,
() => applyCodeAction(codeAction, this._bulkEditService, this._commandService)));
}
return {
actions: actions,
dispose: () => disposables.dispose()
};
} }
return [
new Action('', nls.localize('editor.action.quickFix.noneMessage', "No code actions available")) return {
]; actions: [
new Action('', nls.localize('editor.action.quickFix.noneMessage', "No code actions available"))
],
dispose() { }
};
}); });
} }
......
...@@ -427,7 +427,7 @@ export function registerCodeLensProvider(languageId: string, provider: modes.Cod ...@@ -427,7 +427,7 @@ export function registerCodeLensProvider(languageId: string, provider: modes.Cod
*/ */
export function registerCodeActionProvider(languageId: string, provider: CodeActionProvider): IDisposable { export function registerCodeActionProvider(languageId: string, provider: CodeActionProvider): IDisposable {
return modes.CodeActionProviderRegistry.register(languageId, { return modes.CodeActionProviderRegistry.register(languageId, {
provideCodeActions: (model: model.ITextModel, range: Range, context: modes.CodeActionContext, token: CancellationToken): (modes.Command | modes.CodeAction)[] | Promise<(modes.Command | modes.CodeAction)[]> => { provideCodeActions: (model: model.ITextModel, range: Range, context: modes.CodeActionContext, token: CancellationToken): modes.CodeActionList | Promise<modes.CodeActionList> => {
let markers = StaticServices.markerService.get().read({ resource: model.uri }).filter(m => { let markers = StaticServices.markerService.get().read({ resource: model.uri }).filter(m => {
return Range.areIntersectingOrTouching(m, range); return Range.areIntersectingOrTouching(m, range);
}); });
...@@ -510,7 +510,7 @@ export interface CodeActionProvider { ...@@ -510,7 +510,7 @@ export interface CodeActionProvider {
/** /**
* Provide commands for the given document and range. * Provide commands for the given document and range.
*/ */
provideCodeActions(model: model.ITextModel, range: Range, context: CodeActionContext, token: CancellationToken): (modes.Command | modes.CodeAction)[] | Promise<(modes.Command | modes.CodeAction)[]>; provideCodeActions(model: model.ITextModel, range: Range, context: CodeActionContext, token: CancellationToken): modes.CodeActionList | Promise<modes.CodeActionList>;
} }
/** /**
......
...@@ -4433,7 +4433,7 @@ declare namespace monaco.languages { ...@@ -4433,7 +4433,7 @@ declare namespace monaco.languages {
/** /**
* Provide commands for the given document and range. * Provide commands for the given document and range.
*/ */
provideCodeActions(model: editor.ITextModel, range: Range, context: CodeActionContext, token: CancellationToken): (Command | CodeAction)[] | Promise<(Command | CodeAction)[]>; provideCodeActions(model: editor.ITextModel, range: Range, context: CodeActionContext, token: CancellationToken): CodeActionList | Promise<CodeActionList>;
} }
/** /**
...@@ -4894,6 +4894,10 @@ declare namespace monaco.languages { ...@@ -4894,6 +4894,10 @@ declare namespace monaco.languages {
isPreferred?: boolean; isPreferred?: boolean;
} }
export interface CodeActionList extends IDisposable {
readonly actions: ReadonlyArray<CodeAction>;
}
/** /**
* Represents a parameter of a callable-signature. A parameter can * Represents a parameter of a callable-signature. A parameter can
* have a label and a doc-comment. * have a label and a doc-comment.
......
...@@ -471,7 +471,7 @@ export function validateProperty(property: string): string | null { ...@@ -471,7 +471,7 @@ export function validateProperty(property: string): string | null {
} }
export function getScopes(): { [key: string]: ConfigurationScope } { export function getScopes(): { [key: string]: ConfigurationScope } {
const scopes: { [key: string]: ConfigurationScope } = {}; const scopes = {};
const configurationProperties = configurationRegistry.getConfigurationProperties(); const configurationProperties = configurationRegistry.getConfigurationProperties();
for (const key of Object.keys(configurationProperties)) { for (const key of Object.keys(configurationProperties)) {
scopes[key] = configurationProperties[key].scope; scopes[key] = configurationProperties[key].scope;
......
...@@ -11,7 +11,7 @@ import * as search from 'vs/workbench/contrib/search/common/search'; ...@@ -11,7 +11,7 @@ import * as search from 'vs/workbench/contrib/search/common/search';
import { CancellationToken } from 'vs/base/common/cancellation'; import { CancellationToken } from 'vs/base/common/cancellation';
import { Position as EditorPosition } from 'vs/editor/common/core/position'; import { Position as EditorPosition } from 'vs/editor/common/core/position';
import { Range as EditorRange, IRange } from 'vs/editor/common/core/range'; import { Range as EditorRange, IRange } from 'vs/editor/common/core/range';
import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ISerializedLanguageConfiguration, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, LocationDto, WorkspaceSymbolDto, CodeActionDto, reviveWorkspaceEditDto, ISerializedDocumentFilter, DefinitionLinkDto, ISerializedSignatureHelpProviderMetadata, LinkDto, CallHierarchyDto, SuggestDataDto } from '../common/extHost.protocol'; import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ISerializedLanguageConfiguration, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, LocationDto, WorkspaceSymbolDto, reviveWorkspaceEditDto, ISerializedDocumentFilter, DefinitionLinkDto, ISerializedSignatureHelpProviderMetadata, LinkDto, CallHierarchyDto, SuggestDataDto, CodeActionDto } from '../common/extHost.protocol';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { LanguageConfiguration, IndentationRule, OnEnterRule } from 'vs/editor/common/modes/languageConfiguration'; import { LanguageConfiguration, IndentationRule, OnEnterRule } from 'vs/editor/common/modes/languageConfiguration';
import { IModeService } from 'vs/editor/common/services/modeService'; import { IModeService } from 'vs/editor/common/services/modeService';
...@@ -20,24 +20,20 @@ import { URI } from 'vs/base/common/uri'; ...@@ -20,24 +20,20 @@ import { URI } from 'vs/base/common/uri';
import { Selection } from 'vs/editor/common/core/selection'; import { Selection } from 'vs/editor/common/core/selection';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import * as callh from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import * as callh from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
import { IHeapService } from 'vs/workbench/services/heap/common/heap';
import { mixin } from 'vs/base/common/objects'; import { mixin } from 'vs/base/common/objects';
@extHostNamedCustomer(MainContext.MainThreadLanguageFeatures) @extHostNamedCustomer(MainContext.MainThreadLanguageFeatures)
export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesShape { export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesShape {
private readonly _proxy: ExtHostLanguageFeaturesShape; private readonly _proxy: ExtHostLanguageFeaturesShape;
private readonly _heapService: IHeapService;
private readonly _modeService: IModeService; private readonly _modeService: IModeService;
private readonly _registrations: { [handle: number]: IDisposable; } = Object.create(null); private readonly _registrations: { [handle: number]: IDisposable; } = Object.create(null);
constructor( constructor(
extHostContext: IExtHostContext, extHostContext: IExtHostContext,
@IHeapService heapService: IHeapService,
@IModeService modeService: IModeService, @IModeService modeService: IModeService,
) { ) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostLanguageFeatures); this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostLanguageFeatures);
this._heapService = heapService;
this._modeService = modeService; this._modeService = modeService;
} }
...@@ -100,7 +96,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha ...@@ -100,7 +96,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
} }
} }
private static _reviveCodeActionDto(data: CodeActionDto[] | undefined): modes.CodeAction[] { private static _reviveCodeActionDto(data: ReadonlyArray<CodeActionDto>): modes.CodeAction[] {
if (data) { if (data) {
data.forEach(code => reviveWorkspaceEditDto(code.edit)); data.forEach(code => reviveWorkspaceEditDto(code.edit));
} }
...@@ -239,13 +235,19 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha ...@@ -239,13 +235,19 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
$registerQuickFixSupport(handle: number, selector: ISerializedDocumentFilter[], providedCodeActionKinds?: string[]): void { $registerQuickFixSupport(handle: number, selector: ISerializedDocumentFilter[], providedCodeActionKinds?: string[]): void {
this._registrations[handle] = modes.CodeActionProviderRegistry.register(selector, <modes.CodeActionProvider>{ this._registrations[handle] = modes.CodeActionProviderRegistry.register(selector, <modes.CodeActionProvider>{
provideCodeActions: (model: ITextModel, rangeOrSelection: EditorRange | Selection, context: modes.CodeActionContext, token: CancellationToken): Promise<modes.CodeAction[]> => { provideCodeActions: async (model: ITextModel, rangeOrSelection: EditorRange | Selection, context: modes.CodeActionContext, token: CancellationToken): Promise<modes.CodeActionList | undefined> => {
return this._proxy.$provideCodeActions(handle, model.uri, rangeOrSelection, context, token).then(dto => { const listDto = await this._proxy.$provideCodeActions(handle, model.uri, rangeOrSelection, context, token);
if (dto) { if (!listDto) {
dto.forEach(obj => { this._heapService.trackObject(obj.command); }); return undefined;
}
return <modes.CodeActionList>{
actions: MainThreadLanguageFeatures._reviveCodeActionDto(listDto.actions),
dispose: () => {
if (typeof listDto.cacheId === 'number') {
this._proxy.$releaseCodeActions(handle, listDto.cacheId);
}
} }
return MainThreadLanguageFeatures._reviveCodeActionDto(dto); };
});
}, },
providedCodeActionKinds providedCodeActionKinds
}); });
......
...@@ -305,6 +305,8 @@ class CodeActionOnSaveParticipant implements ISaveParticipant { ...@@ -305,6 +305,8 @@ class CodeActionOnSaveParticipant implements ISaveParticipant {
await this.applyCodeActions(actionsToRun.actions); await this.applyCodeActions(actionsToRun.actions);
} catch { } catch {
// Failure to apply a code action should not block other on save actions // Failure to apply a code action should not block other on save actions
} finally {
actionsToRun.dispose();
} }
} }
} }
......
...@@ -993,6 +993,11 @@ export interface CodeActionDto { ...@@ -993,6 +993,11 @@ export interface CodeActionDto {
isPreferred?: boolean; isPreferred?: boolean;
} }
export interface CodeActionListDto {
cacheId: number;
actions: ReadonlyArray<CodeActionDto>;
}
export type CacheId = number; export type CacheId = number;
export type ChainedCacheId = [CacheId, CacheId]; export type ChainedCacheId = [CacheId, CacheId];
...@@ -1041,7 +1046,8 @@ export interface ExtHostLanguageFeaturesShape { ...@@ -1041,7 +1046,8 @@ export interface ExtHostLanguageFeaturesShape {
$provideHover(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.Hover | undefined>; $provideHover(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.Hover | undefined>;
$provideDocumentHighlights(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.DocumentHighlight[] | undefined>; $provideDocumentHighlights(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise<modes.DocumentHighlight[] | undefined>;
$provideReferences(handle: number, resource: UriComponents, position: IPosition, context: modes.ReferenceContext, token: CancellationToken): Promise<LocationDto[] | undefined>; $provideReferences(handle: number, resource: UriComponents, position: IPosition, context: modes.ReferenceContext, token: CancellationToken): Promise<LocationDto[] | undefined>;
$provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise<CodeActionDto[] | undefined>; $provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise<CodeActionListDto | undefined>;
$releaseCodeActions(handle: number, cacheId: number): void;
$provideDocumentFormattingEdits(handle: number, resource: UriComponents, options: modes.FormattingOptions, token: CancellationToken): Promise<ISingleEditOperation[] | undefined>; $provideDocumentFormattingEdits(handle: number, resource: UriComponents, options: modes.FormattingOptions, token: CancellationToken): Promise<ISingleEditOperation[] | undefined>;
$provideDocumentRangeFormattingEdits(handle: number, resource: UriComponents, range: IRange, options: modes.FormattingOptions, token: CancellationToken): Promise<ISingleEditOperation[] | undefined>; $provideDocumentRangeFormattingEdits(handle: number, resource: UriComponents, range: IRange, options: modes.FormattingOptions, token: CancellationToken): Promise<ISingleEditOperation[] | undefined>;
$provideOnTypeFormattingEdits(handle: number, resource: UriComponents, position: IPosition, ch: string, options: modes.FormattingOptions, token: CancellationToken): Promise<ISingleEditOperation[] | undefined>; $provideOnTypeFormattingEdits(handle: number, resource: UriComponents, position: IPosition, ch: string, options: modes.FormattingOptions, token: CancellationToken): Promise<ISingleEditOperation[] | undefined>;
......
...@@ -15,7 +15,7 @@ import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; ...@@ -15,7 +15,7 @@ import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/common/extHostCommands';
import { ExtHostDiagnostics } from 'vs/workbench/api/common/extHostDiagnostics'; import { ExtHostDiagnostics } from 'vs/workbench/api/common/extHostDiagnostics';
import { asPromise } from 'vs/base/common/async'; import { asPromise } from 'vs/base/common/async';
import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier, IRawColorInfo, IMainContext, IdObject, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, ISerializedLanguageConfiguration, WorkspaceSymbolDto, SuggestResultDto, WorkspaceSymbolsDto, CodeActionDto, ISerializedDocumentFilter, WorkspaceEditDto, ISerializedSignatureHelpProviderMetadata, LinkDto, CodeLensDto, SuggestDataDto, LinksListDto, ChainedCacheId, CodeLensListDto } from './extHost.protocol'; import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier, IRawColorInfo, IMainContext, IdObject, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, ISerializedLanguageConfiguration, WorkspaceSymbolDto, SuggestResultDto, WorkspaceSymbolsDto, CodeActionDto, ISerializedDocumentFilter, WorkspaceEditDto, ISerializedSignatureHelpProviderMetadata, LinkDto, CodeLensDto, SuggestDataDto, LinksListDto, ChainedCacheId, CodeLensListDto, CodeActionListDto } from './extHost.protocol';
import { regExpLeadsToEndlessLoop, regExpFlags } from 'vs/base/common/strings'; import { regExpLeadsToEndlessLoop, regExpFlags } from 'vs/base/common/strings';
import { IPosition } from 'vs/editor/common/core/position'; import { IPosition } from 'vs/editor/common/core/position';
import { IRange, Range as EditorRange } from 'vs/editor/common/core/range'; import { IRange, Range as EditorRange } from 'vs/editor/common/core/range';
...@@ -307,6 +307,9 @@ export interface CustomCodeAction extends CodeActionDto { ...@@ -307,6 +307,9 @@ export interface CustomCodeAction extends CodeActionDto {
class CodeActionAdapter { class CodeActionAdapter {
private static readonly _maxCodeActionsPerFile: number = 1000; private static readonly _maxCodeActionsPerFile: number = 1000;
private readonly _cache = new Cache<vscode.CodeAction | vscode.Command>();
private readonly _disposables = new Map<number, DisposableStore>();
constructor( constructor(
private readonly _documents: ExtHostDocuments, private readonly _documents: ExtHostDocuments,
private readonly _commands: CommandsConverter, private readonly _commands: CommandsConverter,
...@@ -316,7 +319,7 @@ class CodeActionAdapter { ...@@ -316,7 +319,7 @@ class CodeActionAdapter {
private readonly _extensionId: ExtensionIdentifier private readonly _extensionId: ExtensionIdentifier
) { } ) { }
provideCodeActions(resource: URI, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise<CodeActionDto[] | undefined> { provideCodeActions(resource: URI, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise<CodeActionListDto | undefined> {
const doc = this._documents.getDocument(resource); const doc = this._documents.getDocument(resource);
const ran = Selection.isISelection(rangeOrSelection) const ran = Selection.isISelection(rangeOrSelection)
...@@ -339,34 +342,39 @@ class CodeActionAdapter { ...@@ -339,34 +342,39 @@ class CodeActionAdapter {
}; };
return asPromise(() => this._provider.provideCodeActions(doc, ran, codeActionContext, token)).then(commandsOrActions => { return asPromise(() => this._provider.provideCodeActions(doc, ran, codeActionContext, token)).then(commandsOrActions => {
if (!isNonEmptyArray(commandsOrActions)) { if (!isNonEmptyArray(commandsOrActions) || token.isCancellationRequested) {
return undefined; return undefined;
} }
const result: CustomCodeAction[] = [];
const cacheId = this._cache.add(commandsOrActions);
const disposables = new DisposableStore();
this._disposables.set(cacheId, disposables);
const actions: CustomCodeAction[] = [];
for (const candidate of commandsOrActions) { for (const candidate of commandsOrActions) {
if (!candidate) { if (!candidate) {
continue; continue;
} }
if (CodeActionAdapter._isCommand(candidate)) { if (CodeActionAdapter._isCommand(candidate)) {
// old school: synthetic code action // old school: synthetic code action
result.push({ actions.push({
_isSynthetic: true, _isSynthetic: true,
title: candidate.title, title: candidate.title,
command: this._commands.toInternal(candidate), command: this._commands.toInternal2(candidate, disposables),
}); });
} else { } else {
if (codeActionContext.only) { if (codeActionContext.only) {
if (!candidate.kind) { if (!candidate.kind) {
this._logService.warn(`${this._extensionId.value} - Code actions of kind '${codeActionContext.only.value} 'requested but returned code action does not have a 'kind'. Code action will be dropped. Please set 'CodeAction.kind'.`); this._logService.warn(`${this._extensionId.value} - Code actions of kind '${codeActionContext.only.value} 'requested but returned code action does not have a 'kind'. Code action will be dropped. Please set 'CodeAction.kind'.`);
} else if (!codeActionContext.only.contains(candidate.kind)) { } else if (!codeActionContext.only.contains(candidate.kind)) {
this._logService.warn(`${this._extensionId.value} -Code actions of kind '${codeActionContext.only.value} 'requested but returned code action is of kind '${candidate.kind.value}'. Code action will be dropped. Please check 'CodeActionContext.only' to only return requested code actions.`); this._logService.warn(`${this._extensionId.value} - Code actions of kind '${codeActionContext.only.value} 'requested but returned code action is of kind '${candidate.kind.value}'. Code action will be dropped. Please check 'CodeActionContext.only' to only return requested code actions.`);
} }
} }
// new school: convert code action // new school: convert code action
result.push({ actions.push({
title: candidate.title, title: candidate.title,
command: candidate.command && this._commands.toInternal(candidate.command), command: candidate.command && this._commands.toInternal2(candidate.command, disposables),
diagnostics: candidate.diagnostics && candidate.diagnostics.map(typeConvert.Diagnostic.from), diagnostics: candidate.diagnostics && candidate.diagnostics.map(typeConvert.Diagnostic.from),
edit: candidate.edit && typeConvert.WorkspaceEdit.from(candidate.edit), edit: candidate.edit && typeConvert.WorkspaceEdit.from(candidate.edit),
kind: candidate.kind && candidate.kind.value, kind: candidate.kind && candidate.kind.value,
...@@ -375,10 +383,16 @@ class CodeActionAdapter { ...@@ -375,10 +383,16 @@ class CodeActionAdapter {
} }
} }
return result; return <CodeActionListDto>{ cacheId, actions };
}); });
} }
public releaseCodeActions(cachedId: number): void {
dispose(this._disposables.get(cachedId));
this._disposables.delete(cachedId);
this._cache.delete(cachedId);
}
private static _isCommand(thing: any): thing is vscode.Command { private static _isCommand(thing: any): thing is vscode.Command {
return typeof (<vscode.Command>thing).command === 'string' && typeof (<vscode.Command>thing).title === 'string'; return typeof (<vscode.Command>thing).command === 'string' && typeof (<vscode.Command>thing).title === 'string';
} }
...@@ -1276,10 +1290,14 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { ...@@ -1276,10 +1290,14 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
} }
$provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise<CodeActionDto[] | undefined> { $provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise<CodeActionListDto | undefined> {
return this._withAdapter(handle, CodeActionAdapter, adapter => adapter.provideCodeActions(URI.revive(resource), rangeOrSelection, context, token), undefined); return this._withAdapter(handle, CodeActionAdapter, adapter => adapter.provideCodeActions(URI.revive(resource), rangeOrSelection, context, token), undefined);
} }
$releaseCodeActions(handle: number, cacheId: number): void {
this._withAdapter(handle, CodeActionAdapter, adapter => Promise.resolve(adapter.releaseCodeActions(cacheId)), undefined);
}
// --- formatting // --- formatting
registerDocumentFormattingEditProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentFormattingEditProvider): vscode.Disposable { registerDocumentFormattingEditProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentFormattingEditProvider): vscode.Disposable {
......
...@@ -548,7 +548,9 @@ export class MarkerViewModel extends Disposable { ...@@ -548,7 +548,9 @@ export class MarkerViewModel extends Disposable {
if (model) { if (model) {
if (!this.codeActionsPromise) { if (!this.codeActionsPromise) {
this.codeActionsPromise = createCancelablePromise(cancellationToken => { this.codeActionsPromise = createCancelablePromise(cancellationToken => {
return getCodeActions(model, new Range(this.marker.range.startLineNumber, this.marker.range.startColumn, this.marker.range.endLineNumber, this.marker.range.endColumn), { type: 'manual', filter: { kind: CodeActionKind.QuickFix } }, cancellationToken); return getCodeActions(model, new Range(this.marker.range.startLineNumber, this.marker.range.startColumn, this.marker.range.endLineNumber, this.marker.range.endColumn), { type: 'manual', filter: { kind: CodeActionKind.QuickFix } }, cancellationToken).then(actions => {
return this._register(actions);
});
}); });
} }
return this.codeActionsPromise; return this.codeActionsPromise;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册