Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Navigate to next/previous symbolic match in file #35740

Merged
merged 12 commits into from
Oct 24, 2017
132 changes: 127 additions & 5 deletions src/vs/editor/contrib/wordHighlighter/common/wordHighlighter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,28 @@ import { onUnexpectedExternalError } from 'vs/base/common/errors';
import { TPromise } from 'vs/base/common/winjs.base';
import { Range } from 'vs/editor/common/core/range';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { CommonEditorRegistry, commonEditorContribution } from 'vs/editor/common/editorCommonExtensions';
import { CommonEditorRegistry, commonEditorContribution, EditorAction, IActionOptions, editorAction } from 'vs/editor/common/editorCommonExtensions';
import { DocumentHighlight, DocumentHighlightKind, DocumentHighlightProviderRegistry } from 'vs/editor/common/modes';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Position } from 'vs/editor/common/core/position';
import { registerColor, editorSelectionHighlight, activeContrastBorder, overviewRulerSelectionHighlightForeground } from 'vs/platform/theme/common/colorRegistry';
import { registerThemingParticipant, themeColorFromId } from 'vs/platform/theme/common/themeService';
import { CursorChangeReason, ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations';
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { firstIndex } from 'vs/base/common/arrays';

export const editorWordHighlight = registerColor('editor.wordHighlightBackground', { dark: '#575757B8', light: '#57575740', hc: null }, nls.localize('wordHighlight', 'Background color of a symbol during read-access, like reading a variable.'));
export const editorWordHighlightStrong = registerColor('editor.wordHighlightStrongBackground', { dark: '#004972B8', light: '#0e639c40', hc: null }, nls.localize('wordHighlightStrong', 'Background color of a symbol during write-access, like writing to a variable.'));

export const overviewRulerWordHighlightForeground = registerColor('editorOverviewRuler.wordHighlightForeground', { dark: '#A0A0A0', light: '#A0A0A0', hc: '#A0A0A0' }, nls.localize('overviewRulerWordHighlightForeground', 'Overview ruler marker color for symbol highlights.'));
export const overviewRulerWordHighlightStrongForeground = registerColor('editorOverviewRuler.wordHighlightStrongForeground', { dark: '#C0A0C0', light: '#C0A0C0', hc: '#C0A0C0' }, nls.localize('overviewRulerWordHighlightStrongForeground', 'Overview ruler marker color for write-access symbol highlights.'));

export const ctxHasWordHighlights = new RawContextKey<boolean>('hasWordHighlights', false);

export function getOccurrencesAtPosition(model: editorCommon.IReadOnlyModel, position: Position): TPromise<DocumentHighlight[]> {

const orderedByScore = DocumentHighlightProviderRegistry.ordered(model);
Expand Down Expand Up @@ -76,13 +83,23 @@ class WordHighlighter {
private lastCursorPositionChangeTime: number = 0;
private renderDecorationsTimer: number = -1;

constructor(editor: editorCommon.ICommonCodeEditor) {
private _hasWordHighlights: IContextKey<boolean>;
private _ignorePositionChangeEvent: boolean;

constructor(editor: editorCommon.ICommonCodeEditor, contextKeyService: IContextKeyService) {
this.editor = editor;
this._hasWordHighlights = ctxHasWordHighlights.bindTo(contextKeyService);
this._ignorePositionChangeEvent = false;
this.occurrencesHighlight = this.editor.getConfiguration().contribInfo.occurrencesHighlight;
this.model = this.editor.getModel();
this.toUnhook = [];
this.toUnhook.push(editor.onDidChangeCursorPosition((e: ICursorPositionChangedEvent) => {

if (this._ignorePositionChangeEvent) {
// We are changing the position => ignore this event
return;
}

if (!this.occurrencesHighlight) {
// Early exit if nothing needs to be done!
// Leave some form of early exit check here if you wish to continue being a cursor position change listener ;)
Expand Down Expand Up @@ -127,10 +144,45 @@ class WordHighlighter {
this._run();
}

private _getSortedHighlights(): Range[] {
return this._decorationIds
.map((id) => this.model.getDecorationRange(id))
.sort(Range.compareRangesUsingStarts);
}

public moveNext() {
let highlights = this._getSortedHighlights();
let index = firstIndex(highlights, (range) => range.containsPosition(this.editor.getPosition()));
let newIndex = ((index + 1) % highlights.length);
let dest = highlights[newIndex];
try {
this._ignorePositionChangeEvent = true;
this.editor.setPosition(dest.getStartPosition());
this.editor.revealRangeInCenter(dest);
} finally {
this._ignorePositionChangeEvent = false;
}
}

public moveBack() {
let highlights = this._getSortedHighlights();
let index = firstIndex(highlights, (range) => range.containsPosition(this.editor.getPosition()));
let newIndex = ((index - 1 + highlights.length) % highlights.length);
let dest = highlights[newIndex];
try {
this._ignorePositionChangeEvent = true;
this.editor.setPosition(dest.getStartPosition());
this.editor.revealRangeInCenter(dest);
} finally {
this._ignorePositionChangeEvent = false;
}
}

private _removeDecorations(): void {
if (this._decorationIds.length > 0) {
// remove decorations
this._decorationIds = this.editor.deltaDecorations(this._decorationIds, []);
this._hasWordHighlights.set(false);
}
}

Expand Down Expand Up @@ -296,6 +348,7 @@ class WordHighlighter {
}

this._decorationIds = this.editor.deltaDecorations(this._decorationIds, decorations);
this._hasWordHighlights.set(this.hasDecorations());
}

private static _getDecorationOptions(kind: DocumentHighlightKind): ModelDecorationOptions {
Expand Down Expand Up @@ -344,15 +397,21 @@ class WordHighlighter {
}
}



@commonEditorContribution
class WordHighlighterContribution implements editorCommon.IEditorContribution {

private static ID = 'editor.contrib.wordHighlighter';

public static get(editor: editorCommon.ICommonCodeEditor): WordHighlighterContribution {
return editor.getContribution<WordHighlighterContribution>(WordHighlighterContribution.ID);
}

private wordHighligher: WordHighlighter;

constructor(editor: editorCommon.ICommonCodeEditor) {
this.wordHighligher = new WordHighlighter(editor);
constructor(editor: editorCommon.ICommonCodeEditor, @IContextKeyService contextKeyService: IContextKeyService) {
this.wordHighligher = new WordHighlighter(editor, contextKeyService);
}

public getId(): string {
Expand All @@ -366,6 +425,14 @@ class WordHighlighterContribution implements editorCommon.IEditorContribution {
return false;
}

public moveNext() {
this.wordHighligher.moveNext();
}

public moveBack() {
this.wordHighligher.moveBack();
}

public restoreViewState(state: boolean | undefined): void {
if (state) {
this.wordHighligher.restore();
Expand Down Expand Up @@ -398,4 +465,59 @@ registerThemingParticipant((theme, collector) => {
collector.addRule(`.monaco-editor .wordHighlightStrong { border: 1px dashed ${hcOutline}; box-sizing: border-box; }`);
}

});
});

class WordHighlightNavigationAction extends EditorAction {

private _isNext: boolean;

constructor(next: boolean, opts: IActionOptions) {
super(opts);
this._isNext = next;
}

public run(accessor: ServicesAccessor, editor: editorCommon.ICommonCodeEditor): void {
const controller = WordHighlighterContribution.get(editor);
if (!controller) {
return;
}

if (this._isNext) {
controller.moveNext();
} else {
controller.moveBack();
}
}
}

@editorAction
class NextWordHighlightAction extends WordHighlightNavigationAction {
constructor() {
super(true, {
id: 'editor.action.wordHighlight.next',
label: nls.localize('wordHighlight.next.label', "Go to Next Symbol Highlight"),
alias: 'Go to Next Symbol Highlight',
precondition: ctxHasWordHighlights,
kbOpts: {
kbExpr: EditorContextKeys.textFocus,
primary: KeyCode.F7
}
});
}
}

@editorAction
class PrevWordHighlightAction extends WordHighlightNavigationAction {
constructor() {
super(false, {
id: 'editor.action.wordHighlight.prev',
label: nls.localize('wordHighlight.previous.label', "Go to Previous Symbol Highlight"),
alias: 'Go to Previous Symbol Highlight',
precondition: ctxHasWordHighlights,
kbOpts: {
kbExpr: EditorContextKeys.textFocus,
primary: KeyMod.Shift | KeyCode.F7
}
});
}
}