mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-20 20:10:11 -04:00
Merge from master
This commit is contained in:
@@ -3,11 +3,10 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { flatten, isFalsyOrEmpty, mergeSort } from 'vs/base/common/arrays';
|
||||
import { asWinJsPromise } from 'vs/base/common/async';
|
||||
import { flatten, mergeSort, isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { illegalArgument, isPromiseCanceledError, onUnexpectedExternalError } from 'vs/base/common/errors';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { registerLanguageCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
@@ -22,21 +21,43 @@ export function getCodeActions(model: ITextModel, rangeOrSelection: Range | Sele
|
||||
trigger: trigger && trigger.type === 'manual' ? CodeActionTriggerKind.Manual : CodeActionTriggerKind.Automatic
|
||||
};
|
||||
|
||||
const promises = CodeActionProviderRegistry.all(model).map(support => {
|
||||
return asWinJsPromise(token => support.provideCodeActions(model, rangeOrSelection, codeActionContext, token)).then(providedCodeActions => {
|
||||
if (!Array.isArray(providedCodeActions)) {
|
||||
return [];
|
||||
}
|
||||
return providedCodeActions.filter(action => isValidAction(trigger && trigger.filter, action));
|
||||
}, (err): CodeAction[] => {
|
||||
if (isPromiseCanceledError(err)) {
|
||||
throw err;
|
||||
const promises = CodeActionProviderRegistry.all(model)
|
||||
.filter(provider => {
|
||||
if (!provider.providedCodeActionKinds) {
|
||||
return true;
|
||||
}
|
||||
|
||||
onUnexpectedExternalError(err);
|
||||
return [];
|
||||
// Avoid calling providers that we know will not return code actions of interest
|
||||
return provider.providedCodeActionKinds.some(providedKind => {
|
||||
// Filter out actions by kind
|
||||
// The provided kind can be either a subset of a superset of the filtered kind
|
||||
if (trigger && trigger.filter && trigger.filter.kind && !(trigger.filter.kind.contains(providedKind) || new CodeActionKind(providedKind).contains(trigger.filter.kind.value))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't return source actions unless they are explicitly requested
|
||||
if (trigger && CodeActionKind.Source.contains(providedKind) && (!trigger.filter || !trigger.filter.includeSourceActions)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
})
|
||||
.map(support => {
|
||||
return Promise.resolve(support.provideCodeActions(model, rangeOrSelection, codeActionContext, token)).then(providedCodeActions => {
|
||||
if (!Array.isArray(providedCodeActions)) {
|
||||
return [];
|
||||
}
|
||||
return providedCodeActions.filter(action => isValidAction(trigger && trigger.filter, action));
|
||||
}, (err): CodeAction[] => {
|
||||
if (isPromiseCanceledError(err)) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
onUnexpectedExternalError(err);
|
||||
return [];
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(flatten)
|
||||
@@ -44,17 +65,17 @@ export function getCodeActions(model: ITextModel, rangeOrSelection: Range | Sele
|
||||
}
|
||||
|
||||
function isValidAction(filter: CodeActionFilter | undefined, action: CodeAction): boolean {
|
||||
if (!action) {
|
||||
return false;
|
||||
}
|
||||
return action && isValidActionKind(filter, action.kind);
|
||||
}
|
||||
|
||||
function isValidActionKind(filter: CodeActionFilter | undefined, kind: string | undefined): boolean {
|
||||
// Filter out actions by kind
|
||||
if (filter && filter.kind && (!action.kind || !filter.kind.contains(action.kind))) {
|
||||
if (filter && filter.kind && (!kind || !filter.kind.contains(kind))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't return source actions unless they are explicitly requested
|
||||
if (action.kind && CodeActionKind.Source.contains(action.kind) && (!filter || !filter.includeSourceActions)) {
|
||||
if (kind && CodeActionKind.Source.contains(kind) && (!filter || !filter.includeSourceActions)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -62,15 +83,13 @@ function isValidAction(filter: CodeActionFilter | undefined, action: CodeAction)
|
||||
}
|
||||
|
||||
function codeActionsComparator(a: CodeAction, b: CodeAction): number {
|
||||
const aHasDiags = !isFalsyOrEmpty(a.diagnostics);
|
||||
const bHasDiags = !isFalsyOrEmpty(b.diagnostics);
|
||||
if (aHasDiags) {
|
||||
if (bHasDiags) {
|
||||
if (isNonEmptyArray(a.diagnostics)) {
|
||||
if (isNonEmptyArray(b.diagnostics)) {
|
||||
return a.diagnostics[0].message.localeCompare(b.diagnostics[0].message);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
} else if (bHasDiags) {
|
||||
} else if (isNonEmptyArray(b.diagnostics)) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0; // both have no diagnostics
|
||||
|
||||
@@ -7,7 +7,6 @@ import { CancelablePromise } from 'vs/base/common/async';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { escapeRegExpCharacters } from 'vs/base/common/strings';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { EditorAction, EditorCommand, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
|
||||
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
|
||||
@@ -94,7 +93,7 @@ export class QuickFixController implements IEditorContribution {
|
||||
// Triggered for specific scope
|
||||
// Apply if we only have one action or requested autoApply, otherwise show menu
|
||||
e.actions.then(fixes => {
|
||||
if (e.trigger.autoApply === CodeActionAutoApply.First || (e.trigger.autoApply === CodeActionAutoApply.IfSingle && fixes.length === 1)) {
|
||||
if (fixes.length > 0 && e.trigger.autoApply === CodeActionAutoApply.First || (e.trigger.autoApply === CodeActionAutoApply.IfSingle && fixes.length === 1)) {
|
||||
this._onApplyCodeAction(fixes[0]);
|
||||
} else {
|
||||
this._codeActionContextMenu.show(e.actions, e.position);
|
||||
@@ -124,7 +123,7 @@ export class QuickFixController implements IEditorContribution {
|
||||
}
|
||||
|
||||
private _handleLightBulbSelect(coords: { x: number, y: number }): void {
|
||||
if (this._lightBulbWidget.model.actions) {
|
||||
if (this._lightBulbWidget.model && this._lightBulbWidget.model.actions) {
|
||||
this._codeActionContextMenu.show(this._lightBulbWidget.model.actions, coords);
|
||||
}
|
||||
}
|
||||
@@ -144,8 +143,8 @@ export class QuickFixController implements IEditorContribution {
|
||||
this._lightBulbWidget.title = title;
|
||||
}
|
||||
|
||||
private _onApplyCodeAction(action: CodeAction): TPromise<void> {
|
||||
return TPromise.wrap(applyCodeAction(action, this._bulkEditService, this._commandService, this._editor));
|
||||
private _onApplyCodeAction(action: CodeAction): Promise<void> {
|
||||
return applyCodeAction(action, this._bulkEditService, this._commandService, this._editor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
|
||||
import { debounceEvent, Emitter, Event } from 'vs/base/common/event';
|
||||
import { CancelablePromise, createCancelablePromise, TimeoutTimer } from 'vs/base/common/async';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
@@ -24,22 +23,24 @@ export const SUPPORTED_CODE_ACTIONS = new RawContextKey<string>('supportedCodeAc
|
||||
export class CodeActionOracle {
|
||||
|
||||
private _disposables: IDisposable[] = [];
|
||||
private readonly _autoTriggerTimer = new TimeoutTimer();
|
||||
|
||||
constructor(
|
||||
private _editor: ICodeEditor,
|
||||
private readonly _markerService: IMarkerService,
|
||||
private _signalChange: (e: CodeActionsComputeEvent) => any,
|
||||
delay: number = 250,
|
||||
private readonly _delay: number = 250,
|
||||
private readonly _progressService?: IProgressService,
|
||||
) {
|
||||
this._disposables.push(
|
||||
debounceEvent(this._markerService.onMarkerChanged, (last, cur) => last ? last.concat(cur) : cur, delay / 2)(e => this._onMarkerChanges(e)),
|
||||
debounceEvent(this._editor.onDidChangeCursorPosition, (last, cur) => cur, delay)(e => this._onCursorChange())
|
||||
this._markerService.onMarkerChanged(e => this._onMarkerChanges(e)),
|
||||
this._editor.onDidChangeCursorPosition(() => this._onCursorChange()),
|
||||
);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._disposables = dispose(this._disposables);
|
||||
this._autoTriggerTimer.cancel();
|
||||
}
|
||||
|
||||
trigger(trigger: CodeActionTrigger) {
|
||||
@@ -48,21 +49,29 @@ export class CodeActionOracle {
|
||||
}
|
||||
|
||||
private _onMarkerChanges(resources: URI[]): void {
|
||||
const { uri } = this._editor.getModel();
|
||||
for (const resource of resources) {
|
||||
if (resource.toString() === uri.toString()) {
|
||||
const model = this._editor.getModel();
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (resources.some(resource => resource.toString() === model.uri.toString())) {
|
||||
this._autoTriggerTimer.cancelAndSet(() => {
|
||||
this.trigger({ type: 'auto' });
|
||||
return;
|
||||
}
|
||||
}, this._delay);
|
||||
}
|
||||
}
|
||||
|
||||
private _onCursorChange(): void {
|
||||
this.trigger({ type: 'auto' });
|
||||
this._autoTriggerTimer.cancelAndSet(() => {
|
||||
this.trigger({ type: 'auto' });
|
||||
}, this._delay);
|
||||
}
|
||||
|
||||
private _getRangeOfMarker(selection: Selection): Range {
|
||||
private _getRangeOfMarker(selection: Selection): Range | undefined {
|
||||
const model = this._editor.getModel();
|
||||
if (!model) {
|
||||
return undefined;
|
||||
}
|
||||
for (const marker of this._markerService.read({ resource: model.uri })) {
|
||||
if (Range.intersectRanges(marker, selection)) {
|
||||
return Range.lift(marker);
|
||||
@@ -72,9 +81,12 @@ export class CodeActionOracle {
|
||||
}
|
||||
|
||||
private _getRangeOfSelectionUnlessWhitespaceEnclosed(trigger: CodeActionTrigger): Selection | undefined {
|
||||
if (!this._editor.hasModel()) {
|
||||
return undefined;
|
||||
}
|
||||
const model = this._editor.getModel();
|
||||
const selection = this._editor.getSelection();
|
||||
if (selection.isEmpty() && !(trigger.filter && trigger.filter.includeSourceActions)) {
|
||||
if (selection.isEmpty() && trigger.type === 'auto') {
|
||||
const { lineNumber, column } = selection.getPosition();
|
||||
const line = model.getLineContent(lineNumber);
|
||||
if (line.length === 0) {
|
||||
@@ -97,7 +109,7 @@ export class CodeActionOracle {
|
||||
}
|
||||
}
|
||||
}
|
||||
return selection;
|
||||
return selection ? selection : undefined;
|
||||
}
|
||||
|
||||
private _createEventAndSignalChange(trigger: CodeActionTrigger, selection: Selection | undefined): Thenable<CodeAction[] | undefined> {
|
||||
@@ -109,15 +121,26 @@ export class CodeActionOracle {
|
||||
position: undefined,
|
||||
actions: undefined,
|
||||
});
|
||||
return TPromise.as(undefined);
|
||||
return Promise.resolve(undefined);
|
||||
} else {
|
||||
const model = this._editor.getModel();
|
||||
if (!model) {
|
||||
// cancel
|
||||
this._signalChange({
|
||||
trigger,
|
||||
rangeOrSelection: undefined,
|
||||
position: undefined,
|
||||
actions: undefined,
|
||||
});
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
const markerRange = this._getRangeOfMarker(selection);
|
||||
const position = markerRange ? markerRange.getStartPosition() : selection.getStartPosition();
|
||||
const actions = createCancelablePromise(token => getCodeActions(model, selection, trigger, token));
|
||||
|
||||
if (this._progressService && trigger.type === 'manual') {
|
||||
this._progressService.showWhile(TPromise.wrap(actions), 250);
|
||||
this._progressService.showWhile(actions, 250);
|
||||
}
|
||||
|
||||
this._signalChange({
|
||||
@@ -133,16 +156,16 @@ export class CodeActionOracle {
|
||||
|
||||
export interface CodeActionsComputeEvent {
|
||||
trigger: CodeActionTrigger;
|
||||
rangeOrSelection: Range | Selection;
|
||||
position: Position;
|
||||
actions: CancelablePromise<CodeAction[]>;
|
||||
rangeOrSelection: Range | Selection | undefined;
|
||||
position: Position | undefined;
|
||||
actions: CancelablePromise<CodeAction[]> | undefined;
|
||||
}
|
||||
|
||||
export class CodeActionModel {
|
||||
|
||||
private _editor: ICodeEditor;
|
||||
private _markerService: IMarkerService;
|
||||
private _codeActionOracle: CodeActionOracle;
|
||||
private _codeActionOracle?: CodeActionOracle;
|
||||
private _onDidChangeFixes = new Emitter<CodeActionsComputeEvent>();
|
||||
private _disposables: IDisposable[] = [];
|
||||
private readonly _supportedCodeActions: IContextKey<string>;
|
||||
@@ -177,12 +200,13 @@ export class CodeActionModel {
|
||||
this._onDidChangeFixes.fire(undefined);
|
||||
}
|
||||
|
||||
if (this._editor.getModel()
|
||||
&& CodeActionProviderRegistry.has(this._editor.getModel())
|
||||
const model = this._editor.getModel();
|
||||
if (model
|
||||
&& CodeActionProviderRegistry.has(model)
|
||||
&& !this._editor.getConfiguration().readOnly) {
|
||||
|
||||
const supportedActions: string[] = [];
|
||||
for (const provider of CodeActionProviderRegistry.all(this._editor.getModel())) {
|
||||
for (const provider of CodeActionProviderRegistry.all(model)) {
|
||||
if (Array.isArray(provider.providedCodeActionKinds)) {
|
||||
supportedActions.push(...provider.providedCodeActionKinds);
|
||||
}
|
||||
@@ -201,6 +225,6 @@ export class CodeActionModel {
|
||||
if (this._codeActionOracle) {
|
||||
return this._codeActionOracle.trigger(trigger);
|
||||
}
|
||||
return TPromise.as(undefined);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ export class CodeActionKind {
|
||||
}
|
||||
}
|
||||
|
||||
export enum CodeActionAutoApply {
|
||||
export const enum CodeActionAutoApply {
|
||||
IfSingle = 1,
|
||||
First = 2,
|
||||
Never = 3
|
||||
|
||||
@@ -8,7 +8,6 @@ import { Action } from 'vs/base/common/actions';
|
||||
import { always } from 'vs/base/common/async';
|
||||
import { canceled } from 'vs/base/common/errors';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { ScrollType } from 'vs/editor/common/editorCommon';
|
||||
@@ -25,12 +24,12 @@ export class CodeActionContextMenu {
|
||||
constructor(
|
||||
private readonly _editor: ICodeEditor,
|
||||
private readonly _contextMenuService: IContextMenuService,
|
||||
private readonly _onApplyCodeAction: (action: CodeAction) => TPromise<any>
|
||||
private readonly _onApplyCodeAction: (action: CodeAction) => Promise<any>
|
||||
) { }
|
||||
|
||||
show(fixes: Thenable<CodeAction[]>, at: { x: number; y: number } | Position) {
|
||||
|
||||
const actions = fixes.then(value => {
|
||||
const actionsPromise = fixes ? fixes.then(value => {
|
||||
return value.map(action => {
|
||||
return new Action(action.command ? action.command.id : action.title, action.title, undefined, true, () => {
|
||||
return always(
|
||||
@@ -41,24 +40,26 @@ export class CodeActionContextMenu {
|
||||
}).then(actions => {
|
||||
if (!this._editor.getDomNode()) {
|
||||
// cancel when editor went off-dom
|
||||
return TPromise.wrapError<any>(canceled());
|
||||
return Promise.reject(canceled());
|
||||
}
|
||||
return actions;
|
||||
});
|
||||
}) : Promise.resolve([] as Action[]);
|
||||
|
||||
this._contextMenuService.showContextMenu({
|
||||
getAnchor: () => {
|
||||
if (Position.isIPosition(at)) {
|
||||
at = this._toCoords(at);
|
||||
}
|
||||
return at;
|
||||
},
|
||||
getActions: () => TPromise.wrap(actions),
|
||||
onHide: () => {
|
||||
this._visible = false;
|
||||
this._editor.focus();
|
||||
},
|
||||
autoSelectFirstItem: true
|
||||
actionsPromise.then(actions => {
|
||||
this._contextMenuService.showContextMenu({
|
||||
getAnchor: () => {
|
||||
if (Position.isIPosition(at)) {
|
||||
at = this._toCoords(at);
|
||||
}
|
||||
return at;
|
||||
},
|
||||
getActions: () => actions,
|
||||
onHide: () => {
|
||||
this._visible = false;
|
||||
this._editor.focus();
|
||||
},
|
||||
autoSelectFirstItem: true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import 'vs/css!./lightBulbWidget';
|
||||
import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser';
|
||||
import { TextModel } from 'vs/editor/common/model/textModel';
|
||||
import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger';
|
||||
import { CodeActionsComputeEvent } from './codeActionModel';
|
||||
|
||||
export class LightBulbWidget implements IDisposable, IContentWidget {
|
||||
@@ -25,8 +24,8 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
|
||||
|
||||
readonly onClick: Event<{ x: number, y: number }> = this._onClick.event;
|
||||
|
||||
private _position: IContentWidgetPosition;
|
||||
private _model: CodeActionsComputeEvent;
|
||||
private _position: IContentWidgetPosition | null;
|
||||
private _model: CodeActionsComputeEvent | null;
|
||||
private _futureFixes = new CancellationTokenSource();
|
||||
|
||||
constructor(editor: ICodeEditor) {
|
||||
@@ -40,7 +39,8 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
|
||||
this._disposables.push(this._editor.onDidChangeModelLanguage(_ => this._futureFixes.cancel()));
|
||||
this._disposables.push(this._editor.onDidChangeModelContent(_ => {
|
||||
// cancel when the line in question has been removed
|
||||
if (this._model && this.model.position.lineNumber >= this._editor.getModel().getLineCount()) {
|
||||
const editorModel = this._editor.getModel();
|
||||
if (!this.model || !this.model.position || !editorModel || this.model.position.lineNumber >= editorModel.getLineCount()) {
|
||||
this._futureFixes.cancel();
|
||||
}
|
||||
}));
|
||||
@@ -53,7 +53,7 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
|
||||
const { lineHeight } = this._editor.getConfiguration();
|
||||
|
||||
let pad = Math.floor(lineHeight / 3);
|
||||
if (this._position && this._position.position.lineNumber < this._model.position.lineNumber) {
|
||||
if (this._position && this._model && this._model.position && this._position.position !== null && this._position.position.lineNumber < this._model.position.lineNumber) {
|
||||
pad += lineHeight;
|
||||
}
|
||||
|
||||
@@ -96,13 +96,13 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
|
||||
return this._domNode;
|
||||
}
|
||||
|
||||
getPosition(): IContentWidgetPosition {
|
||||
getPosition(): IContentWidgetPosition | null {
|
||||
return this._position;
|
||||
}
|
||||
|
||||
set model(value: CodeActionsComputeEvent) {
|
||||
set model(value: CodeActionsComputeEvent | null) {
|
||||
|
||||
if (this._position && (!value.position || this._position.position.lineNumber !== value.position.lineNumber)) {
|
||||
if (!value || this._position && (!value.position || this._position.position && this._position.position.lineNumber !== value.position.lineNumber)) {
|
||||
// hide when getting a 'hide'-request or when currently
|
||||
// showing on another line
|
||||
this.hide();
|
||||
@@ -115,23 +115,23 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
|
||||
const { token } = this._futureFixes;
|
||||
this._model = value;
|
||||
|
||||
if (!this._model || !this._model.actions) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selection = this._model.rangeOrSelection;
|
||||
this._model.actions.then(fixes => {
|
||||
if (!token.isCancellationRequested && fixes && fixes.length > 0) {
|
||||
if (selection.isEmpty() && fixes.every(fix => fix.kind && CodeActionKind.Refactor.contains(fix.kind))) {
|
||||
this.hide();
|
||||
} else {
|
||||
this._show();
|
||||
}
|
||||
if (!token.isCancellationRequested && fixes && fixes.length > 0 && selection) {
|
||||
this._show();
|
||||
} else {
|
||||
this.hide();
|
||||
}
|
||||
}).catch(err => {
|
||||
}).catch(() => {
|
||||
this.hide();
|
||||
});
|
||||
}
|
||||
|
||||
get model(): CodeActionsComputeEvent {
|
||||
get model(): CodeActionsComputeEvent | null {
|
||||
return this._model;
|
||||
}
|
||||
|
||||
@@ -148,7 +148,10 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
|
||||
if (!config.contribInfo.lightbulbEnabled) {
|
||||
return;
|
||||
}
|
||||
const { lineNumber } = this._model.position;
|
||||
if (!this._model || !this._model.position) {
|
||||
return;
|
||||
}
|
||||
const { lineNumber, column } = this._model.position;
|
||||
const model = this._editor.getModel();
|
||||
if (!model) {
|
||||
return;
|
||||
@@ -158,13 +161,21 @@ export class LightBulbWidget implements IDisposable, IContentWidget {
|
||||
const lineContent = model.getLineContent(lineNumber);
|
||||
const indent = TextModel.computeIndentLevel(lineContent, tabSize);
|
||||
const lineHasSpace = config.fontInfo.spaceWidth * indent > 22;
|
||||
const isFolded = (lineNumber) => {
|
||||
return lineNumber > 2 && this._editor.getTopForLineNumber(lineNumber) === this._editor.getTopForLineNumber(lineNumber - 1);
|
||||
};
|
||||
|
||||
let effectiveLineNumber = lineNumber;
|
||||
if (!lineHasSpace) {
|
||||
if (lineNumber > 1) {
|
||||
if (lineNumber > 1 && !isFolded(lineNumber - 1)) {
|
||||
effectiveLineNumber -= 1;
|
||||
} else {
|
||||
} else if (!isFolded(lineNumber + 1)) {
|
||||
effectiveLineNumber += 1;
|
||||
} else if (column * config.fontInfo.spaceWidth < 22) {
|
||||
// cannot show lightbulb above/below and showing
|
||||
// it inline would overlay the cursor...
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,11 +2,9 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { dispose, IDisposable } 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 { TextModel } from 'vs/editor/common/model/textModel';
|
||||
import { CodeAction, CodeActionContext, CodeActionProvider, CodeActionProviderRegistry, Command, LanguageIdentifier, ResourceTextEdit, WorkspaceEdit } from 'vs/editor/common/modes';
|
||||
@@ -194,4 +192,27 @@ suite('CodeAction', () => {
|
||||
assert.strictEqual(actions[0].title, 'a');
|
||||
}
|
||||
});
|
||||
|
||||
test('getCodeActions should not invoke code action providers filtered out by providedCodeActionKinds', async function () {
|
||||
let wasInvoked = false;
|
||||
const provider = new class implements CodeActionProvider {
|
||||
provideCodeActions() {
|
||||
wasInvoked = true;
|
||||
return [];
|
||||
}
|
||||
|
||||
providedCodeActionKinds = [CodeActionKind.Refactor.value];
|
||||
};
|
||||
|
||||
disposables.push(CodeActionProviderRegistry.register('fooLang', provider));
|
||||
|
||||
const actions = await getCodeActions(model, new Range(1, 1, 2, 1), {
|
||||
type: 'auto',
|
||||
filter: {
|
||||
kind: CodeActionKind.QuickFix
|
||||
}
|
||||
});
|
||||
assert.strictEqual(actions.length, 0);
|
||||
assert.strictEqual(wasInvoked, false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { TextModel } from 'vs/editor/common/model/textModel';
|
||||
@@ -135,21 +135,33 @@ suite('CodeAction', () => {
|
||||
|
||||
editor.setSelection({ startLineNumber: 1, startColumn: 1, endLineNumber: 4, endColumn: 1 });
|
||||
});
|
||||
});
|
||||
|
||||
// // case 2 - selection over multiple lines & manual trigger -> lightbulb
|
||||
// await new TPromise(resolve => {
|
||||
test('Orcale -> should only auto trigger once for cursor and marker update right after each other', done => {
|
||||
const reg = CodeActionProviderRegistry.register(languageIdentifier.language, testProvider);
|
||||
disposables.push(reg);
|
||||
|
||||
// editor.setSelection({ startLineNumber: 1, startColumn: 1, endLineNumber: 4, endColumn: 1 });
|
||||
let triggerCount = 0;
|
||||
const oracle = new CodeActionOracle(editor, markerService, e => {
|
||||
assert.equal(e.trigger.type, 'auto');
|
||||
++triggerCount;
|
||||
|
||||
// let oracle = new QuickFixOracle(editor, markerService, e => {
|
||||
// assert.equal(e.type, 'manual');
|
||||
// assert.ok(e.range.equalsRange({ startLineNumber: 1, startColumn: 1, endLineNumber: 4, endColumn: 1 }));
|
||||
// give time for second trigger before completing test
|
||||
setTimeout(() => {
|
||||
oracle.dispose();
|
||||
assert.strictEqual(triggerCount, 1);
|
||||
done();
|
||||
}, 50);
|
||||
}, 5 /*delay*/);
|
||||
|
||||
// oracle.dispose();
|
||||
// resolve(null);
|
||||
// }, 5);
|
||||
markerService.changeOne('fake', uri, [{
|
||||
startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 6,
|
||||
message: 'error',
|
||||
severity: 1,
|
||||
code: '',
|
||||
source: ''
|
||||
}]);
|
||||
|
||||
// oracle.trigger('manual');
|
||||
// });
|
||||
editor.setSelection({ startLineNumber: 1, startColumn: 1, endLineNumber: 4, endColumn: 1 });
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user