mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-22 04:40:30 -04:00
Refresh master with initial release/0.24 snapshot (#332)
* Initial port of release/0.24 source code * Fix additional headers * Fix a typo in launch.json
This commit is contained in:
@@ -5,14 +5,24 @@
|
||||
'use strict';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { ICommonCodeEditor, ScrollType } from 'vs/editor/common/editorCommon';
|
||||
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { KeyCode, KeyMod, KeyChord } from 'vs/base/common/keyCodes';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { ICommonCodeEditor, ScrollType, IEditorContribution, FindMatch, TrackedRangeStickiness, OverviewRulerLane, IModel } from 'vs/editor/common/editorCommon';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { editorAction, ServicesAccessor, EditorAction } from 'vs/editor/common/editorCommonExtensions';
|
||||
import { editorAction, commonEditorContribution, ServicesAccessor, EditorAction } from 'vs/editor/common/editorCommonExtensions';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents';
|
||||
import { CursorChangeReason, ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
|
||||
import { CursorMoveCommands } from 'vs/editor/common/controller/cursorMoveCommands';
|
||||
import { CursorState, RevealTarget } from 'vs/editor/common/controller/cursorCommon';
|
||||
import { Constants } from 'vs/editor/common/core/uint';
|
||||
import { DocumentHighlightProviderRegistry } from 'vs/editor/common/modes';
|
||||
import { CommonFindController } from 'vs/editor/contrib/find/common/findController';
|
||||
import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations';
|
||||
import { overviewRulerSelectionHighlightForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { themeColorFromId } from 'vs/platform/theme/common/themeService';
|
||||
import { INewFindReplaceState, FindOptionOverride } from 'vs/editor/contrib/find/common/findState';
|
||||
|
||||
@editorAction
|
||||
export class InsertCursorAbove extends EditorAction {
|
||||
@@ -139,3 +149,764 @@ class InsertCursorAtEndOfEachLineSelected extends EditorAction {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class MultiCursorSessionResult {
|
||||
constructor(
|
||||
public readonly selections: Selection[],
|
||||
public readonly revealRange: Range,
|
||||
public readonly revealScrollType: ScrollType
|
||||
) { }
|
||||
}
|
||||
|
||||
export class MultiCursorSession {
|
||||
|
||||
public static create(editor: ICommonCodeEditor, findController: CommonFindController): MultiCursorSession {
|
||||
const findState = findController.getState();
|
||||
|
||||
// Find widget owns entirely what we search for if:
|
||||
// - focus is not in the editor (i.e. it is in the find widget)
|
||||
// - and the search widget is visible
|
||||
// - and the search string is non-empty
|
||||
if (!editor.isFocused() && findState.isRevealed && findState.searchString.length > 0) {
|
||||
// Find widget owns what is searched for
|
||||
return new MultiCursorSession(editor, findController, false, findState.searchString, findState.wholeWord, findState.matchCase, null);
|
||||
}
|
||||
|
||||
// Otherwise, the selection gives the search text, and the find widget gives the search settings
|
||||
// The exception is the find state disassociation case: when beginning with a single, collapsed selection
|
||||
let isDisconnectedFromFindController = false;
|
||||
let wholeWord: boolean;
|
||||
let matchCase: boolean;
|
||||
const selections = editor.getSelections();
|
||||
if (selections.length === 1 && selections[0].isEmpty()) {
|
||||
isDisconnectedFromFindController = true;
|
||||
wholeWord = true;
|
||||
matchCase = true;
|
||||
} else {
|
||||
wholeWord = findState.wholeWord;
|
||||
matchCase = findState.matchCase;
|
||||
}
|
||||
|
||||
// Selection owns what is searched for
|
||||
const s = editor.getSelection();
|
||||
|
||||
let searchText: string;
|
||||
let currentMatch: Selection = null;
|
||||
|
||||
if (s.isEmpty()) {
|
||||
// selection is empty => expand to current word
|
||||
const word = editor.getModel().getWordAtPosition(s.getStartPosition());
|
||||
if (!word) {
|
||||
return null;
|
||||
}
|
||||
searchText = word.word;
|
||||
currentMatch = new Selection(s.startLineNumber, word.startColumn, s.startLineNumber, word.endColumn);
|
||||
} else {
|
||||
searchText = editor.getModel().getValueInRange(s).replace(/\r\n/g, '\n');
|
||||
}
|
||||
|
||||
return new MultiCursorSession(editor, findController, isDisconnectedFromFindController, searchText, wholeWord, matchCase, currentMatch);
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly _editor: ICommonCodeEditor,
|
||||
public readonly findController: CommonFindController,
|
||||
public readonly isDisconnectedFromFindController: boolean,
|
||||
public readonly searchText: string,
|
||||
public readonly wholeWord: boolean,
|
||||
public readonly matchCase: boolean,
|
||||
public currentMatch: Selection
|
||||
) { }
|
||||
|
||||
public addSelectionToNextFindMatch(): MultiCursorSessionResult {
|
||||
const nextMatch = this._getNextMatch();
|
||||
if (!nextMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const allSelections = this._editor.getSelections();
|
||||
return new MultiCursorSessionResult(allSelections.concat(nextMatch), nextMatch, ScrollType.Smooth);
|
||||
}
|
||||
|
||||
public moveSelectionToNextFindMatch(): MultiCursorSessionResult {
|
||||
const nextMatch = this._getNextMatch();
|
||||
if (!nextMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const allSelections = this._editor.getSelections();
|
||||
return new MultiCursorSessionResult(allSelections.slice(0, allSelections.length - 1).concat(nextMatch), nextMatch, ScrollType.Smooth);
|
||||
}
|
||||
|
||||
private _getNextMatch(): Selection {
|
||||
if (this.currentMatch) {
|
||||
const result = this.currentMatch;
|
||||
this.currentMatch = null;
|
||||
return result;
|
||||
}
|
||||
|
||||
this.findController.highlightFindOptions();
|
||||
|
||||
const allSelections = this._editor.getSelections();
|
||||
const lastAddedSelection = allSelections[allSelections.length - 1];
|
||||
const nextMatch = this._editor.getModel().findNextMatch(this.searchText, lastAddedSelection.getEndPosition(), false, this.matchCase, this.wholeWord ? this._editor.getConfiguration().wordSeparators : null, false);
|
||||
|
||||
if (!nextMatch) {
|
||||
return null;
|
||||
}
|
||||
return new Selection(nextMatch.range.startLineNumber, nextMatch.range.startColumn, nextMatch.range.endLineNumber, nextMatch.range.endColumn);
|
||||
}
|
||||
|
||||
public addSelectionToPreviousFindMatch(): MultiCursorSessionResult {
|
||||
const previousMatch = this._getPreviousMatch();
|
||||
if (!previousMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const allSelections = this._editor.getSelections();
|
||||
return new MultiCursorSessionResult(allSelections.concat(previousMatch), previousMatch, ScrollType.Smooth);
|
||||
}
|
||||
|
||||
public moveSelectionToPreviousFindMatch(): MultiCursorSessionResult {
|
||||
const previousMatch = this._getPreviousMatch();
|
||||
if (!previousMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const allSelections = this._editor.getSelections();
|
||||
return new MultiCursorSessionResult(allSelections.slice(0, allSelections.length - 1).concat(previousMatch), previousMatch, ScrollType.Smooth);
|
||||
}
|
||||
|
||||
private _getPreviousMatch(): Selection {
|
||||
if (this.currentMatch) {
|
||||
const result = this.currentMatch;
|
||||
this.currentMatch = null;
|
||||
return result;
|
||||
}
|
||||
|
||||
this.findController.highlightFindOptions();
|
||||
|
||||
const allSelections = this._editor.getSelections();
|
||||
const lastAddedSelection = allSelections[allSelections.length - 1];
|
||||
const previousMatch = this._editor.getModel().findPreviousMatch(this.searchText, lastAddedSelection.getStartPosition(), false, this.matchCase, this.wholeWord ? this._editor.getConfiguration().wordSeparators : null, false);
|
||||
|
||||
if (!previousMatch) {
|
||||
return null;
|
||||
}
|
||||
return new Selection(previousMatch.range.startLineNumber, previousMatch.range.startColumn, previousMatch.range.endLineNumber, previousMatch.range.endColumn);
|
||||
}
|
||||
|
||||
public selectAll(): FindMatch[] {
|
||||
this.findController.highlightFindOptions();
|
||||
|
||||
return this._editor.getModel().findMatches(this.searchText, true, false, this.matchCase, this.wholeWord ? this._editor.getConfiguration().wordSeparators : null, false, Constants.MAX_SAFE_SMALL_INTEGER);
|
||||
}
|
||||
}
|
||||
|
||||
@commonEditorContribution
|
||||
export class MultiCursorSelectionController extends Disposable implements IEditorContribution {
|
||||
|
||||
private static ID = 'editor.contrib.multiCursorController';
|
||||
|
||||
private readonly _editor: ICommonCodeEditor;
|
||||
private _ignoreSelectionChange: boolean;
|
||||
private _session: MultiCursorSession;
|
||||
private _sessionDispose: IDisposable[];
|
||||
|
||||
public static get(editor: ICommonCodeEditor): MultiCursorSelectionController {
|
||||
return editor.getContribution<MultiCursorSelectionController>(MultiCursorSelectionController.ID);
|
||||
}
|
||||
|
||||
constructor(editor: ICommonCodeEditor) {
|
||||
super();
|
||||
this._editor = editor;
|
||||
this._ignoreSelectionChange = false;
|
||||
this._session = null;
|
||||
this._sessionDispose = [];
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._endSession();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public getId(): string {
|
||||
return MultiCursorSelectionController.ID;
|
||||
}
|
||||
|
||||
private _beginSessionIfNeeded(findController: CommonFindController): void {
|
||||
if (!this._session) {
|
||||
// Create a new session
|
||||
const session = MultiCursorSession.create(this._editor, findController);
|
||||
if (!session) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._session = session;
|
||||
|
||||
const newState: INewFindReplaceState = { searchString: this._session.searchText };
|
||||
if (this._session.isDisconnectedFromFindController) {
|
||||
newState.wholeWordOverride = FindOptionOverride.True;
|
||||
newState.matchCaseOverride = FindOptionOverride.True;
|
||||
newState.isRegexOverride = FindOptionOverride.False;
|
||||
}
|
||||
findController.getState().change(newState, false);
|
||||
|
||||
this._sessionDispose = [
|
||||
this._editor.onDidChangeCursorSelection((e) => {
|
||||
if (this._ignoreSelectionChange) {
|
||||
return;
|
||||
}
|
||||
this._endSession();
|
||||
}),
|
||||
this._editor.onDidBlurEditorText(() => {
|
||||
this._endSession();
|
||||
}),
|
||||
findController.getState().addChangeListener((e) => {
|
||||
if (e.matchCase || e.wholeWord) {
|
||||
this._endSession();
|
||||
}
|
||||
})
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
private _endSession(): void {
|
||||
this._sessionDispose = dispose(this._sessionDispose);
|
||||
if (this._session && this._session.isDisconnectedFromFindController) {
|
||||
const newState: INewFindReplaceState = {
|
||||
wholeWordOverride: FindOptionOverride.NotSet,
|
||||
matchCaseOverride: FindOptionOverride.NotSet,
|
||||
isRegexOverride: FindOptionOverride.NotSet,
|
||||
};
|
||||
this._session.findController.getState().change(newState, false);
|
||||
}
|
||||
this._session = null;
|
||||
}
|
||||
|
||||
private _setSelections(selections: Selection[]): void {
|
||||
this._ignoreSelectionChange = true;
|
||||
this._editor.setSelections(selections);
|
||||
this._ignoreSelectionChange = false;
|
||||
}
|
||||
|
||||
private _expandEmptyToWord(model: IModel, selection: Selection): Selection {
|
||||
if (!selection.isEmpty()) {
|
||||
return selection;
|
||||
}
|
||||
const word = model.getWordAtPosition(selection.getStartPosition());
|
||||
if (!word) {
|
||||
return selection;
|
||||
}
|
||||
return new Selection(selection.startLineNumber, word.startColumn, selection.startLineNumber, word.endColumn);
|
||||
}
|
||||
|
||||
private _applySessionResult(result: MultiCursorSessionResult): void {
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
this._setSelections(result.selections);
|
||||
if (result.revealRange) {
|
||||
this._editor.revealRangeInCenterIfOutsideViewport(result.revealRange, result.revealScrollType);
|
||||
}
|
||||
}
|
||||
|
||||
public getSession(findController: CommonFindController): MultiCursorSession {
|
||||
return this._session;
|
||||
}
|
||||
|
||||
public addSelectionToNextFindMatch(findController: CommonFindController): void {
|
||||
if (!this._session) {
|
||||
// If there are multiple cursors, handle the case where they do not all select the same text.
|
||||
const allSelections = this._editor.getSelections();
|
||||
if (allSelections.length > 1) {
|
||||
const findState = findController.getState();
|
||||
const matchCase = findState.matchCase;
|
||||
const selectionsContainSameText = modelRangesContainSameText(this._editor.getModel(), allSelections, matchCase);
|
||||
if (!selectionsContainSameText) {
|
||||
const model = this._editor.getModel();
|
||||
let resultingSelections: Selection[] = [];
|
||||
for (let i = 0, len = allSelections.length; i < len; i++) {
|
||||
resultingSelections[i] = this._expandEmptyToWord(model, allSelections[i]);
|
||||
}
|
||||
this._editor.setSelections(resultingSelections);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
this._beginSessionIfNeeded(findController);
|
||||
if (this._session) {
|
||||
this._applySessionResult(this._session.addSelectionToNextFindMatch());
|
||||
}
|
||||
}
|
||||
|
||||
public addSelectionToPreviousFindMatch(findController: CommonFindController): void {
|
||||
this._beginSessionIfNeeded(findController);
|
||||
if (this._session) {
|
||||
this._applySessionResult(this._session.addSelectionToPreviousFindMatch());
|
||||
}
|
||||
}
|
||||
|
||||
public moveSelectionToNextFindMatch(findController: CommonFindController): void {
|
||||
this._beginSessionIfNeeded(findController);
|
||||
if (this._session) {
|
||||
this._applySessionResult(this._session.moveSelectionToNextFindMatch());
|
||||
}
|
||||
}
|
||||
|
||||
public moveSelectionToPreviousFindMatch(findController: CommonFindController): void {
|
||||
this._beginSessionIfNeeded(findController);
|
||||
if (this._session) {
|
||||
this._applySessionResult(this._session.moveSelectionToPreviousFindMatch());
|
||||
}
|
||||
}
|
||||
|
||||
public selectAll(findController: CommonFindController): void {
|
||||
let matches: FindMatch[] = null;
|
||||
|
||||
const findState = findController.getState();
|
||||
|
||||
// Special case: find widget owns entirely what we search for if:
|
||||
// - focus is not in the editor (i.e. it is in the find widget)
|
||||
// - and the search widget is visible
|
||||
// - and the search string is non-empty
|
||||
// - and we're searching for a regex
|
||||
if (!this._editor.isFocused() && findState.isRevealed && findState.searchString.length > 0 && findState.isRegex) {
|
||||
|
||||
matches = this._editor.getModel().findMatches(findState.searchString, true, findState.isRegex, findState.matchCase, findState.wholeWord ? this._editor.getConfiguration().wordSeparators : null, false, Constants.MAX_SAFE_SMALL_INTEGER);
|
||||
|
||||
} else {
|
||||
|
||||
this._beginSessionIfNeeded(findController);
|
||||
if (!this._session) {
|
||||
return;
|
||||
}
|
||||
|
||||
matches = this._session.selectAll();
|
||||
}
|
||||
|
||||
if (matches.length > 0) {
|
||||
const editorSelection = this._editor.getSelection();
|
||||
// Have the primary cursor remain the one where the action was invoked
|
||||
for (let i = 0, len = matches.length; i < len; i++) {
|
||||
const match = matches[i];
|
||||
const intersection = match.range.intersectRanges(editorSelection);
|
||||
if (intersection) {
|
||||
// bingo!
|
||||
matches[i] = matches[0];
|
||||
matches[0] = match;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this._setSelections(matches.map(m => new Selection(m.range.startLineNumber, m.range.startColumn, m.range.endLineNumber, m.range.endColumn)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class MultiCursorSelectionControllerAction extends EditorAction {
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void {
|
||||
const multiCursorController = MultiCursorSelectionController.get(editor);
|
||||
if (!multiCursorController) {
|
||||
return;
|
||||
}
|
||||
const findController = CommonFindController.get(editor);
|
||||
if (!findController) {
|
||||
return null;
|
||||
}
|
||||
this._run(multiCursorController, findController);
|
||||
}
|
||||
|
||||
protected abstract _run(multiCursorController: MultiCursorSelectionController, findController: CommonFindController): void;
|
||||
}
|
||||
|
||||
@editorAction
|
||||
export class AddSelectionToNextFindMatchAction extends MultiCursorSelectionControllerAction {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'editor.action.addSelectionToNextFindMatch',
|
||||
label: nls.localize('addSelectionToNextFindMatch', "Add Selection To Next Find Match"),
|
||||
alias: 'Add Selection To Next Find Match',
|
||||
precondition: null,
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.focus,
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KEY_D
|
||||
}
|
||||
});
|
||||
}
|
||||
protected _run(multiCursorController: MultiCursorSelectionController, findController: CommonFindController): void {
|
||||
multiCursorController.addSelectionToNextFindMatch(findController);
|
||||
}
|
||||
}
|
||||
|
||||
@editorAction
|
||||
export class AddSelectionToPreviousFindMatchAction extends MultiCursorSelectionControllerAction {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'editor.action.addSelectionToPreviousFindMatch',
|
||||
label: nls.localize('addSelectionToPreviousFindMatch', "Add Selection To Previous Find Match"),
|
||||
alias: 'Add Selection To Previous Find Match',
|
||||
precondition: null
|
||||
});
|
||||
}
|
||||
protected _run(multiCursorController: MultiCursorSelectionController, findController: CommonFindController): void {
|
||||
multiCursorController.addSelectionToPreviousFindMatch(findController);
|
||||
}
|
||||
}
|
||||
|
||||
@editorAction
|
||||
export class MoveSelectionToNextFindMatchAction extends MultiCursorSelectionControllerAction {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'editor.action.moveSelectionToNextFindMatch',
|
||||
label: nls.localize('moveSelectionToNextFindMatch', "Move Last Selection To Next Find Match"),
|
||||
alias: 'Move Last Selection To Next Find Match',
|
||||
precondition: null,
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.focus,
|
||||
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_D)
|
||||
}
|
||||
});
|
||||
}
|
||||
protected _run(multiCursorController: MultiCursorSelectionController, findController: CommonFindController): void {
|
||||
multiCursorController.moveSelectionToNextFindMatch(findController);
|
||||
}
|
||||
}
|
||||
|
||||
@editorAction
|
||||
export class MoveSelectionToPreviousFindMatchAction extends MultiCursorSelectionControllerAction {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'editor.action.moveSelectionToPreviousFindMatch',
|
||||
label: nls.localize('moveSelectionToPreviousFindMatch', "Move Last Selection To Previous Find Match"),
|
||||
alias: 'Move Last Selection To Previous Find Match',
|
||||
precondition: null
|
||||
});
|
||||
}
|
||||
protected _run(multiCursorController: MultiCursorSelectionController, findController: CommonFindController): void {
|
||||
multiCursorController.moveSelectionToPreviousFindMatch(findController);
|
||||
}
|
||||
}
|
||||
|
||||
@editorAction
|
||||
export class SelectHighlightsAction extends MultiCursorSelectionControllerAction {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'editor.action.selectHighlights',
|
||||
label: nls.localize('selectAllOccurrencesOfFindMatch', "Select All Occurrences of Find Match"),
|
||||
alias: 'Select All Occurrences of Find Match',
|
||||
precondition: null,
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.focus,
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_L
|
||||
}
|
||||
});
|
||||
}
|
||||
protected _run(multiCursorController: MultiCursorSelectionController, findController: CommonFindController): void {
|
||||
multiCursorController.selectAll(findController);
|
||||
}
|
||||
}
|
||||
|
||||
@editorAction
|
||||
export class CompatChangeAll extends MultiCursorSelectionControllerAction {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'editor.action.changeAll',
|
||||
label: nls.localize('changeAll.label', "Change All Occurrences"),
|
||||
alias: 'Change All Occurrences',
|
||||
precondition: EditorContextKeys.writable,
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.textFocus,
|
||||
primary: KeyMod.CtrlCmd | KeyCode.F2
|
||||
},
|
||||
menuOpts: {
|
||||
group: '1_modification',
|
||||
order: 1.2
|
||||
}
|
||||
});
|
||||
}
|
||||
protected _run(multiCursorController: MultiCursorSelectionController, findController: CommonFindController): void {
|
||||
multiCursorController.selectAll(findController);
|
||||
}
|
||||
}
|
||||
|
||||
class SelectionHighlighterState {
|
||||
public readonly lastWordUnderCursor: Selection;
|
||||
public readonly searchText: string;
|
||||
public readonly matchCase: boolean;
|
||||
public readonly wordSeparators: string;
|
||||
|
||||
constructor(lastWordUnderCursor: Selection, searchText: string, matchCase: boolean, wordSeparators: string) {
|
||||
this.lastWordUnderCursor = lastWordUnderCursor;
|
||||
this.searchText = searchText;
|
||||
this.matchCase = matchCase;
|
||||
this.wordSeparators = wordSeparators;
|
||||
}
|
||||
|
||||
/**
|
||||
* Everything equals except for `lastWordUnderCursor`
|
||||
*/
|
||||
public static softEquals(a: SelectionHighlighterState, b: SelectionHighlighterState): boolean {
|
||||
if (!a && !b) {
|
||||
return true;
|
||||
}
|
||||
if (!a || !b) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
a.searchText === b.searchText
|
||||
&& a.matchCase === b.matchCase
|
||||
&& a.wordSeparators === b.wordSeparators
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@commonEditorContribution
|
||||
export class SelectionHighlighter extends Disposable implements IEditorContribution {
|
||||
private static ID = 'editor.contrib.selectionHighlighter';
|
||||
|
||||
private editor: ICommonCodeEditor;
|
||||
private _isEnabled: boolean;
|
||||
private decorations: string[];
|
||||
private updateSoon: RunOnceScheduler;
|
||||
private state: SelectionHighlighterState;
|
||||
|
||||
constructor(editor: ICommonCodeEditor) {
|
||||
super();
|
||||
this.editor = editor;
|
||||
this._isEnabled = editor.getConfiguration().contribInfo.selectionHighlight;
|
||||
this.decorations = [];
|
||||
this.updateSoon = this._register(new RunOnceScheduler(() => this._update(), 300));
|
||||
this.state = null;
|
||||
|
||||
this._register(editor.onDidChangeConfiguration((e) => {
|
||||
this._isEnabled = editor.getConfiguration().contribInfo.selectionHighlight;
|
||||
}));
|
||||
this._register(editor.onDidChangeCursorSelection((e: ICursorSelectionChangedEvent) => {
|
||||
|
||||
if (!this._isEnabled) {
|
||||
// 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 ;)
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.selection.isEmpty()) {
|
||||
if (e.reason === CursorChangeReason.Explicit) {
|
||||
if (this.state && (!this.state.lastWordUnderCursor || !this.state.lastWordUnderCursor.containsPosition(e.selection.getStartPosition()))) {
|
||||
// no longer valid
|
||||
this._setState(null);
|
||||
}
|
||||
this.updateSoon.schedule();
|
||||
} else {
|
||||
this._setState(null);
|
||||
|
||||
}
|
||||
} else {
|
||||
this._update();
|
||||
}
|
||||
}));
|
||||
this._register(editor.onDidChangeModel((e) => {
|
||||
this._setState(null);
|
||||
}));
|
||||
this._register(CommonFindController.get(editor).getState().addChangeListener((e) => {
|
||||
this._update();
|
||||
}));
|
||||
}
|
||||
|
||||
public getId(): string {
|
||||
return SelectionHighlighter.ID;
|
||||
}
|
||||
|
||||
private _update(): void {
|
||||
this._setState(SelectionHighlighter._createState(this._isEnabled, this.editor));
|
||||
}
|
||||
|
||||
private static _createState(isEnabled: boolean, editor: ICommonCodeEditor): SelectionHighlighterState {
|
||||
if (!isEnabled) {
|
||||
return null;
|
||||
}
|
||||
const model = editor.getModel();
|
||||
if (!model) {
|
||||
return null;
|
||||
}
|
||||
const s = editor.getSelection();
|
||||
if (s.startLineNumber !== s.endLineNumber) {
|
||||
// multiline forbidden for perf reasons
|
||||
return null;
|
||||
}
|
||||
const multiCursorController = MultiCursorSelectionController.get(editor);
|
||||
if (!multiCursorController) {
|
||||
return null;
|
||||
}
|
||||
const findController = CommonFindController.get(editor);
|
||||
if (!findController) {
|
||||
return null;
|
||||
}
|
||||
let r = multiCursorController.getSession(findController);
|
||||
if (!r) {
|
||||
const allSelections = editor.getSelections();
|
||||
if (allSelections.length > 1) {
|
||||
const findState = findController.getState();
|
||||
const matchCase = findState.matchCase;
|
||||
const selectionsContainSameText = modelRangesContainSameText(editor.getModel(), allSelections, matchCase);
|
||||
if (!selectionsContainSameText) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
r = MultiCursorSession.create(editor, findController);
|
||||
}
|
||||
if (!r) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let lastWordUnderCursor: Selection = null;
|
||||
const hasFindOccurrences = DocumentHighlightProviderRegistry.has(model);
|
||||
if (r.currentMatch) {
|
||||
// This is an empty selection
|
||||
if (hasFindOccurrences) {
|
||||
// Do not interfere with semantic word highlighting in the no selection case
|
||||
return null;
|
||||
}
|
||||
|
||||
const config = editor.getConfiguration();
|
||||
if (!config.contribInfo.occurrencesHighlight) {
|
||||
return null;
|
||||
}
|
||||
|
||||
lastWordUnderCursor = r.currentMatch;
|
||||
}
|
||||
if (/^[ \t]+$/.test(r.searchText)) {
|
||||
// whitespace only selection
|
||||
return null;
|
||||
}
|
||||
if (r.searchText.length > 200) {
|
||||
// very long selection
|
||||
return null;
|
||||
}
|
||||
|
||||
// TODO: better handling of this case
|
||||
const findState = findController.getState();
|
||||
const caseSensitive = findState.matchCase;
|
||||
|
||||
// Return early if the find widget shows the exact same matches
|
||||
if (findState.isRevealed) {
|
||||
let findStateSearchString = findState.searchString;
|
||||
if (!caseSensitive) {
|
||||
findStateSearchString = findStateSearchString.toLowerCase();
|
||||
}
|
||||
|
||||
let mySearchString = r.searchText;
|
||||
if (!caseSensitive) {
|
||||
mySearchString = mySearchString.toLowerCase();
|
||||
}
|
||||
|
||||
if (findStateSearchString === mySearchString && r.matchCase === findState.matchCase && r.wholeWord === findState.wholeWord && !findState.isRegex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return new SelectionHighlighterState(lastWordUnderCursor, r.searchText, r.matchCase, r.wholeWord ? editor.getConfiguration().wordSeparators : null);
|
||||
}
|
||||
|
||||
private _setState(state: SelectionHighlighterState): void {
|
||||
if (SelectionHighlighterState.softEquals(this.state, state)) {
|
||||
this.state = state;
|
||||
return;
|
||||
}
|
||||
this.state = state;
|
||||
|
||||
if (!this.state) {
|
||||
if (this.decorations.length > 0) {
|
||||
this.decorations = this.editor.deltaDecorations(this.decorations, []);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const model = this.editor.getModel();
|
||||
const hasFindOccurrences = DocumentHighlightProviderRegistry.has(model);
|
||||
|
||||
let allMatches = model.findMatches(this.state.searchText, true, false, this.state.matchCase, this.state.wordSeparators, false).map(m => m.range);
|
||||
allMatches.sort(Range.compareRangesUsingStarts);
|
||||
|
||||
let selections = this.editor.getSelections();
|
||||
selections.sort(Range.compareRangesUsingStarts);
|
||||
|
||||
// do not overlap with selection (issue #64 and #512)
|
||||
let matches: Range[] = [];
|
||||
for (let i = 0, j = 0, len = allMatches.length, lenJ = selections.length; i < len;) {
|
||||
const match = allMatches[i];
|
||||
|
||||
if (j >= lenJ) {
|
||||
// finished all editor selections
|
||||
matches.push(match);
|
||||
i++;
|
||||
} else {
|
||||
const cmp = Range.compareRangesUsingStarts(match, selections[j]);
|
||||
if (cmp < 0) {
|
||||
// match is before sel
|
||||
matches.push(match);
|
||||
i++;
|
||||
} else if (cmp > 0) {
|
||||
// sel is before match
|
||||
j++;
|
||||
} else {
|
||||
// sel is equal to match
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const decorations = matches.map(r => {
|
||||
return {
|
||||
range: r,
|
||||
// Show in overviewRuler only if model has no semantic highlighting
|
||||
options: (hasFindOccurrences ? SelectionHighlighter._SELECTION_HIGHLIGHT : SelectionHighlighter._SELECTION_HIGHLIGHT_OVERVIEW)
|
||||
};
|
||||
});
|
||||
|
||||
this.decorations = this.editor.deltaDecorations(this.decorations, decorations);
|
||||
}
|
||||
|
||||
private static _SELECTION_HIGHLIGHT_OVERVIEW = ModelDecorationOptions.register({
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
className: 'selectionHighlight',
|
||||
overviewRuler: {
|
||||
color: themeColorFromId(overviewRulerSelectionHighlightForeground),
|
||||
darkColor: themeColorFromId(overviewRulerSelectionHighlightForeground),
|
||||
position: OverviewRulerLane.Center
|
||||
}
|
||||
});
|
||||
|
||||
private static _SELECTION_HIGHLIGHT = ModelDecorationOptions.register({
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
className: 'selectionHighlight',
|
||||
});
|
||||
|
||||
public dispose(): void {
|
||||
this._setState(null);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
function modelRangesContainSameText(model: IModel, ranges: Range[], matchCase: boolean): boolean {
|
||||
const selectedText = getValueInRange(model, ranges[0], !matchCase);
|
||||
for (let i = 1, len = ranges.length; i < len; i++) {
|
||||
const range = ranges[i];
|
||||
if (range.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
const thisSelectedText = getValueInRange(model, range, !matchCase);
|
||||
if (selectedText !== thisSelectedText) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function getValueInRange(model: IModel, range: Range, toLowerCase: boolean): string {
|
||||
const text = model.getValueInRange(range);
|
||||
return (toLowerCase ? text.toLowerCase() : text);
|
||||
}
|
||||
|
||||
@@ -5,11 +5,14 @@
|
||||
'use strict';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { withMockCodeEditor } from 'vs/editor/test/common/mocks/mockCodeEditor';
|
||||
import { withMockCodeEditor, MockCodeEditor } from 'vs/editor/test/common/mocks/mockCodeEditor';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { InsertCursorAbove, InsertCursorBelow } from 'vs/editor/contrib/multicursor/common/multicursor';
|
||||
import { Handler } from 'vs/editor/common/editorCommon';
|
||||
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { InsertCursorAbove, InsertCursorBelow, MultiCursorSelectionController, SelectHighlightsAction, AddSelectionToNextFindMatchAction } from 'vs/editor/contrib/multicursor/common/multicursor';
|
||||
import { Handler, EndOfLineSequence } from 'vs/editor/common/editorCommon';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { CommonFindController } from 'vs/editor/contrib/find/common/findController';
|
||||
|
||||
suite('Multicursor', () => {
|
||||
|
||||
@@ -42,3 +45,525 @@ suite('Multicursor', () => {
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function fromRange(rng: Range): number[] {
|
||||
return [rng.startLineNumber, rng.startColumn, rng.endLineNumber, rng.endColumn];
|
||||
}
|
||||
|
||||
suite('Multicursor selection', () => {
|
||||
let queryState: { [key: string]: any; } = {};
|
||||
let serviceCollection = new ServiceCollection();
|
||||
serviceCollection.set(IStorageService, <any>{
|
||||
get: (key: string) => queryState[key],
|
||||
getBoolean: (key: string) => !!queryState[key],
|
||||
store: (key: string, value: any) => { queryState[key] = value; }
|
||||
});
|
||||
|
||||
test('issue #8817: Cursor position changes when you cancel multicursor', () => {
|
||||
withMockCodeEditor([
|
||||
'var x = (3 * 5)',
|
||||
'var y = (3 * 5)',
|
||||
'var z = (3 * 5)',
|
||||
], { serviceCollection: serviceCollection }, (editor, cursor) => {
|
||||
|
||||
let findController = editor.registerAndInstantiateContribution<CommonFindController>(CommonFindController);
|
||||
let multiCursorSelectController = editor.registerAndInstantiateContribution<MultiCursorSelectionController>(MultiCursorSelectionController);
|
||||
let selectHighlightsAction = new SelectHighlightsAction();
|
||||
|
||||
editor.setSelection(new Selection(2, 9, 2, 16));
|
||||
|
||||
selectHighlightsAction.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections().map(fromRange), [
|
||||
[2, 9, 2, 16],
|
||||
[1, 9, 1, 16],
|
||||
[3, 9, 3, 16],
|
||||
]);
|
||||
|
||||
editor.trigger('test', 'removeSecondaryCursors', null);
|
||||
|
||||
assert.deepEqual(fromRange(editor.getSelection()), [2, 9, 2, 16]);
|
||||
|
||||
multiCursorSelectController.dispose();
|
||||
findController.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
test('issue #5400: "Select All Occurrences of Find Match" does not select all if find uses regex', () => {
|
||||
withMockCodeEditor([
|
||||
'something',
|
||||
'someething',
|
||||
'someeething',
|
||||
'nothing'
|
||||
], { serviceCollection: serviceCollection }, (editor, cursor) => {
|
||||
|
||||
let findController = editor.registerAndInstantiateContribution<CommonFindController>(CommonFindController);
|
||||
let multiCursorSelectController = editor.registerAndInstantiateContribution<MultiCursorSelectionController>(MultiCursorSelectionController);
|
||||
let selectHighlightsAction = new SelectHighlightsAction();
|
||||
|
||||
editor._isFocused = false;
|
||||
|
||||
editor.setSelection(new Selection(1, 1, 1, 1));
|
||||
findController.getState().change({ searchString: 'some+thing', isRegex: true, isRevealed: true }, false);
|
||||
|
||||
selectHighlightsAction.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections().map(fromRange), [
|
||||
[1, 1, 1, 10],
|
||||
[2, 1, 2, 11],
|
||||
[3, 1, 3, 12],
|
||||
]);
|
||||
|
||||
assert.equal(findController.getState().searchString, 'some+thing');
|
||||
|
||||
multiCursorSelectController.dispose();
|
||||
findController.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
test('AddSelectionToNextFindMatchAction can work with multiline', () => {
|
||||
withMockCodeEditor([
|
||||
'',
|
||||
'qwe',
|
||||
'rty',
|
||||
'',
|
||||
'qwe',
|
||||
'',
|
||||
'rty',
|
||||
'qwe',
|
||||
'rty'
|
||||
], { serviceCollection: serviceCollection }, (editor, cursor) => {
|
||||
|
||||
let findController = editor.registerAndInstantiateContribution<CommonFindController>(CommonFindController);
|
||||
let multiCursorSelectController = editor.registerAndInstantiateContribution<MultiCursorSelectionController>(MultiCursorSelectionController);
|
||||
let addSelectionToNextFindMatch = new AddSelectionToNextFindMatchAction();
|
||||
|
||||
editor.setSelection(new Selection(2, 1, 3, 4));
|
||||
|
||||
addSelectionToNextFindMatch.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections().map(fromRange), [
|
||||
[2, 1, 3, 4],
|
||||
[8, 1, 9, 4]
|
||||
]);
|
||||
|
||||
editor.trigger('test', 'removeSecondaryCursors', null);
|
||||
|
||||
assert.deepEqual(fromRange(editor.getSelection()), [2, 1, 3, 4]);
|
||||
|
||||
multiCursorSelectController.dispose();
|
||||
findController.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
test('issue #6661: AddSelectionToNextFindMatchAction can work with touching ranges', () => {
|
||||
withMockCodeEditor([
|
||||
'abcabc',
|
||||
'abc',
|
||||
'abcabc',
|
||||
], { serviceCollection: serviceCollection }, (editor, cursor) => {
|
||||
|
||||
let findController = editor.registerAndInstantiateContribution<CommonFindController>(CommonFindController);
|
||||
let multiCursorSelectController = editor.registerAndInstantiateContribution<MultiCursorSelectionController>(MultiCursorSelectionController);
|
||||
let addSelectionToNextFindMatch = new AddSelectionToNextFindMatchAction();
|
||||
|
||||
editor.setSelection(new Selection(1, 1, 1, 4));
|
||||
|
||||
addSelectionToNextFindMatch.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections().map(fromRange), [
|
||||
[1, 1, 1, 4],
|
||||
[1, 4, 1, 7]
|
||||
]);
|
||||
|
||||
addSelectionToNextFindMatch.run(null, editor);
|
||||
addSelectionToNextFindMatch.run(null, editor);
|
||||
addSelectionToNextFindMatch.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections().map(fromRange), [
|
||||
[1, 1, 1, 4],
|
||||
[1, 4, 1, 7],
|
||||
[2, 1, 2, 4],
|
||||
[3, 1, 3, 4],
|
||||
[3, 4, 3, 7]
|
||||
]);
|
||||
|
||||
editor.trigger('test', Handler.Type, { text: 'z' });
|
||||
assert.deepEqual(editor.getSelections().map(fromRange), [
|
||||
[1, 2, 1, 2],
|
||||
[1, 3, 1, 3],
|
||||
[2, 2, 2, 2],
|
||||
[3, 2, 3, 2],
|
||||
[3, 3, 3, 3]
|
||||
]);
|
||||
assert.equal(editor.getValue(), [
|
||||
'zz',
|
||||
'z',
|
||||
'zz',
|
||||
].join('\n'));
|
||||
|
||||
multiCursorSelectController.dispose();
|
||||
findController.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
test('issue #23541: Multiline Ctrl+D does not work in CRLF files', () => {
|
||||
withMockCodeEditor([
|
||||
'',
|
||||
'qwe',
|
||||
'rty',
|
||||
'',
|
||||
'qwe',
|
||||
'',
|
||||
'rty',
|
||||
'qwe',
|
||||
'rty'
|
||||
], { serviceCollection: serviceCollection }, (editor, cursor) => {
|
||||
|
||||
editor.getModel().setEOL(EndOfLineSequence.CRLF);
|
||||
|
||||
let findController = editor.registerAndInstantiateContribution<CommonFindController>(CommonFindController);
|
||||
let multiCursorSelectController = editor.registerAndInstantiateContribution<MultiCursorSelectionController>(MultiCursorSelectionController);
|
||||
let addSelectionToNextFindMatch = new AddSelectionToNextFindMatchAction();
|
||||
|
||||
editor.setSelection(new Selection(2, 1, 3, 4));
|
||||
|
||||
addSelectionToNextFindMatch.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections().map(fromRange), [
|
||||
[2, 1, 3, 4],
|
||||
[8, 1, 9, 4]
|
||||
]);
|
||||
|
||||
editor.trigger('test', 'removeSecondaryCursors', null);
|
||||
|
||||
assert.deepEqual(fromRange(editor.getSelection()), [2, 1, 3, 4]);
|
||||
|
||||
multiCursorSelectController.dispose();
|
||||
findController.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
function testMulticursor(text: string[], callback: (editor: MockCodeEditor, findController: CommonFindController) => void): void {
|
||||
withMockCodeEditor(text, { serviceCollection: serviceCollection }, (editor, cursor) => {
|
||||
let findController = editor.registerAndInstantiateContribution<CommonFindController>(CommonFindController);
|
||||
let multiCursorSelectController = editor.registerAndInstantiateContribution<MultiCursorSelectionController>(MultiCursorSelectionController);
|
||||
|
||||
callback(editor, findController);
|
||||
|
||||
multiCursorSelectController.dispose();
|
||||
findController.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
function testAddSelectionToNextFindMatchAction(text: string[], callback: (editor: MockCodeEditor, action: AddSelectionToNextFindMatchAction, findController: CommonFindController) => void): void {
|
||||
testMulticursor(text, (editor, findController) => {
|
||||
let action = new AddSelectionToNextFindMatchAction();
|
||||
callback(editor, action, findController);
|
||||
});
|
||||
}
|
||||
|
||||
test('AddSelectionToNextFindMatchAction starting with single collapsed selection', () => {
|
||||
const text = [
|
||||
'abc pizza',
|
||||
'abc house',
|
||||
'abc bar'
|
||||
];
|
||||
testAddSelectionToNextFindMatchAction(text, (editor, action, findController) => {
|
||||
editor.setSelections([
|
||||
new Selection(1, 2, 1, 2),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(2, 1, 2, 4),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(2, 1, 2, 4),
|
||||
new Selection(3, 1, 3, 4),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(2, 1, 2, 4),
|
||||
new Selection(3, 1, 3, 4),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
test('AddSelectionToNextFindMatchAction starting with two selections, one being collapsed 1)', () => {
|
||||
const text = [
|
||||
'abc pizza',
|
||||
'abc house',
|
||||
'abc bar'
|
||||
];
|
||||
testAddSelectionToNextFindMatchAction(text, (editor, action, findController) => {
|
||||
editor.setSelections([
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(2, 2, 2, 2),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(2, 1, 2, 4),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(2, 1, 2, 4),
|
||||
new Selection(3, 1, 3, 4),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(2, 1, 2, 4),
|
||||
new Selection(3, 1, 3, 4),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
test('AddSelectionToNextFindMatchAction starting with two selections, one being collapsed 2)', () => {
|
||||
const text = [
|
||||
'abc pizza',
|
||||
'abc house',
|
||||
'abc bar'
|
||||
];
|
||||
testAddSelectionToNextFindMatchAction(text, (editor, action, findController) => {
|
||||
editor.setSelections([
|
||||
new Selection(1, 2, 1, 2),
|
||||
new Selection(2, 1, 2, 4),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(2, 1, 2, 4),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(2, 1, 2, 4),
|
||||
new Selection(3, 1, 3, 4),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(2, 1, 2, 4),
|
||||
new Selection(3, 1, 3, 4),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
test('AddSelectionToNextFindMatchAction starting with all collapsed selections', () => {
|
||||
const text = [
|
||||
'abc pizza',
|
||||
'abc house',
|
||||
'abc bar'
|
||||
];
|
||||
testAddSelectionToNextFindMatchAction(text, (editor, action, findController) => {
|
||||
editor.setSelections([
|
||||
new Selection(1, 2, 1, 2),
|
||||
new Selection(2, 2, 2, 2),
|
||||
new Selection(3, 1, 3, 1),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(2, 1, 2, 4),
|
||||
new Selection(3, 1, 3, 4),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(2, 1, 2, 4),
|
||||
new Selection(3, 1, 3, 4),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
test('AddSelectionToNextFindMatchAction starting with all collapsed selections on different words', () => {
|
||||
const text = [
|
||||
'abc pizza',
|
||||
'abc house',
|
||||
'abc bar'
|
||||
];
|
||||
testAddSelectionToNextFindMatchAction(text, (editor, action, findController) => {
|
||||
editor.setSelections([
|
||||
new Selection(1, 6, 1, 6),
|
||||
new Selection(2, 6, 2, 6),
|
||||
new Selection(3, 6, 3, 6),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 5, 1, 10),
|
||||
new Selection(2, 5, 2, 10),
|
||||
new Selection(3, 5, 3, 8),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 5, 1, 10),
|
||||
new Selection(2, 5, 2, 10),
|
||||
new Selection(3, 5, 3, 8),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
test('issue #20651: AddSelectionToNextFindMatchAction case insensitive', () => {
|
||||
const text = [
|
||||
'test',
|
||||
'testte',
|
||||
'Test',
|
||||
'testte',
|
||||
'test'
|
||||
];
|
||||
testAddSelectionToNextFindMatchAction(text, (editor, action, findController) => {
|
||||
editor.setSelections([
|
||||
new Selection(1, 1, 1, 5),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 5),
|
||||
new Selection(2, 1, 2, 5),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 5),
|
||||
new Selection(2, 1, 2, 5),
|
||||
new Selection(3, 1, 3, 5),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 5),
|
||||
new Selection(2, 1, 2, 5),
|
||||
new Selection(3, 1, 3, 5),
|
||||
new Selection(4, 1, 4, 5),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 5),
|
||||
new Selection(2, 1, 2, 5),
|
||||
new Selection(3, 1, 3, 5),
|
||||
new Selection(4, 1, 4, 5),
|
||||
new Selection(5, 1, 5, 5),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 5),
|
||||
new Selection(2, 1, 2, 5),
|
||||
new Selection(3, 1, 3, 5),
|
||||
new Selection(4, 1, 4, 5),
|
||||
new Selection(5, 1, 5, 5),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Find state disassociation', () => {
|
||||
|
||||
const text = [
|
||||
'app',
|
||||
'apples',
|
||||
'whatsapp',
|
||||
'app',
|
||||
'App',
|
||||
' app'
|
||||
];
|
||||
|
||||
test('enters mode', () => {
|
||||
testAddSelectionToNextFindMatchAction(text, (editor, action, findController) => {
|
||||
editor.setSelections([
|
||||
new Selection(1, 2, 1, 2),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(4, 1, 4, 4),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(4, 1, 4, 4),
|
||||
new Selection(6, 2, 6, 5),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
test('leaves mode when selection changes', () => {
|
||||
testAddSelectionToNextFindMatchAction(text, (editor, action, findController) => {
|
||||
editor.setSelections([
|
||||
new Selection(1, 2, 1, 2),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(4, 1, 4, 4),
|
||||
]);
|
||||
|
||||
// change selection
|
||||
editor.setSelections([
|
||||
new Selection(1, 1, 1, 4),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(2, 1, 2, 4),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
test('Select Highlights respects mode ', () => {
|
||||
testMulticursor(text, (editor, findController) => {
|
||||
let action = new SelectHighlightsAction();
|
||||
editor.setSelections([
|
||||
new Selection(1, 2, 1, 2),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(4, 1, 4, 4),
|
||||
new Selection(6, 2, 6, 5),
|
||||
]);
|
||||
|
||||
action.run(null, editor);
|
||||
assert.deepEqual(editor.getSelections(), [
|
||||
new Selection(1, 1, 1, 4),
|
||||
new Selection(4, 1, 4, 4),
|
||||
new Selection(6, 2, 6, 5),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user