diff --git a/src/vs/editor/contrib/gotoError/gotoError.ts b/src/vs/editor/contrib/gotoError/gotoError.ts index 2b2b716642fbe..6d51226ce4687 100644 --- a/src/vs/editor/contrib/gotoError/gotoError.ts +++ b/src/vs/editor/contrib/gotoError/gotoError.ts @@ -17,12 +17,16 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { registerEditorAction, registerEditorContribution, ServicesAccessor, IActionOptions, EditorAction, EditorCommand, registerEditorCommand } from 'vs/editor/browser/editorExtensions'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { MarkerNavigationWidget } from './gotoErrorWidget'; +import { compare } from 'vs/base/common/strings'; +import { binarySearch } from 'vs/base/common/arrays'; +import { IEditorService } from 'vs/platform/editor/common/editor'; +import { TPromise } from 'vs/base/common/winjs.base'; class MarkerModel { @@ -47,9 +51,13 @@ class MarkerModel { // listen on editor this._toUnbind.push(this._editor.onDidDispose(() => this.dispose())); this._toUnbind.push(this._editor.onDidChangeCursorPosition(() => { - if (!this._ignoreSelectionChange) { - this._nextIdx = -1; + if (this._ignoreSelectionChange) { + return; } + if (this.currentMarker && Range.containsPosition(this.currentMarker, this._editor.getPosition())) { + return; + } + this._nextIdx = -1; })); } @@ -62,13 +70,15 @@ class MarkerModel { } public setMarkers(markers: IMarker[]): void { - // assign - this._markers = markers || []; - - // sort markers - this._markers.sort((left, right) => Severity.compare(left.severity, right.severity) || Range.compareRangesUsingStarts(left, right)); - this._nextIdx = -1; + 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); } @@ -95,7 +105,7 @@ class MarkerModel { } if (range.containsPosition(position) || position.isBeforeOrEqual(range.getStartPosition())) { - this._nextIdx = i + (fwd ? 0 : -1); + this._nextIdx = i; found = true; break; } @@ -109,29 +119,43 @@ class MarkerModel { } } - public move(fwd: boolean, circle: boolean): boolean { + get currentMarker(): IMarker { + return this.canNavigate() ? this._markers[this._nextIdx] : undefined; + } + + public move(fwd: boolean, inCircles: boolean): boolean { if (!this.canNavigate()) { this._onCurrentMarkerChanged.fire(undefined); - return false; + return !inCircles; } - let old = this._nextIdx; + let oldIdx = this._nextIdx; + let atEdge = false; + if (this._nextIdx === -1) { this._initIdx(fwd); + } else if (fwd) { - this._nextIdx = (this._nextIdx + 1) % this._markers.length; - } else { - this._nextIdx = (this._nextIdx - 1 + this._markers.length) % this._markers.length; + 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 (circle || old === -1 || fwd && old < this._nextIdx || !fwd && old > this._nextIdx) { + if (oldIdx !== this._nextIdx) { const marker = this._markers[this._nextIdx]; this._onCurrentMarkerChanged.fire(marker); - return false; } - // we circled, didn't send an event, and return `true` - return true; + return atEdge; } public canNavigate(): boolean { @@ -160,26 +184,6 @@ class MarkerModel { } } -class MarkerNavigationAction extends EditorAction { - - private _isNext: boolean; - - constructor(next: boolean, opts: IActionOptions) { - super(opts); - this._isNext = next; - } - - public run(accessor: ServicesAccessor, editor: ICodeEditor): void { - const controller = MarkerController.get(editor); - if (!controller) { - return; - } - - const model = controller.getOrCreateModel(); - model.move(this._isNext, true); - } -} - class MarkerController implements editorCommon.IEditorContribution { private static readonly ID = 'editor.contrib.markerController'; @@ -274,6 +278,79 @@ class MarkerController implements editorCommon.IEditorContribution { } } +class MarkerNavigationAction extends EditorAction { + + private _isNext: boolean; + + constructor(next: boolean, opts: IActionOptions) { + super(opts); + this._isNext = next; + } + + public run(accessor: ServicesAccessor, editor: ICodeEditor): TPromise { + + const markerService = accessor.get(IMarkerService); + const editorService = accessor.get(IEditorService); + const controller = MarkerController.get(editor); + if (!controller) { + return undefined; + } + + const model = controller.getOrCreateModel(); + const atEdge = model.move(this._isNext, false); + if (!atEdge) { + return undefined; + } + + // try with the next/prev file + let oldMarker = model.currentMarker || { resource: editor.getModel().uri, severity: Severity.Error, startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }; + let markers = markerService.read().sort(MarkerNavigationAction.compareMarker); + 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 (newMarker.resource.toString() === editor.getModel().uri.toString()) { + // the next `resource` is this resource which + // means we cycle within this file + model.move(this._isNext, true); + return 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.openEditor({ + resource: newMarker.resource, + options: { pinned: false, revealIfOpened: true, revealInCenterIfOutsideViewport: true, selection: newMarker } + }).then(editor => { + if (!editor || !isCodeEditor(editor.getControl())) { + return undefined; + } + return (editor.getControl()).getAction(this.id).run(); + }); + } + + static compareMarker(a: IMarker, b: IMarker): number { + let res = compare(a.resource.toString(), b.resource.toString()); + if (res === 0) { + res = Severity.compare(a.severity, b.severity); + } + if (res === 0) { + res = Range.compareRangesUsingStarts(a, b); + } + return res; + } +} + class NextMarkerAction extends MarkerNavigationAction { constructor() { super(true, {