提交 136bbd3b 编写于 作者: J Johannes Rieken

rework gotoError logic to be fit for #96708

上级 c650757b
......@@ -4,12 +4,11 @@
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { Emitter } from 'vs/base/common/event';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IMarker, IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers';
import { IMarker } from 'vs/platform/markers/common/markers';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
......@@ -18,194 +17,32 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { MarkerNavigationWidget } from './gotoErrorWidget';
import { compare } from 'vs/base/common/strings';
import { binarySearch, find } from 'vs/base/common/arrays';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { onUnexpectedError } from 'vs/base/common/errors';
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { isEqual } from 'vs/base/common/resources';
import { TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor';
import { Codicon, registerIcon } from 'vs/base/common/codicons';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
class MarkerModel {
private readonly _editor: ICodeEditor;
private _markers: IMarker[];
private _nextIdx: number;
private readonly _toUnbind = new DisposableStore();
private _ignoreSelectionChange: boolean;
private readonly _onCurrentMarkerChanged: Emitter<IMarker | undefined>;
private readonly _onMarkerSetChanged: Emitter<MarkerModel>;
constructor(editor: ICodeEditor, markers: IMarker[]) {
this._editor = editor;
this._markers = [];
this._nextIdx = -1;
this._ignoreSelectionChange = false;
this._onCurrentMarkerChanged = new Emitter<IMarker | undefined>();
this._onMarkerSetChanged = new Emitter<MarkerModel>();
this.setMarkers(markers);
// listen on editor
this._toUnbind.add(this._editor.onDidDispose(() => this.dispose()));
this._toUnbind.add(this._editor.onDidChangeCursorPosition(() => {
if (this._ignoreSelectionChange) {
return;
}
if (this.currentMarker && this._editor.getPosition() && Range.containsPosition(this.currentMarker, this._editor.getPosition()!)) {
return;
}
this._nextIdx = -1;
}));
}
public get onCurrentMarkerChanged() {
return this._onCurrentMarkerChanged.event;
}
public get onMarkerSetChanged() {
return this._onMarkerSetChanged.event;
}
public setMarkers(markers: IMarker[]): void {
let oldMarker = this._nextIdx >= 0 ? this._markers[this._nextIdx] : undefined;
this._markers = markers || [];
this._markers.sort(MarkerNavigationAction.compareMarker);
if (!oldMarker) {
this._nextIdx = -1;
} else {
this._nextIdx = Math.max(-1, binarySearch(this._markers, oldMarker, MarkerNavigationAction.compareMarker));
}
this._onMarkerSetChanged.fire(this);
}
public withoutWatchingEditorPosition(callback: () => void): void {
this._ignoreSelectionChange = true;
try {
callback();
} finally {
this._ignoreSelectionChange = false;
}
}
private _initIdx(fwd: boolean): void {
let found = false;
const position = this._editor.getPosition();
for (let i = 0; i < this._markers.length; i++) {
let range = Range.lift(this._markers[i]);
if (range.isEmpty() && this._editor.getModel()) {
const word = this._editor.getModel()!.getWordAtPosition(range.getStartPosition());
if (word) {
range = new Range(range.startLineNumber, word.startColumn, range.startLineNumber, word.endColumn);
}
}
if (position && (range.containsPosition(position) || position.isBeforeOrEqual(range.getStartPosition()))) {
this._nextIdx = i;
found = true;
break;
}
}
if (!found) {
// after the last change
this._nextIdx = fwd ? 0 : this._markers.length - 1;
}
if (this._nextIdx < 0) {
this._nextIdx = this._markers.length - 1;
}
}
get currentMarker(): IMarker | undefined {
return this.canNavigate() ? this._markers[this._nextIdx] : undefined;
}
set currentMarker(marker: IMarker | undefined) {
const idx = this._nextIdx;
this._nextIdx = -1;
if (marker) {
this._nextIdx = this.indexOf(marker);
}
if (this._nextIdx !== idx) {
this._onCurrentMarkerChanged.fire(marker);
}
}
public move(fwd: boolean, inCircles: boolean): boolean {
if (!this.canNavigate()) {
this._onCurrentMarkerChanged.fire(undefined);
return !inCircles;
}
let oldIdx = this._nextIdx;
let atEdge = false;
if (this._nextIdx === -1) {
this._initIdx(fwd);
} else if (fwd) {
if (inCircles || this._nextIdx + 1 < this._markers.length) {
this._nextIdx = (this._nextIdx + 1) % this._markers.length;
} else {
atEdge = true;
}
} else if (!fwd) {
if (inCircles || this._nextIdx > 0) {
this._nextIdx = (this._nextIdx - 1 + this._markers.length) % this._markers.length;
} else {
atEdge = true;
}
}
if (oldIdx !== this._nextIdx) {
const marker = this._markers[this._nextIdx];
this._onCurrentMarkerChanged.fire(marker);
}
return atEdge;
}
public canNavigate(): boolean {
return this._markers.length > 0;
}
public findMarkerAtPosition(pos: Position): IMarker | undefined {
return find(this._markers, marker => Range.containsPosition(marker, pos));
}
public get total() {
return this._markers.length;
}
public indexOf(marker: IMarker): number {
return 1 + this._markers.indexOf(marker);
}
public dispose(): void {
this._toUnbind.dispose();
}
}
import { IMarkerNavigationService, MarkerList } from 'vs/editor/contrib/gotoError/markerNavigationService';
export class MarkerController implements IEditorContribution {
public static readonly ID = 'editor.contrib.markerController';
static readonly ID = 'editor.contrib.markerController';
public static get(editor: ICodeEditor): MarkerController {
static get(editor: ICodeEditor): MarkerController {
return editor.getContribution<MarkerController>(MarkerController.ID);
}
private readonly _editor: ICodeEditor;
private _model: MarkerModel | null = null;
private _widget: MarkerNavigationWidget | null = null;
private readonly _widgetVisible: IContextKey<boolean>;
private readonly _disposeOnClose = new DisposableStore();
private readonly _sessionDispoables = new DisposableStore();
private _model?: MarkerList;
private _widget?: MarkerNavigationWidget;
constructor(
editor: ICodeEditor,
@IMarkerService private readonly _markerService: IMarkerService,
@IMarkerNavigationService private readonly _markerNavigationService: IMarkerNavigationService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@ICodeEditorService private readonly _editorService: ICodeEditorService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
......@@ -214,195 +51,134 @@ export class MarkerController implements IEditorContribution {
this._widgetVisible = CONTEXT_MARKERS_NAVIGATION_VISIBLE.bindTo(this._contextKeyService);
}
public dispose(): void {
dispose(): void {
this._cleanUp();
this._disposeOnClose.dispose();
this._sessionDispoables.dispose();
}
private _cleanUp(): void {
this._widgetVisible.reset();
this._disposeOnClose.clear();
this._widget = null;
this._model = null;
this._sessionDispoables.clear();
this._widget = undefined;
this._model = undefined;
}
public getOrCreateModel(): MarkerModel {
private _getOrCreateModel(uri: URI | undefined): MarkerList {
if (this._model) {
if (this._model && this._model.matches(uri)) {
return this._model;
}
let reusePosition = false;
if (this._model) {
reusePosition = true;
this._cleanUp();
}
const markers = this._getMarkers();
this._model = new MarkerModel(this._editor, markers);
this._markerService.onMarkerChanged(this._onMarkerChanged, this, this._disposeOnClose);
this._model = this._markerNavigationService.getMarkerList(uri);
if (reusePosition) {
this._model.move(true, this._editor.getModel()!, this._editor.getPosition()!);
}
this._widget = this._instantiationService.createInstance(MarkerNavigationWidget, this._editor);
this._widget.onDidClose(() => this.close(), this, this._sessionDispoables);
this._widgetVisible.set(true);
this._widget.onDidClose(() => this.closeMarkersNavigation(), this, this._disposeOnClose);
this._disposeOnClose.add(this._model);
this._disposeOnClose.add(this._widget);
this._sessionDispoables.add(this._model);
this._sessionDispoables.add(this._widget);
this._disposeOnClose.add(this._widget.onDidSelectRelatedInformation(related => {
this._editorService.openCodeEditor({
resource: related.resource,
options: { pinned: true, revealIfOpened: true, selection: Range.lift(related).collapseToStart() }
}, this._editor).then(undefined, onUnexpectedError);
this.closeMarkersNavigation(false);
}));
this._disposeOnClose.add(this._editor.onDidChangeModel(() => this._cleanUp()));
this._disposeOnClose.add(this._model.onCurrentMarkerChanged(marker => {
if (!marker || !this._model) {
this._cleanUp();
} else {
this._model.withoutWatchingEditorPosition(() => {
if (!this._widget || !this._model) {
return;
}
this._widget.showAtMarker(marker, this._model.indexOf(marker), this._model.total);
});
// follow cursor
this._sessionDispoables.add(this._editor.onDidChangeCursorPosition(e => {
if (!this._model?.selected || !Range.containsPosition(this._model?.selected.marker, e.position)) {
this._model?.resetIndex();
}
}));
this._disposeOnClose.add(this._model.onMarkerSetChanged(() => {
// update markers
this._sessionDispoables.add(this._model.onDidChange(() => {
if (!this._widget || !this._widget.position || !this._model) {
return;
}
const marker = this._model.findMarkerAtPosition(this._widget.position);
if (marker) {
this._widget.updateMarker(marker);
const info = this._model.find(this._editor.getModel()!.uri, this._widget!.position!);
if (info) {
this._widget.updateMarker(info.marker);
} else {
this._widget.showStale();
}
}));
// open related
this._sessionDispoables.add(this._widget.onDidSelectRelatedInformation(related => {
this._editorService.openCodeEditor({
resource: related.resource,
options: { pinned: true, revealIfOpened: true, selection: Range.lift(related).collapseToStart() }
}, this._editor);
this.close(false);
}));
this._sessionDispoables.add(this._editor.onDidChangeModel(() => this._cleanUp()));
return this._model;
}
public closeMarkersNavigation(focusEditor: boolean = true): void {
close(focusEditor: boolean = true): void {
this._cleanUp();
if (focusEditor) {
this._editor.focus();
}
}
public show(marker: IMarker): void {
const model = this.getOrCreateModel();
model.currentMarker = marker;
}
private _onMarkerChanged(changedResources: readonly URI[]): void {
const editorModel = this._editor.getModel();
if (!editorModel) {
return;
}
if (!this._model) {
return;
}
if (!changedResources.some(r => isEqual(editorModel.uri, r))) {
return;
showAtMarker(marker: IMarker): void {
if (this._editor.hasModel()) {
const model = this._getOrCreateModel(this._editor.getModel().uri);
model.resetIndex();
model.move(true, this._editor.getModel(), new Position(marker.startLineNumber, marker.startColumn));
if (model.selected) {
this._widget!.showAtMarker(model.selected.marker, model.selected.index, model.selected.total);
}
}
this._model.setMarkers(this._getMarkers());
}
private _getMarkers(): IMarker[] {
let model = this._editor.getModel();
if (!model) {
return [];
}
async nagivate(next: boolean, multiFile: boolean) {
if (this._editor.hasModel()) {
const model = this._getOrCreateModel(multiFile ? undefined : this._editor.getModel().uri);
model.move(next, this._editor.getModel(), this._editor.getPosition());
if (!model.selected) {
return;
}
if (model.selected.marker.resource.toString() !== this._editor.getModel().uri.toString()) {
// show in different editor
this._cleanUp();
const otherEditor = await this._editorService.openCodeEditor({
resource: model.selected.marker.resource,
options: { pinned: false, revealIfOpened: true, selectionRevealType: TextEditorSelectionRevealType.NearTop, selection: model.selected.marker }
}, this._editor);
if (otherEditor) {
MarkerController.get(otherEditor).close();
MarkerController.get(otherEditor).nagivate(next, multiFile);
}
return this._markerService.read({
resource: model.uri,
severities: MarkerSeverity.Error | MarkerSeverity.Warning | MarkerSeverity.Info
});
} else {
// show in this editor
this._widget!.showAtMarker(model.selected.marker, model.selected.index, model.selected.total);
}
}
}
}
class MarkerNavigationAction extends EditorAction {
private readonly _isNext: boolean;
private readonly _multiFile: boolean;
constructor(next: boolean, multiFile: boolean, opts: IActionOptions) {
constructor(
private readonly _next: boolean,
private readonly _multiFile: boolean,
opts: IActionOptions
) {
super(opts);
this._isNext = next;
this._multiFile = multiFile;
}
public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
const markerService = accessor.get(IMarkerService);
const editorService = accessor.get(ICodeEditorService);
const controller = MarkerController.get(editor);
if (!controller) {
return Promise.resolve(undefined);
}
const model = controller.getOrCreateModel();
const atEdge = model.move(this._isNext, !this._multiFile);
if (!atEdge || !this._multiFile) {
return Promise.resolve(undefined);
}
// try with the next/prev file
let markers = markerService.read({ severities: MarkerSeverity.Error | MarkerSeverity.Warning | MarkerSeverity.Info }).sort(MarkerNavigationAction.compareMarker);
if (markers.length === 0) {
return Promise.resolve(undefined);
}
const editorModel = editor.getModel();
if (!editorModel) {
return Promise.resolve(undefined);
}
let oldMarker = model.currentMarker || <IMarker>{ resource: editorModel!.uri, severity: MarkerSeverity.Error, startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 };
let idx = binarySearch(markers, oldMarker, MarkerNavigationAction.compareMarker);
if (idx < 0) {
// find best match...
idx = ~idx;
idx %= markers.length;
} else if (this._isNext) {
idx = (idx + 1) % markers.length;
} else {
idx = (idx + markers.length - 1) % markers.length;
}
let newMarker = markers[idx];
if (isEqual(newMarker.resource, editorModel.uri)) {
// the next `resource` is this resource which
// means we cycle within this file
model.move(this._isNext, true);
return Promise.resolve(undefined);
}
// close the widget for this editor-instance, open the resource
// for the next marker and re-start marker navigation in there
controller.closeMarkersNavigation();
return editorService.openCodeEditor({
resource: newMarker.resource,
options: { pinned: false, revealIfOpened: true, selectionRevealType: TextEditorSelectionRevealType.NearTop, selection: newMarker }
}, editor).then(editor => {
if (!editor) {
return undefined;
}
return editor.getAction(this.id).run();
});
}
static compareMarker(a: IMarker, b: IMarker): number {
let res = compare(a.resource.toString(), b.resource.toString());
if (res === 0) {
res = MarkerSeverity.compare(a.severity, b.severity);
}
if (res === 0) {
res = Range.compareRangesUsingStarts(a, b);
async run(_accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
if (editor.hasModel()) {
MarkerController.get(editor).nagivate(this._next, this._multiFile);
}
return res;
}
}
......@@ -501,7 +277,7 @@ const MarkerCommand = EditorCommand.bindToContribution<MarkerController>(MarkerC
registerEditorCommand(new MarkerCommand({
id: 'closeMarkersNavigation',
precondition: CONTEXT_MARKERS_NAVIGATION_VISIBLE,
handler: x => x.closeMarkersNavigation(),
handler: x => x.close(),
kbOpts: {
weight: KeybindingWeight.EditorContrib + 50,
kbExpr: EditorContextKeys.focus,
......
......@@ -8,7 +8,6 @@ import * as nls from 'vs/nls';
import * as dom from 'vs/base/browser/dom';
import { dispose, DisposableStore } from 'vs/base/common/lifecycle';
import { IMarker, MarkerSeverity, IRelatedInformation } from 'vs/platform/markers/common/markers';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { registerColor, oneOf, textLinkForeground, editorErrorForeground, editorErrorBorder, editorWarningForeground, editorWarningBorder, editorInfoForeground, editorInfoBorder } from 'vs/platform/theme/common/colorRegistry';
......@@ -296,6 +295,9 @@ export class MarkerNavigationWidget extends PeekViewWidget {
protected _fillHead(container: HTMLElement): void {
super._fillHead(container);
this._disposables.add(this._actionbarWidget!.actionRunner.onDidBeforeRun(e => this.editor.focus()));
const actions: IAction[] = [];
const menu = this._menuService.createMenu(MarkerNavigationWidget.TitleMenu, this._contextKeyService);
createAndFillInActionBarActions(menu, undefined, actions);
......@@ -327,7 +329,7 @@ export class MarkerNavigationWidget extends PeekViewWidget {
this._disposables.add(this._message);
}
show(where: Position, heightInLines: number): void {
show(): void {
throw new Error('call showAtMarker');
}
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IMarkerService, MarkerSeverity, IMarker } from 'vs/platform/markers/common/markers';
import { URI } from 'vs/base/common/uri';
import { Emitter, Event } from 'vs/base/common/event';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { compare } from 'vs/base/common/strings';
import { binarySearch } from 'vs/base/common/arrays';
import { ITextModel } from 'vs/editor/common/model';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
export class MarkerCoordinate {
constructor(
readonly marker: IMarker,
readonly index: number,
readonly total: number
) { }
}
export class MarkerList {
private readonly _onDidChange = new Emitter<void>();
readonly onDidChange: Event<void> = this._onDidChange.event;
private readonly _dispoables = new DisposableStore();
private _markers: IMarker[] = [];
private _nextIdx: number = -1;
constructor(
private readonly _scope: URI | undefined,
@IMarkerService private readonly _markerService: IMarkerService,
) {
const filter = { resource: this._scope, severities: MarkerSeverity.Error | MarkerSeverity.Warning | MarkerSeverity.Info };
this._markers = this._markerService.read(filter).sort(MarkerList._compareMarker);
this._dispoables.add(_markerService.onMarkerChanged(e => {
if (!this._scope || e.some(e => e.toString() === _scope?.toString())) {
this._markers = this._markerService.read(filter).sort(MarkerList._compareMarker);
this._nextIdx = -1;
this._onDidChange.fire();
}
}));
}
dispose(): void {
this._dispoables.dispose();
this._onDidChange.dispose();
}
matches(uri: URI | undefined) {
if (this._scope === uri) {
return true;
}
if (this._scope && uri && this._scope.toString() === uri.toString()) {
return true;
}
return false;
}
get selected(): MarkerCoordinate | undefined {
const marker = this._markers[this._nextIdx];
return marker && new MarkerCoordinate(marker, this._nextIdx + 1, this._markers.length);
}
private _initIdx(model: ITextModel, position: Position, fwd: boolean): void {
let found = false;
let idx = this._markers.findIndex(marker => marker.resource.toString() === model.uri.toString());
if (idx < 0) {
idx = binarySearch(this._markers, <any>{ resource: model.uri }, (a, b) => compare(a.resource.toString(), b.resource.toString()));
if (idx < 0) {
idx = ~idx;
}
}
for (let i = idx; i < this._markers.length; i++) {
let range = Range.lift(this._markers[i]);
if (range.isEmpty()) {
const word = model.getWordAtPosition(range.getStartPosition());
if (word) {
range = new Range(range.startLineNumber, word.startColumn, range.startLineNumber, word.endColumn);
}
}
if (position && (range.containsPosition(position) || position.isBeforeOrEqual(range.getStartPosition()))) {
this._nextIdx = i;
found = true;
break;
}
if (this._markers[i].resource.toString() !== model.uri.toString()) {
break;
}
}
if (!found) {
// after the last change
this._nextIdx = fwd ? 0 : this._markers.length - 1;
}
if (this._nextIdx < 0) {
this._nextIdx = this._markers.length - 1;
}
}
resetIndex() {
this._nextIdx = -1;
}
move(fwd: boolean, model: ITextModel, position: Position): boolean {
if (this._markers.length === 0) {
return false;
}
let oldIdx = this._nextIdx;
if (this._nextIdx === -1) {
this._initIdx(model, position, fwd);
} else if (fwd) {
this._nextIdx = (this._nextIdx + 1) % this._markers.length;
} else if (!fwd) {
this._nextIdx = (this._nextIdx - 1 + this._markers.length) % this._markers.length;
}
if (oldIdx !== this._nextIdx) {
return true;
}
return false;
}
find(uri: URI, position: Position): MarkerCoordinate | undefined {
let idx = this._markers.findIndex(marker => marker.resource.toString() === uri.toString());
if (idx < 0) {
return undefined;
}
for (; idx < this._markers.length; idx++) {
if (Range.containsPosition(this._markers[idx], position)) {
return new MarkerCoordinate(this._markers[idx], idx + 1, this._markers.length);
}
}
return undefined;
}
private static _compareMarker(a: IMarker, b: IMarker): number {
let res = compare(a.resource.toString(), b.resource.toString());
if (res === 0) {
res = MarkerSeverity.compare(a.severity, b.severity);
}
if (res === 0) {
res = Range.compareRangesUsingStarts(a, b);
}
return res;
}
}
export const IMarkerNavigationService = createDecorator<IMarkerNavigationService>('IMarkerNavigationService');
export interface IMarkerNavigationService {
readonly _serviceBrand: undefined;
getMarkerList(resource: URI | undefined): MarkerList;
}
class MarkerNavigationService implements IMarkerNavigationService {
readonly _serviceBrand: undefined;
constructor(@IMarkerService private readonly _markerService: IMarkerService) { }
getMarkerList(resource: URI | undefined) {
return new MarkerList(resource, this._markerService);
}
}
registerSingleton(IMarkerNavigationService, MarkerNavigationService, true);
......@@ -32,7 +32,7 @@
user-select: text;
-webkit-user-select: text;
-ms-user-select: text;
padding: 8px 12px 0px 20px;
padding: 8px 12px 0 20px;
}
.monaco-editor .marker-widget .descriptioncontainer .message {
......
......@@ -567,7 +567,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
commandId: NextMarkerAction.ID,
run: () => {
this.hide();
MarkerController.get(this._editor).show(markerHover.marker);
MarkerController.get(this._editor).showAtMarker(markerHover.marker);
this._editor.focus();
}
}));
......@@ -686,4 +686,3 @@ registerThemingParticipant((theme, collector) => {
collector.addRule(`.monaco-editor-hover .hover-contents a.code-link span:hover { color: ${linkFg}; }`);
}
});
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册