提交 79b9a50b 编写于 作者: S Sandeep Somavarapu

Fix #67694

上级 36e8e306
......@@ -4,26 +4,15 @@
*--------------------------------------------------------------------------------------------*/
import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { MarkersModel, compareMarkersByUri, Marker } from './markersModel';
import { MarkersModel, compareMarkersByUri } from './markersModel';
import { Disposable } from 'vs/base/common/lifecycle';
import { IMarkerService, MarkerSeverity, IMarker, IMarkerData } from 'vs/platform/markers/common/markers';
import { IMarkerService, MarkerSeverity, IMarker } from 'vs/platform/markers/common/markers';
import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity';
import { localize } from 'vs/nls';
import Constants from './constants';
import { URI } from 'vs/base/common/uri';
import { groupBy } from 'vs/base/common/arrays';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IAction, Action } from 'vs/base/common/actions';
import { applyCodeAction } from 'vs/editor/contrib/codeAction/codeActionCommands';
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { IModelService } from 'vs/editor/common/services/modelService';
import { CodeAction } from 'vs/editor/common/modes';
import { Range } from 'vs/editor/common/core/range';
import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction';
import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger';
import { timeout, CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
export const IMarkersWorkbenchService = createDecorator<IMarkersWorkbenchService>('markersWorkbenchService');
......@@ -35,8 +24,6 @@ export interface IFilter {
export interface IMarkersWorkbenchService {
_serviceBrand: any;
readonly markersModel: MarkersModel;
hasQuickFixes(marker: Marker): Promise<boolean>;
getQuickFixActions(marker: Marker): Promise<IAction[]>;
}
export class MarkersWorkbenchService extends Disposable implements IMarkersWorkbenchService {
......@@ -44,17 +31,9 @@ export class MarkersWorkbenchService extends Disposable implements IMarkersWorkb
readonly markersModel: MarkersModel;
private readonly allFixesCache: Map<string, CancelablePromise<CodeAction[]>> = new Map<string, CancelablePromise<CodeAction[]>>();
private readonly codeActionsPromises: Map<string, Map<string, CancelablePromise<CodeAction[]>>> = new Map<string, Map<string, CancelablePromise<CodeAction[]>>>();
private readonly codeActions: Map<string, Map<string, CodeAction[]>> = new Map<string, Map<string, CodeAction[]>>();
constructor(
@IMarkerService private readonly markerService: IMarkerService,
@IInstantiationService instantiationService: IInstantiationService,
@IBulkEditService private readonly bulkEditService: IBulkEditService,
@ICommandService private readonly commandService: ICommandService,
@IEditorService private readonly editorService: IEditorService,
@IModelService private readonly modelService: IModelService
) {
super();
this.markersModel = this._register(instantiationService.createInstance(MarkersModel, this.readMarkers()));
......@@ -68,17 +47,6 @@ export class MarkersWorkbenchService extends Disposable implements IMarkersWorkb
private onMarkerChanged(resources: URI[]): void {
for (const resource of resources) {
const allFixes = this.allFixesCache.get(resource.toString());
if (allFixes) {
allFixes.cancel();
this.allFixesCache.delete(resource.toString());
}
const codeActions = this.codeActionsPromises.get(resource.toString());
if (codeActions) {
codeActions.forEach(promise => promise.cancel());
this.codeActionsPromises.delete(resource.toString());
}
this.codeActions.delete(resource.toString());
this.markersModel.setResourceMarkers(resource, this.readMarkers(resource));
}
}
......@@ -87,93 +55,6 @@ export class MarkersWorkbenchService extends Disposable implements IMarkersWorkb
return this.markerService.read({ resource, severities: MarkerSeverity.Error | MarkerSeverity.Warning | MarkerSeverity.Info });
}
getQuickFixActions(marker: Marker): Promise<IAction[]> {
const markerKey = IMarkerData.makeKey(marker.marker);
let codeActionsPerMarker = this.codeActions.get(marker.resource.toString());
if (!codeActionsPerMarker) {
codeActionsPerMarker = new Map<string, CodeAction[]>();
this.codeActions.set(marker.resource.toString(), codeActionsPerMarker);
}
const codeActions = codeActionsPerMarker.get(markerKey);
if (codeActions) {
return Promise.resolve(this.toActions(codeActions, marker));
} else {
let codeActionsPromisesPerMarker = this.codeActionsPromises.get(marker.resource.toString());
if (!codeActionsPromisesPerMarker) {
codeActionsPromisesPerMarker = new Map<string, CancelablePromise<CodeAction[]>>();
this.codeActionsPromises.set(marker.resource.toString(), codeActionsPromisesPerMarker);
}
if (!codeActionsPromisesPerMarker.has(markerKey)) {
const codeActionsPromise = this.getFixes(marker);
codeActionsPromisesPerMarker.set(markerKey, codeActionsPromise);
codeActionsPromise.then(codeActions => codeActionsPerMarker!.set(markerKey, codeActions));
}
// Wait for 100ms for code actions fetching.
return timeout(100).then(() => this.toActions(codeActionsPerMarker!.get(markerKey) || [], marker));
}
}
private toActions(codeActions: CodeAction[], marker: Marker): IAction[] {
return codeActions.map(codeAction => new Action(
codeAction.command ? codeAction.command.id : codeAction.title,
codeAction.title,
undefined,
true,
() => {
return this.openFileAtMarker(marker)
.then(() => applyCodeAction(codeAction, this.bulkEditService, this.commandService));
}));
}
async hasQuickFixes(marker: Marker): Promise<boolean> {
if (!this.modelService.getModel(marker.resource)) {
// Return early, If the model is not yet created
return false;
}
let allFixesPromise = this.allFixesCache.get(marker.resource.toString());
if (!allFixesPromise) {
allFixesPromise = this._getFixes(marker.resource);
this.allFixesCache.set(marker.resource.toString(), allFixesPromise);
}
const allFixes = await allFixesPromise;
if (allFixes.length) {
const markerKey = IMarkerData.makeKey(marker.marker);
for (const fix of allFixes) {
if (fix.diagnostics && fix.diagnostics.some(d => IMarkerData.makeKey(d) === markerKey)) {
return true;
}
}
}
return false;
}
private openFileAtMarker(element: Marker): Promise<void> {
const { resource, selection } = { resource: element.resource, selection: element.range };
return this.editorService.openEditor({
resource,
options: {
selection,
preserveFocus: true,
pinned: false,
revealIfVisible: true
},
}, ACTIVE_GROUP).then(() => undefined);
}
private getFixes(marker: Marker): CancelablePromise<CodeAction[]> {
return this._getFixes(marker.resource, new Range(marker.range.startLineNumber, marker.range.startColumn, marker.range.endLineNumber, marker.range.endColumn));
}
private _getFixes(uri: URI, range?: Range): CancelablePromise<CodeAction[]> {
return createCancelablePromise(cancellationToken => {
const model = this.modelService.getModel(uri);
if (model) {
return getCodeActions(model, range ? range : model.getFullModelRange(), { type: 'manual', filter: { kind: CodeActionKind.QuickFix } }, cancellationToken);
}
return Promise.resolve([]);
});
}
}
export class ActivityUpdater extends Disposable implements IWorkbenchContribution {
......
......@@ -385,6 +385,18 @@ export class MarkersPanel extends Panel implements IMarkerFilterController {
this.filterInputActionItem.focus();
}
}));
this._register(Event.any<any>(this.tree.onDidChangeSelection, this.tree.onDidChangeFocus)(() => {
const elements: TreeElement[] = [...this.tree.getSelection(), ...this.tree.getFocus()];
for (const element of elements) {
if (element instanceof Marker) {
const viewModel = this.markersViewModel.getViewModel(element);
if (viewModel) {
viewModel.showLightBulb();
}
}
}
}));
}
private createActions(): void {
......@@ -611,34 +623,35 @@ export class MarkersPanel extends Panel implements IMarkerFilterController {
e.browserEvent.preventDefault();
e.browserEvent.stopPropagation();
this._getMenuActions(e.element).then(actions => {
this.contextMenuService.showContextMenu({
getAnchor: () => e.anchor,
getActions: () => actions,
getActionItem: (action) => {
const keybinding = this.keybindingService.lookupKeybinding(action.id);
if (keybinding) {
return new ActionItem(action, action, { label: true, keybinding: keybinding.getLabel() });
}
return null;
},
onHide: (wasCancelled?: boolean) => {
if (wasCancelled) {
this.tree.domFocus();
}
this.contextMenuService.showContextMenu({
getAnchor: () => e.anchor,
getActions: () => this.getMenuActions(e.element),
getActionItem: (action) => {
const keybinding = this.keybindingService.lookupKeybinding(action.id);
if (keybinding) {
return new ActionItem(action, action, { label: true, keybinding: keybinding.getLabel() });
}
});
return null;
},
onHide: (wasCancelled?: boolean) => {
if (wasCancelled) {
this.tree.domFocus();
}
}
});
}
private async _getMenuActions(element: TreeElement): Promise<IAction[]> {
private getMenuActions(element: TreeElement): IAction[] {
const result: IAction[] = [];
if (element instanceof Marker) {
const quickFixActions = await this.markersWorkbenchService.getQuickFixActions(element);
if (quickFixActions.length) {
result.push(...quickFixActions);
result.push(new Separator());
const viewModel = this.markersViewModel.getViewModel(element);
if (viewModel) {
const quickFixActions = viewModel.quickFixAction.quickFixes;
if (quickFixActions.length) {
result.push(...quickFixActions);
result.push(new Separator());
}
}
}
......
......@@ -5,7 +5,7 @@
import { Delayer } from 'vs/base/common/async';
import * as DOM from 'vs/base/browser/dom';
import { Action, IActionChangeEvent } from 'vs/base/common/actions';
import { Action, IActionChangeEvent, IAction } from 'vs/base/common/actions';
import { HistoryInputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import { KeyCode } from 'vs/base/common/keyCodes';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
......@@ -25,9 +25,7 @@ import { localize } from 'vs/nls';
import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ContextScopedHistoryInputBox } from 'vs/platform/widget/browser/contextScopedHistoryWidget';
import { Marker } from 'vs/workbench/contrib/markers/electron-browser/markersModel';
import { IModelService } from 'vs/editor/common/services/modelService';
import { isEqual } from 'vs/base/common/resources';
import { Marker } from 'vs/workbench/parts/markers/electron-browser/markersModel';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { Event, Emitter } from 'vs/base/common/event';
import { FilterOptions } from 'vs/workbench/contrib/markers/electron-browser/markersFilterOptions';
......@@ -296,28 +294,25 @@ export class QuickFixAction extends Action {
public static readonly ID: string = 'workbench.actions.problems.quickfix';
private updated: boolean = false;
private disposables: IDisposable[] = [];
private readonly _onShowQuickFixes: Emitter<void> = new Emitter<void>();
readonly onShowQuickFixes: Event<void> = this._onShowQuickFixes.event;
private _quickFixes: IAction[] = [];
get quickFixes(): IAction[] {
return this._quickFixes;
}
set quickFixes(quickFixes: IAction[]) {
this._quickFixes = quickFixes;
this.enabled = this._quickFixes.length > 0;
}
constructor(
readonly marker: Marker,
@IModelService modelService: IModelService,
@IMarkersWorkbenchService private readonly markerWorkbenchService: IMarkersWorkbenchService,
) {
super(QuickFixAction.ID, Messages.MARKERS_PANEL_ACTION_TOOLTIP_QUICKFIX, 'markers-panel-action-quickfix', false);
this.disposables.push(this._onShowQuickFixes);
if (modelService.getModel(this.marker.resource)) {
this.update();
} else {
modelService.onModelAdded(model => {
if (isEqual(model.uri, marker.resource)) {
this.update();
}
}, this, this.disposables);
}
}
run(): Promise<void> {
......@@ -325,13 +320,6 @@ export class QuickFixAction extends Action {
return Promise.resolve();
}
private update(): void {
if (!this.updated) {
this.markerWorkbenchService.hasQuickFixes(this.marker).then(hasFixes => this.enabled = hasFixes);
this.updated = true;
}
}
dispose(): void {
dispose(this.disposables);
super.dispose();
......@@ -342,7 +330,6 @@ export class QuickFixActionItem extends ActionItem {
constructor(action: QuickFixAction,
@IContextMenuService private readonly contextMenuService: IContextMenuService,
@IMarkersWorkbenchService private readonly markerWorkbenchService: IMarkersWorkbenchService
) {
super(null, action, { icon: true, label: false });
}
......@@ -360,11 +347,12 @@ export class QuickFixActionItem extends ActionItem {
return;
}
const elementPosition = DOM.getDomNodePagePosition(this.element);
this.markerWorkbenchService.getQuickFixActions((<QuickFixAction>this.getAction()).marker).then(actions => {
const quickFixes = (<QuickFixAction>this.getAction()).quickFixes;
if (quickFixes.length) {
this.contextMenuService.showContextMenu({
getAnchor: () => ({ x: elementPosition.left + 10, y: elementPosition.top + elementPosition.height + 4 }),
getActions: () => actions
getActions: () => quickFixes
});
});
}
}
}
......@@ -19,7 +19,7 @@ import { IDisposable, dispose, Disposable, toDisposable } from 'vs/base/common/l
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { QuickFixAction, QuickFixActionItem } from 'vs/workbench/contrib/markers/electron-browser/markersPanelActions';
import { ILabelService } from 'vs/platform/label/common/label';
import { dirname, basename } from 'vs/base/common/resources';
import { dirname, basename, isEqual } from 'vs/base/common/resources';
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { ITreeFilter, TreeVisibility, TreeFilterResult, ITreeRenderer, ITreeNode, ITreeDragAndDrop, ITreeDragOverReaction } from 'vs/base/browser/ui/tree/tree';
import { FilterOptions } from 'vs/workbench/contrib/markers/electron-browser/markersFilterOptions';
......@@ -28,11 +28,22 @@ import { Event, Emitter } from 'vs/base/common/event';
import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
import { isUndefinedOrNull } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import { Action } from 'vs/base/common/actions';
import { Action, IAction } from 'vs/base/common/actions';
import { localize } from 'vs/nls';
import { IDragAndDropData } from 'vs/base/browser/dnd';
import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView';
import { fillResourceDataTransfers } from 'vs/workbench/browser/dnd';
import { CancelablePromise, createCancelablePromise, Delayer } from 'vs/base/common/async';
import { IModelService } from 'vs/editor/common/services/modelService';
import { Range } from 'vs/editor/common/core/range';
import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction';
import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger';
import { ITextModel } from 'vs/editor/common/model';
import { CodeAction } from 'vs/editor/common/modes';
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { applyCodeAction } from 'vs/editor/contrib/codeAction/codeActionCommands';
export type TreeElement = ResourceMarkers | Marker | RelatedInformation;
......@@ -235,7 +246,7 @@ class MarkerWidget extends Disposable {
private disposables: IDisposable[] = [];
constructor(
parent: HTMLElement,
private parent: HTMLElement,
private readonly markersViewModel: MarkersViewModel,
instantiationService: IInstantiationService
) {
......@@ -262,6 +273,8 @@ class MarkerWidget extends Disposable {
this.renderMultilineActionbar(element);
this.renderMessageAndDetails(element, filterData);
this.disposables.push(dom.addDisposableListener(this.parent, dom.EventType.MOUSE_OVER, () => this.markersViewModel.onMarkerMouseHover(element)));
this.disposables.push(dom.addDisposableListener(this.parent, dom.EventType.MOUSE_LEAVE, () => this.markersViewModel.onMarkerMouseLeave(element)));
}
private renderQuickfixActionbar(marker: Marker): void {
......@@ -474,11 +487,26 @@ export class MarkerViewModel extends Disposable {
private readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChange: Event<void> = this._onDidChange.event;
private modelPromise: CancelablePromise<ITextModel> | null = null;
private codeActionsPromise: CancelablePromise<CodeAction[]> | null = null;
constructor(
private readonly marker: Marker,
@IInstantiationService private instantiationService: IInstantiationService
@IModelService private modelService: IModelService,
@IInstantiationService private instantiationService: IInstantiationService,
@IBulkEditService private readonly bulkEditService: IBulkEditService,
@ICommandService private readonly commandService: ICommandService,
@IEditorService private readonly editorService: IEditorService
) {
super();
this._register(toDisposable(() => {
if (this.modelPromise) {
this.modelPromise.cancel();
}
if (this.codeActionsPromise) {
this.codeActionsPromise.cancel();
}
}));
}
private _multiline: boolean = true;
......@@ -500,6 +528,91 @@ export class MarkerViewModel extends Disposable {
}
return this._quickFixAction;
}
showLightBulb(): void {
this.setQuickFixes(true);
}
showQuickfixes(): void {
this.setQuickFixes(false).then(() => this.quickFixAction.run());
}
async getQuickFixes(waitForModel: boolean): Promise<IAction[]> {
const codeActions = await this.getCodeActions(waitForModel);
return codeActions ? this.toActions(codeActions) : [];
}
private async setQuickFixes(waitForModel: boolean): Promise<void> {
const quickFixes = await this.getQuickFixes(waitForModel);
this.quickFixAction.quickFixes = quickFixes;
}
private getCodeActions(waitForModel: boolean): Promise<CodeAction[] | null> {
if (this.codeActionsPromise !== null) {
return this.codeActionsPromise;
}
return this.getModel(waitForModel)
.then(model => {
if (model) {
if (!this.codeActionsPromise) {
this.codeActionsPromise = createCancelablePromise(cancellationToken => {
console.log('Fetching code actions for ', this.marker.marker.message);
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 this.codeActionsPromise;
}
return null;
});
}
private toActions(codeActions: CodeAction[]): IAction[] {
return codeActions.map(codeAction => new Action(
codeAction.command ? codeAction.command.id : codeAction.title,
codeAction.title,
undefined,
true,
() => {
return this.openFileAtMarker(this.marker)
.then(() => applyCodeAction(codeAction, this.bulkEditService, this.commandService));
}));
}
private openFileAtMarker(element: Marker): Promise<void> {
const { resource, selection } = { resource: element.resource, selection: element.range };
return this.editorService.openEditor({
resource,
options: {
selection,
preserveFocus: true,
pinned: false,
revealIfVisible: true
},
}, ACTIVE_GROUP).then(() => undefined);
}
private getModel(waitForModel: boolean): Promise<ITextModel | null> {
const model = this.modelService.getModel(this.marker.resource);
if (model) {
return Promise.resolve(model);
}
if (waitForModel) {
if (this.modelPromise === null) {
this.modelPromise = createCancelablePromise(cancellationToken => {
return new Promise((c) => {
this._register(this.modelService.onModelAdded(model => {
if (isEqual(model.uri, this.marker.resource)) {
c(model);
}
}));
});
});
}
return this.modelPromise;
}
return Promise.resolve(null);
}
}
export class MarkersViewModel extends Disposable {
......@@ -512,6 +625,9 @@ export class MarkersViewModel extends Disposable {
private bulkUpdate: boolean = false;
private hoveredMarker: Marker;
private hoverDelayer: Delayer<void> = new Delayer<void>(300);
constructor(
multiline: boolean = true,
@IInstantiationService private instantiationService: IInstantiationService
......@@ -546,6 +662,9 @@ export class MarkersViewModel extends Disposable {
dispose(value.disposables);
}
this.markersViewStates.delete(marker.hash);
if (this.hoveredMarker === marker) {
this.hoveredMarker = null;
}
}
this.markersPerResource.delete(resource.toString());
}
......@@ -555,6 +674,24 @@ export class MarkersViewModel extends Disposable {
return value ? value.viewModel : null;
}
onMarkerMouseHover(marker: Marker): void {
this.hoveredMarker = marker;
this.hoverDelayer.trigger(() => {
if (this.hoveredMarker) {
const model = this.getViewModel(this.hoveredMarker);
if (model) {
model.showLightBulb();
}
}
});
}
onMarkerMouseLeave(marker: Marker): void {
if (this.hoveredMarker === marker) {
this.hoveredMarker = null;
}
}
private _multiline: boolean = true;
get multiline(): boolean {
return this._multiline;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册