mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-04-01 09:30:31 -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:
@@ -147,7 +147,7 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget {
|
||||
this._revealTemporarily();
|
||||
}
|
||||
|
||||
private _hideSoon = this._register(new RunOnceScheduler(() => this._hide(), 1000));
|
||||
private _hideSoon = this._register(new RunOnceScheduler(() => this._hide(), 2000));
|
||||
|
||||
private _revealTemporarily(): void {
|
||||
this._show();
|
||||
|
||||
@@ -48,7 +48,7 @@ const NLS_REPLACE_INPUT_PLACEHOLDER = nls.localize('placeholder.replace', "Repla
|
||||
const NLS_REPLACE_BTN_LABEL = nls.localize('label.replaceButton', "Replace");
|
||||
const NLS_REPLACE_ALL_BTN_LABEL = nls.localize('label.replaceAllButton', "Replace All");
|
||||
const NLS_TOGGLE_REPLACE_MODE_BTN_LABEL = nls.localize('label.toggleReplaceButton', "Toggle Replace mode");
|
||||
const NLS_MATCHES_COUNT_LIMIT_TITLE = nls.localize('title.matchesCountLimit', "Only the first 999 results are highlighted, but all find operations work on the entire text.");
|
||||
const NLS_MATCHES_COUNT_LIMIT_TITLE = nls.localize('title.matchesCountLimit', "Only the first {0} results are highlighted, but all find operations work on the entire text.", MATCHES_LIMIT);
|
||||
const NLS_MATCHES_LOCATION = nls.localize('label.matchesLocation', "{0} of {1}");
|
||||
const NLS_NO_RESULTS = nls.localize('label.noResults', "No Results");
|
||||
|
||||
@@ -921,7 +921,7 @@ class SimpleCheckbox extends Widget {
|
||||
|
||||
this._label = document.createElement('label');
|
||||
this._label.className = 'label';
|
||||
// Connect the label and the checkbox. Checkbox will get checked when the label recieves a click.
|
||||
// Connect the label and the checkbox. Checkbox will get checked when the label receives a click.
|
||||
this._label.htmlFor = this._checkbox.id;
|
||||
this._label.tabIndex = -1;
|
||||
|
||||
|
||||
@@ -3,16 +3,25 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench .simple-find-part {
|
||||
.monaco-workbench .simple-find-part-wrapper {
|
||||
overflow: hidden;
|
||||
z-index: 10;
|
||||
position: absolute;
|
||||
top: -40px;
|
||||
top: 0;
|
||||
right: 28px;
|
||||
width: 220px;
|
||||
max-width: calc(100% - 28px - 28px - 8px);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.monaco-workbench .simple-find-part {
|
||||
z-index: 10;
|
||||
position: relative;
|
||||
top: -40px;
|
||||
display: flex;
|
||||
padding: 4px;
|
||||
align-items: center;
|
||||
width: 220px;
|
||||
max-width: calc(100% - 28px - 28px - 8px);
|
||||
pointer-events: all;
|
||||
|
||||
-webkit-transition: top 200ms linear;
|
||||
-o-transition: top 200ms linear;
|
||||
|
||||
@@ -25,6 +25,7 @@ const NLS_CLOSE_BTN_LABEL = nls.localize('label.closeButton', "Close");
|
||||
export abstract class SimpleFindWidget extends Widget {
|
||||
protected _findInput: FindInput;
|
||||
protected _domNode: HTMLElement;
|
||||
protected _innerDomNode: HTMLElement;
|
||||
protected _isVisible: boolean;
|
||||
protected _focusTracker: dom.IFocusTracker;
|
||||
protected _findInputFocusTracker: dom.IFocusTracker;
|
||||
@@ -91,14 +92,19 @@ export abstract class SimpleFindWidget extends Widget {
|
||||
onKeyDown: (e) => { }
|
||||
});
|
||||
|
||||
this._domNode = document.createElement('div');
|
||||
this._domNode.classList.add('simple-find-part');
|
||||
this._domNode.appendChild(this._findInput.domNode);
|
||||
this._domNode.appendChild(prevBtn.domNode);
|
||||
this._domNode.appendChild(nextBtn.domNode);
|
||||
this._domNode.appendChild(closeBtn.domNode);
|
||||
this._innerDomNode = document.createElement('div');
|
||||
this._innerDomNode.classList.add('simple-find-part');
|
||||
this._innerDomNode.appendChild(this._findInput.domNode);
|
||||
this._innerDomNode.appendChild(prevBtn.domNode);
|
||||
this._innerDomNode.appendChild(nextBtn.domNode);
|
||||
this._innerDomNode.appendChild(closeBtn.domNode);
|
||||
|
||||
this.onkeyup(this._domNode, e => {
|
||||
// _domNode wraps _innerDomNode, ensuring that
|
||||
this._domNode = document.createElement('div');
|
||||
this._domNode.classList.add('simple-find-part-wrapper');
|
||||
this._domNode.appendChild(this._innerDomNode);
|
||||
|
||||
this.onkeyup(this._innerDomNode, e => {
|
||||
if (e.equals(KeyCode.Escape)) {
|
||||
this.hide();
|
||||
e.preventDefault();
|
||||
@@ -106,7 +112,7 @@ export abstract class SimpleFindWidget extends Widget {
|
||||
}
|
||||
});
|
||||
|
||||
this._focusTracker = this._register(dom.trackFocus(this._domNode));
|
||||
this._focusTracker = this._register(dom.trackFocus(this._innerDomNode));
|
||||
this._register(this._focusTracker.addFocusListener(this.onFocusTrackerFocus.bind(this)));
|
||||
this._register(this._focusTracker.addBlurListener(this.onFocusTrackerBlur.bind(this)));
|
||||
|
||||
@@ -114,17 +120,17 @@ export abstract class SimpleFindWidget extends Widget {
|
||||
this._register(this._findInputFocusTracker.addFocusListener(this.onFindInputFocusTrackerFocus.bind(this)));
|
||||
this._register(this._findInputFocusTracker.addBlurListener(this.onFindInputFocusTrackerBlur.bind(this)));
|
||||
|
||||
this._register(dom.addDisposableListener(this._domNode, 'click', (event) => {
|
||||
this._register(dom.addDisposableListener(this._innerDomNode, 'click', (event) => {
|
||||
event.stopPropagation();
|
||||
}));
|
||||
}
|
||||
|
||||
protected abstract onInputChanged();
|
||||
protected abstract find(previous: boolean);
|
||||
protected abstract onFocusTrackerFocus();
|
||||
protected abstract onFocusTrackerBlur();
|
||||
protected abstract onFindInputFocusTrackerFocus();
|
||||
protected abstract onFindInputFocusTrackerBlur();
|
||||
protected abstract onInputChanged(): void;
|
||||
protected abstract find(previous: boolean): void;
|
||||
protected abstract onFocusTrackerFocus(): void;
|
||||
protected abstract onFocusTrackerBlur(): void;
|
||||
protected abstract onFindInputFocusTrackerFocus(): void;
|
||||
protected abstract onFindInputFocusTrackerBlur(): void;
|
||||
|
||||
protected get inputValue() {
|
||||
return this._findInput.getValue();
|
||||
@@ -163,13 +169,13 @@ export abstract class SimpleFindWidget extends Widget {
|
||||
this._isVisible = true;
|
||||
|
||||
setTimeout(() => {
|
||||
dom.addClass(this._domNode, 'visible');
|
||||
this._domNode.setAttribute('aria-hidden', 'false');
|
||||
dom.addClass(this._innerDomNode, 'visible');
|
||||
this._innerDomNode.setAttribute('aria-hidden', 'false');
|
||||
if (!this.animate) {
|
||||
dom.addClass(this._domNode, 'noanimation');
|
||||
dom.addClass(this._innerDomNode, 'noanimation');
|
||||
}
|
||||
setTimeout(() => {
|
||||
dom.removeClass(this._domNode, 'noanimation');
|
||||
dom.removeClass(this._innerDomNode, 'noanimation');
|
||||
this._findInput.select();
|
||||
}, 200);
|
||||
}, 0);
|
||||
@@ -179,8 +185,8 @@ export abstract class SimpleFindWidget extends Widget {
|
||||
if (this._isVisible) {
|
||||
this._isVisible = false;
|
||||
|
||||
dom.removeClass(this._domNode, 'visible');
|
||||
this._domNode.setAttribute('aria-hidden', 'true');
|
||||
dom.removeClass(this._innerDomNode, 'visible');
|
||||
this._innerDomNode.setAttribute('aria-hidden', 'true');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,25 +6,18 @@
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { HistoryNavigator } from 'vs/base/common/history';
|
||||
import { KeyCode, KeyMod, KeyChord } from 'vs/base/common/keyCodes';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ContextKeyExpr, RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { editorAction, commonEditorContribution, ServicesAccessor, EditorAction, EditorCommand, CommonEditorRegistry } from 'vs/editor/common/editorCommonExtensions';
|
||||
import { editorAction, ServicesAccessor, EditorAction, EditorCommand, CommonEditorRegistry } from 'vs/editor/common/editorCommonExtensions';
|
||||
import { FIND_IDS, FindModelBoundToEditorModel, ToggleCaseSensitiveKeybinding, ToggleRegexKeybinding, ToggleWholeWordKeybinding, ToggleSearchScopeKeybinding, ShowPreviousFindTermKeybinding, ShowNextFindTermKeybinding } from 'vs/editor/contrib/find/common/findModel';
|
||||
import { FindReplaceState, FindReplaceStateChangedEvent, INewFindReplaceState } from 'vs/editor/contrib/find/common/findState';
|
||||
import { getSelectionSearchString } from 'vs/editor/contrib/find/common/find';
|
||||
import { DocumentHighlightProviderRegistry } from 'vs/editor/common/modes';
|
||||
import { RunOnceScheduler, Delayer } from 'vs/base/common/async';
|
||||
import { CursorChangeReason, ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
|
||||
import { Delayer } from 'vs/base/common/async';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations';
|
||||
import { overviewRulerSelectionHighlightForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { themeColorFromId } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
export const enum FindStartFocusAction {
|
||||
NoFocusChange,
|
||||
@@ -514,614 +507,6 @@ export class StartFindReplaceAction extends EditorAction {
|
||||
}
|
||||
}
|
||||
|
||||
export interface IMultiCursorFindInput {
|
||||
changeFindSearchString: boolean;
|
||||
allowMultiline: boolean;
|
||||
highlightFindOptions: boolean;
|
||||
}
|
||||
|
||||
export interface IMultiCursorFindResult {
|
||||
searchText: string;
|
||||
matchCase: boolean;
|
||||
wholeWord: boolean;
|
||||
|
||||
currentMatch: Selection;
|
||||
}
|
||||
|
||||
function multiCursorFind(editor: editorCommon.ICommonCodeEditor, input: IMultiCursorFindInput): IMultiCursorFindResult {
|
||||
let controller = CommonFindController.get(editor);
|
||||
if (!controller) {
|
||||
return null;
|
||||
}
|
||||
let state = controller.getState();
|
||||
let searchText: string;
|
||||
let currentMatch: Selection;
|
||||
|
||||
// In any case, if the find widget was ever opened, the options are taken from it
|
||||
let wholeWord = state.wholeWord;
|
||||
let matchCase = state.matchCase;
|
||||
|
||||
// Find widget owns 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() && state.isRevealed && state.searchString.length > 0) {
|
||||
// Find widget owns what is searched for
|
||||
searchText = state.searchString;
|
||||
} else {
|
||||
// Selection owns what is searched for
|
||||
let s = editor.getSelection();
|
||||
|
||||
if (s.startLineNumber !== s.endLineNumber && !input.allowMultiline) {
|
||||
// multiline forbidden
|
||||
return null;
|
||||
}
|
||||
|
||||
if (s.isEmpty()) {
|
||||
// selection is empty => expand to current word
|
||||
let 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');
|
||||
}
|
||||
if (input.changeFindSearchString) {
|
||||
controller.setSearchString(searchText);
|
||||
}
|
||||
}
|
||||
|
||||
if (input.highlightFindOptions) {
|
||||
controller.highlightFindOptions();
|
||||
}
|
||||
|
||||
return {
|
||||
searchText: searchText,
|
||||
matchCase: matchCase,
|
||||
wholeWord: wholeWord,
|
||||
currentMatch: currentMatch
|
||||
};
|
||||
}
|
||||
|
||||
export abstract class SelectNextFindMatchAction extends EditorAction {
|
||||
protected _getNextMatch(editor: editorCommon.ICommonCodeEditor): Selection {
|
||||
let r = multiCursorFind(editor, {
|
||||
changeFindSearchString: true,
|
||||
allowMultiline: true,
|
||||
highlightFindOptions: true
|
||||
});
|
||||
if (!r) {
|
||||
return null;
|
||||
}
|
||||
if (r.currentMatch) {
|
||||
return r.currentMatch;
|
||||
}
|
||||
|
||||
let allSelections = editor.getSelections();
|
||||
let lastAddedSelection = allSelections[allSelections.length - 1];
|
||||
|
||||
let nextMatch = editor.getModel().findNextMatch(r.searchText, lastAddedSelection.getEndPosition(), false, r.matchCase, r.wholeWord ? editor.getConfiguration().wordSeparators : null, false);
|
||||
|
||||
if (!nextMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Selection(nextMatch.range.startLineNumber, nextMatch.range.startColumn, nextMatch.range.endLineNumber, nextMatch.range.endColumn);
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class SelectPreviousFindMatchAction extends EditorAction {
|
||||
protected _getPreviousMatch(editor: editorCommon.ICommonCodeEditor): Selection {
|
||||
let r = multiCursorFind(editor, {
|
||||
changeFindSearchString: true,
|
||||
allowMultiline: true,
|
||||
highlightFindOptions: true
|
||||
});
|
||||
if (!r) {
|
||||
return null;
|
||||
}
|
||||
if (r.currentMatch) {
|
||||
return r.currentMatch;
|
||||
}
|
||||
|
||||
let allSelections = editor.getSelections();
|
||||
let lastAddedSelection = allSelections[allSelections.length - 1];
|
||||
|
||||
let previousMatch = editor.getModel().findPreviousMatch(r.searchText, lastAddedSelection.getStartPosition(), false, r.matchCase, r.wholeWord ? editor.getConfiguration().wordSeparators : null, false);
|
||||
|
||||
if (!previousMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Selection(previousMatch.range.startLineNumber, previousMatch.range.startColumn, previousMatch.range.endLineNumber, previousMatch.range.endColumn);
|
||||
}
|
||||
}
|
||||
|
||||
@editorAction
|
||||
export class AddSelectionToNextFindMatchAction extends SelectNextFindMatchAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: FIND_IDS.AddSelectionToNextFindMatchAction,
|
||||
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
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: editorCommon.ICommonCodeEditor): void {
|
||||
const allSelections = editor.getSelections();
|
||||
|
||||
// If there are mulitple cursors, handle the case where they do not all select the same text.
|
||||
if (allSelections.length > 1) {
|
||||
const model = editor.getModel();
|
||||
const controller = CommonFindController.get(editor);
|
||||
if (!controller) {
|
||||
return;
|
||||
}
|
||||
const findState = controller.getState();
|
||||
const caseSensitive = findState.matchCase;
|
||||
|
||||
let selectionsContainSameText = true;
|
||||
|
||||
let selectedText = model.getValueInRange(allSelections[0]);
|
||||
if (!caseSensitive) {
|
||||
selectedText = selectedText.toLowerCase();
|
||||
}
|
||||
for (let i = 1, len = allSelections.length; i < len; i++) {
|
||||
let selection = allSelections[i];
|
||||
if (selection.isEmpty()) {
|
||||
selectionsContainSameText = false;
|
||||
break;
|
||||
}
|
||||
|
||||
let thisSelectedText = model.getValueInRange(selection);
|
||||
if (!caseSensitive) {
|
||||
thisSelectedText = thisSelectedText.toLowerCase();
|
||||
}
|
||||
if (selectedText !== thisSelectedText) {
|
||||
selectionsContainSameText = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!selectionsContainSameText) {
|
||||
let resultingSelections: Selection[] = [];
|
||||
for (let i = 0, len = allSelections.length; i < len; i++) {
|
||||
let selection = allSelections[i];
|
||||
if (selection.isEmpty()) {
|
||||
let word = editor.getModel().getWordAtPosition(selection.getStartPosition());
|
||||
if (word) {
|
||||
resultingSelections[i] = new Selection(selection.startLineNumber, word.startColumn, selection.startLineNumber, word.endColumn);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
resultingSelections[i] = selection;
|
||||
}
|
||||
editor.setSelections(resultingSelections);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let nextMatch = this._getNextMatch(editor);
|
||||
|
||||
if (!nextMatch) {
|
||||
return;
|
||||
}
|
||||
|
||||
editor.setSelections(allSelections.concat(nextMatch));
|
||||
editor.revealRangeInCenterIfOutsideViewport(nextMatch, editorCommon.ScrollType.Smooth);
|
||||
}
|
||||
}
|
||||
|
||||
@editorAction
|
||||
export class AddSelectionToPreviousFindMatchAction extends SelectPreviousFindMatchAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: FIND_IDS.AddSelectionToPreviousFindMatchAction,
|
||||
label: nls.localize('addSelectionToPreviousFindMatch', "Add Selection To Previous Find Match"),
|
||||
alias: 'Add Selection To Previous Find Match',
|
||||
precondition: null
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: editorCommon.ICommonCodeEditor): void {
|
||||
let previousMatch = this._getPreviousMatch(editor);
|
||||
|
||||
if (!previousMatch) {
|
||||
return;
|
||||
}
|
||||
|
||||
let allSelections = editor.getSelections();
|
||||
editor.setSelections(allSelections.concat(previousMatch));
|
||||
editor.revealRangeInCenterIfOutsideViewport(previousMatch, editorCommon.ScrollType.Smooth);
|
||||
}
|
||||
}
|
||||
|
||||
@editorAction
|
||||
export class MoveSelectionToNextFindMatchAction extends SelectNextFindMatchAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: FIND_IDS.MoveSelectionToNextFindMatchAction,
|
||||
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)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: editorCommon.ICommonCodeEditor): void {
|
||||
let nextMatch = this._getNextMatch(editor);
|
||||
|
||||
if (!nextMatch) {
|
||||
return;
|
||||
}
|
||||
|
||||
let allSelections = editor.getSelections();
|
||||
editor.setSelections(allSelections.slice(0, allSelections.length - 1).concat(nextMatch));
|
||||
editor.revealRangeInCenterIfOutsideViewport(nextMatch, editorCommon.ScrollType.Smooth);
|
||||
}
|
||||
}
|
||||
|
||||
@editorAction
|
||||
export class MoveSelectionToPreviousFindMatchAction extends SelectPreviousFindMatchAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: FIND_IDS.MoveSelectionToPreviousFindMatchAction,
|
||||
label: nls.localize('moveSelectionToPreviousFindMatch', "Move Last Selection To Previous Find Match"),
|
||||
alias: 'Move Last Selection To Previous Find Match',
|
||||
precondition: null
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: editorCommon.ICommonCodeEditor): void {
|
||||
let previousMatch = this._getPreviousMatch(editor);
|
||||
|
||||
if (!previousMatch) {
|
||||
return;
|
||||
}
|
||||
|
||||
let allSelections = editor.getSelections();
|
||||
editor.setSelections(allSelections.slice(0, allSelections.length - 1).concat(previousMatch));
|
||||
editor.revealRangeInCenterIfOutsideViewport(previousMatch, editorCommon.ScrollType.Smooth);
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class AbstractSelectHighlightsAction extends EditorAction {
|
||||
public run(accessor: ServicesAccessor, editor: editorCommon.ICommonCodeEditor): void {
|
||||
let controller = CommonFindController.get(editor);
|
||||
if (!controller) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let matches: Range[] = null;
|
||||
|
||||
const findState = controller.getState();
|
||||
if (findState.isRevealed && findState.isRegex && findState.searchString.length > 0) {
|
||||
|
||||
matches = editor.getModel().findMatches(findState.searchString, true, findState.isRegex, findState.matchCase, findState.wholeWord ? editor.getConfiguration().wordSeparators : null, false).map(m => m.range);
|
||||
|
||||
} else {
|
||||
|
||||
let r = multiCursorFind(editor, {
|
||||
changeFindSearchString: true,
|
||||
allowMultiline: true,
|
||||
highlightFindOptions: true
|
||||
});
|
||||
if (!r) {
|
||||
return;
|
||||
}
|
||||
|
||||
matches = editor.getModel().findMatches(r.searchText, true, false, r.matchCase, r.wholeWord ? editor.getConfiguration().wordSeparators : null, false).map(m => m.range);
|
||||
}
|
||||
|
||||
if (matches.length > 0) {
|
||||
let editorSelection = editor.getSelection();
|
||||
for (let i = 0, len = matches.length; i < len; i++) {
|
||||
let match = matches[i];
|
||||
let intersection = match.intersectRanges(editorSelection);
|
||||
if (intersection) {
|
||||
// bingo!
|
||||
matches.splice(i, 1);
|
||||
matches.unshift(match);
|
||||
break;
|
||||
}
|
||||
}
|
||||
editor.setSelections(matches.map(m => new Selection(m.startLineNumber, m.startColumn, m.endLineNumber, m.endColumn)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@editorAction
|
||||
export class SelectHighlightsAction extends AbstractSelectHighlightsAction {
|
||||
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
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@editorAction
|
||||
export class CompatChangeAll extends AbstractSelectHighlightsAction {
|
||||
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
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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.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 editorCommon.IEditorContribution {
|
||||
private static ID = 'editor.contrib.selectionHighlighter';
|
||||
|
||||
private editor: editorCommon.ICommonCodeEditor;
|
||||
private _isEnabled: boolean;
|
||||
private decorations: string[];
|
||||
private updateSoon: RunOnceScheduler;
|
||||
private state: SelectionHighlighterState;
|
||||
|
||||
constructor(editor: editorCommon.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: editorCommon.ICommonCodeEditor): SelectionHighlighterState {
|
||||
const model = editor.getModel();
|
||||
if (!model) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const config = editor.getConfiguration();
|
||||
|
||||
let lastWordUnderCursor: Selection = null;
|
||||
if (!isEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const r = multiCursorFind(editor, {
|
||||
changeFindSearchString: false,
|
||||
allowMultiline: false,
|
||||
highlightFindOptions: false
|
||||
});
|
||||
if (!r) {
|
||||
return 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const controller = CommonFindController.get(editor);
|
||||
if (!controller) {
|
||||
return null;
|
||||
}
|
||||
const findState = controller.getState();
|
||||
const caseSensitive = findState.matchCase;
|
||||
|
||||
const selections = editor.getSelections();
|
||||
let firstSelectedText = model.getValueInRange(selections[0]);
|
||||
if (!caseSensitive) {
|
||||
firstSelectedText = firstSelectedText.toLowerCase();
|
||||
}
|
||||
for (let i = 1; i < selections.length; i++) {
|
||||
let selectedText = model.getValueInRange(selections[i]);
|
||||
if (!caseSensitive) {
|
||||
selectedText = selectedText.toLowerCase();
|
||||
}
|
||||
if (firstSelectedText !== selectedText) {
|
||||
// not all selections have the same text
|
||||
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: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
className: 'selectionHighlight',
|
||||
overviewRuler: {
|
||||
color: themeColorFromId(overviewRulerSelectionHighlightForeground),
|
||||
darkColor: themeColorFromId(overviewRulerSelectionHighlightForeground),
|
||||
position: editorCommon.OverviewRulerLane.Center
|
||||
}
|
||||
});
|
||||
|
||||
private static _SELECTION_HIGHLIGHT = ModelDecorationOptions.register({
|
||||
stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
className: 'selectionHighlight',
|
||||
});
|
||||
|
||||
public dispose(): void {
|
||||
this._setState(null);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@editorAction
|
||||
export class ShowNextFindTermAction extends MatchFindAction {
|
||||
|
||||
@@ -16,6 +16,7 @@ export class FindDecorations implements IDisposable {
|
||||
|
||||
private _editor: editorCommon.ICommonCodeEditor;
|
||||
private _decorations: string[];
|
||||
private _overviewRulerApproximateDecorations: string[];
|
||||
private _findScopeDecorationId: string;
|
||||
private _rangeHighlightDecorationId: string;
|
||||
private _highlightedDecorationId: string;
|
||||
@@ -24,6 +25,7 @@ export class FindDecorations implements IDisposable {
|
||||
constructor(editor: editorCommon.ICommonCodeEditor) {
|
||||
this._editor = editor;
|
||||
this._decorations = [];
|
||||
this._overviewRulerApproximateDecorations = [];
|
||||
this._findScopeDecorationId = null;
|
||||
this._rangeHighlightDecorationId = null;
|
||||
this._highlightedDecorationId = null;
|
||||
@@ -35,6 +37,7 @@ export class FindDecorations implements IDisposable {
|
||||
|
||||
this._editor = null;
|
||||
this._decorations = [];
|
||||
this._overviewRulerApproximateDecorations = [];
|
||||
this._findScopeDecorationId = null;
|
||||
this._rangeHighlightDecorationId = null;
|
||||
this._highlightedDecorationId = null;
|
||||
@@ -43,6 +46,7 @@ export class FindDecorations implements IDisposable {
|
||||
|
||||
public reset(): void {
|
||||
this._decorations = [];
|
||||
this._overviewRulerApproximateDecorations = [];
|
||||
this._findScopeDecorationId = null;
|
||||
this._rangeHighlightDecorationId = null;
|
||||
this._highlightedDecorationId = null;
|
||||
@@ -68,11 +72,21 @@ export class FindDecorations implements IDisposable {
|
||||
this.setCurrentFindMatch(null);
|
||||
}
|
||||
|
||||
private _getDecorationIndex(decorationId: string): number {
|
||||
const index = this._decorations.indexOf(decorationId);
|
||||
if (index >= 0) {
|
||||
return index + 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
public getCurrentMatchesPosition(desiredRange: Range): number {
|
||||
for (let i = 0, len = this._decorations.length; i < len; i++) {
|
||||
let range = this._editor.getModel().getDecorationRange(this._decorations[i]);
|
||||
if (desiredRange.equalsRange(range)) {
|
||||
return (i + 1);
|
||||
let candidates = this._editor.getModel().getDecorationsInRange(desiredRange);
|
||||
for (let i = 0, len = candidates.length; i < len; i++) {
|
||||
const candidate = candidates[i];
|
||||
const candidateOpts = candidate.options;
|
||||
if (candidateOpts === FindDecorations._FIND_MATCH_DECORATION || candidateOpts === FindDecorations._CURRENT_FIND_MATCH_DECORATION) {
|
||||
return this._getDecorationIndex(candidate.id);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
@@ -95,12 +109,12 @@ export class FindDecorations implements IDisposable {
|
||||
if (this._highlightedDecorationId !== null || newCurrentDecorationId !== null) {
|
||||
this._editor.changeDecorations((changeAccessor: editorCommon.IModelDecorationsChangeAccessor) => {
|
||||
if (this._highlightedDecorationId !== null) {
|
||||
changeAccessor.changeDecorationOptions(this._highlightedDecorationId, FindDecorations.createFindMatchDecorationOptions(false));
|
||||
changeAccessor.changeDecorationOptions(this._highlightedDecorationId, FindDecorations._FIND_MATCH_DECORATION);
|
||||
this._highlightedDecorationId = null;
|
||||
}
|
||||
if (newCurrentDecorationId !== null) {
|
||||
this._highlightedDecorationId = newCurrentDecorationId;
|
||||
changeAccessor.changeDecorationOptions(this._highlightedDecorationId, FindDecorations.createFindMatchDecorationOptions(true));
|
||||
changeAccessor.changeDecorationOptions(this._highlightedDecorationId, FindDecorations._CURRENT_FIND_MATCH_DECORATION);
|
||||
}
|
||||
if (this._rangeHighlightDecorationId !== null) {
|
||||
changeAccessor.removeDecoration(this._rangeHighlightDecorationId);
|
||||
@@ -108,6 +122,11 @@ export class FindDecorations implements IDisposable {
|
||||
}
|
||||
if (newCurrentDecorationId !== null) {
|
||||
let rng = this._editor.getModel().getDecorationRange(newCurrentDecorationId);
|
||||
if (rng.startLineNumber !== rng.endLineNumber && rng.endColumn === 1) {
|
||||
let lineBeforeEnd = rng.endLineNumber - 1;
|
||||
let lineBeforeEndMaxColumn = this._editor.getModel().getLineMaxColumn(lineBeforeEnd);
|
||||
rng = new Range(rng.startLineNumber, rng.startColumn, lineBeforeEnd, lineBeforeEndMaxColumn);
|
||||
}
|
||||
this._rangeHighlightDecorationId = changeAccessor.addDecoration(rng, FindDecorations._RANGE_HIGHLIGHT_DECORATION);
|
||||
}
|
||||
});
|
||||
@@ -116,34 +135,82 @@ export class FindDecorations implements IDisposable {
|
||||
return matchPosition;
|
||||
}
|
||||
|
||||
public set(matches: Range[], findScope: Range): void {
|
||||
let newDecorations: editorCommon.IModelDeltaDecoration[] = matches.map((match) => {
|
||||
return {
|
||||
range: match,
|
||||
options: FindDecorations.createFindMatchDecorationOptions(false)
|
||||
};
|
||||
});
|
||||
if (findScope) {
|
||||
newDecorations.unshift({
|
||||
range: findScope,
|
||||
options: FindDecorations._FIND_SCOPE_DECORATION
|
||||
});
|
||||
}
|
||||
let tmpDecorations = this._editor.deltaDecorations(this._allDecorations(), newDecorations);
|
||||
public set(findMatches: editorCommon.FindMatch[], findScope: Range): void {
|
||||
this._editor.changeDecorations((accessor) => {
|
||||
|
||||
if (findScope) {
|
||||
this._findScopeDecorationId = tmpDecorations.shift();
|
||||
} else {
|
||||
this._findScopeDecorationId = null;
|
||||
}
|
||||
this._decorations = tmpDecorations;
|
||||
this._rangeHighlightDecorationId = null;
|
||||
this._highlightedDecorationId = null;
|
||||
let findMatchesOptions: ModelDecorationOptions = FindDecorations._FIND_MATCH_DECORATION;
|
||||
let newOverviewRulerApproximateDecorations: editorCommon.IModelDeltaDecoration[] = [];
|
||||
|
||||
if (findMatches.length > 1000) {
|
||||
// we go into a mode where the overview ruler gets "approximate" decorations
|
||||
// the reason is that the overview ruler paints all the decorations in the file and we don't want to cause freezes
|
||||
findMatchesOptions = FindDecorations._FIND_MATCH_NO_OVERVIEW_DECORATION;
|
||||
|
||||
// approximate a distance in lines where matches should be merged
|
||||
const lineCount = this._editor.getModel().getLineCount();
|
||||
const height = this._editor.getLayoutInfo().height;
|
||||
const approxPixelsPerLine = height / lineCount;
|
||||
const mergeLinesDelta = Math.max(2, Math.ceil(3 / approxPixelsPerLine));
|
||||
|
||||
// merge decorations as much as possible
|
||||
let prevStartLineNumber = findMatches[0].range.startLineNumber;
|
||||
let prevEndLineNumber = findMatches[0].range.endLineNumber;
|
||||
for (let i = 1, len = findMatches.length; i < len; i++) {
|
||||
const range = findMatches[i].range;
|
||||
if (prevEndLineNumber + mergeLinesDelta >= range.startLineNumber) {
|
||||
if (range.endLineNumber > prevEndLineNumber) {
|
||||
prevEndLineNumber = range.endLineNumber;
|
||||
}
|
||||
} else {
|
||||
newOverviewRulerApproximateDecorations.push({
|
||||
range: new Range(prevStartLineNumber, 1, prevEndLineNumber, 1),
|
||||
options: FindDecorations._FIND_MATCH_ONLY_OVERVIEW_DECORATION
|
||||
});
|
||||
prevStartLineNumber = range.startLineNumber;
|
||||
prevEndLineNumber = range.endLineNumber;
|
||||
}
|
||||
}
|
||||
|
||||
newOverviewRulerApproximateDecorations.push({
|
||||
range: new Range(prevStartLineNumber, 1, prevEndLineNumber, 1),
|
||||
options: FindDecorations._FIND_MATCH_ONLY_OVERVIEW_DECORATION
|
||||
});
|
||||
}
|
||||
|
||||
// Find matches
|
||||
let newFindMatchesDecorations: editorCommon.IModelDeltaDecoration[] = new Array<editorCommon.IModelDeltaDecoration>(findMatches.length);
|
||||
for (let i = 0, len = findMatches.length; i < len; i++) {
|
||||
newFindMatchesDecorations[i] = {
|
||||
range: findMatches[i].range,
|
||||
options: findMatchesOptions
|
||||
};
|
||||
}
|
||||
this._decorations = accessor.deltaDecorations(this._decorations, newFindMatchesDecorations);
|
||||
|
||||
// Overview ruler approximate decorations
|
||||
this._overviewRulerApproximateDecorations = accessor.deltaDecorations(this._overviewRulerApproximateDecorations, newOverviewRulerApproximateDecorations);
|
||||
|
||||
// Range highlight
|
||||
if (this._rangeHighlightDecorationId) {
|
||||
accessor.removeDecoration(this._rangeHighlightDecorationId);
|
||||
this._rangeHighlightDecorationId = null;
|
||||
}
|
||||
|
||||
// Find scope
|
||||
if (this._findScopeDecorationId) {
|
||||
accessor.removeDecoration(this._findScopeDecorationId);
|
||||
this._findScopeDecorationId = null;
|
||||
}
|
||||
if (findScope) {
|
||||
this._findScopeDecorationId = accessor.addDecoration(findScope, FindDecorations._FIND_SCOPE_DECORATION);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _allDecorations(): string[] {
|
||||
let result: string[] = [];
|
||||
result = result.concat(this._decorations);
|
||||
result = result.concat(this._overviewRulerApproximateDecorations);
|
||||
if (this._findScopeDecorationId) {
|
||||
result.push(this._findScopeDecorationId);
|
||||
}
|
||||
@@ -153,10 +220,6 @@ export class FindDecorations implements IDisposable {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static createFindMatchDecorationOptions(isCurrent: boolean): ModelDecorationOptions {
|
||||
return (isCurrent ? this._CURRENT_FIND_MATCH_DECORATION : this._FIND_MATCH_DECORATION);
|
||||
}
|
||||
|
||||
private static _CURRENT_FIND_MATCH_DECORATION = ModelDecorationOptions.register({
|
||||
stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
className: 'currentFindMatch',
|
||||
@@ -179,6 +242,21 @@ export class FindDecorations implements IDisposable {
|
||||
}
|
||||
});
|
||||
|
||||
private static _FIND_MATCH_NO_OVERVIEW_DECORATION = ModelDecorationOptions.register({
|
||||
stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
className: 'findMatch',
|
||||
showIfCollapsed: true
|
||||
});
|
||||
|
||||
private static _FIND_MATCH_ONLY_OVERVIEW_DECORATION = ModelDecorationOptions.register({
|
||||
stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
overviewRuler: {
|
||||
color: themeColorFromId(editorFindMatchHighlight),
|
||||
darkColor: themeColorFromId(editorFindMatchHighlight),
|
||||
position: editorCommon.OverviewRulerLane.Center
|
||||
}
|
||||
});
|
||||
|
||||
private static _RANGE_HIGHLIGHT_DECORATION = ModelDecorationOptions.register({
|
||||
stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
className: 'rangeHighlight',
|
||||
|
||||
@@ -50,10 +50,6 @@ export const FIND_IDS = {
|
||||
PreviousMatchFindAction: 'editor.action.previousMatchFindAction',
|
||||
NextSelectionMatchFindAction: 'editor.action.nextSelectionMatchFindAction',
|
||||
PreviousSelectionMatchFindAction: 'editor.action.previousSelectionMatchFindAction',
|
||||
AddSelectionToNextFindMatchAction: 'editor.action.addSelectionToNextFindMatch',
|
||||
AddSelectionToPreviousFindMatchAction: 'editor.action.addSelectionToPreviousFindMatch',
|
||||
MoveSelectionToNextFindMatchAction: 'editor.action.moveSelectionToNextFindMatch',
|
||||
MoveSelectionToPreviousFindMatchAction: 'editor.action.moveSelectionToPreviousFindMatch',
|
||||
StartFindReplaceAction: 'editor.action.startFindReplaceAction',
|
||||
CloseFindWidgetCommand: 'closeFindWidget',
|
||||
ToggleCaseSensitiveCommand: 'toggleFindCaseSensitive',
|
||||
@@ -67,7 +63,7 @@ export const FIND_IDS = {
|
||||
ShowNextFindTermAction: 'find.history.showNext'
|
||||
};
|
||||
|
||||
export const MATCHES_LIMIT = 999;
|
||||
export const MATCHES_LIMIT = 19999;
|
||||
|
||||
export class FindModelBoundToEditorModel {
|
||||
|
||||
@@ -130,6 +126,10 @@ export class FindModelBoundToEditorModel {
|
||||
// The find model is disposed during a find state changed event
|
||||
return;
|
||||
}
|
||||
if (!this._editor.getModel()) {
|
||||
// The find model will be disposed momentarily
|
||||
return;
|
||||
}
|
||||
if (e.searchString || e.isReplaceRevealed || e.isRegex || e.wholeWord || e.matchCase || e.searchScope) {
|
||||
if (e.searchScope) {
|
||||
this.research(e.moveCursor, this._state.searchScope);
|
||||
@@ -171,7 +171,7 @@ export class FindModelBoundToEditorModel {
|
||||
}
|
||||
|
||||
let findMatches = this._findMatches(findScope, false, MATCHES_LIMIT);
|
||||
this._decorations.set(findMatches.map(match => match.range), findScope);
|
||||
this._decorations.set(findMatches, findScope);
|
||||
|
||||
this._state.changeMatchInfo(
|
||||
this._decorations.getCurrentMatchesPosition(this._editor.getSelection()),
|
||||
@@ -409,6 +409,18 @@ export class FindModelBoundToEditorModel {
|
||||
return;
|
||||
}
|
||||
|
||||
let searchRegex = searchData.regex;
|
||||
if (!searchRegex.multiline) {
|
||||
let mod = 'm';
|
||||
if (searchRegex.ignoreCase) {
|
||||
mod += 'i';
|
||||
}
|
||||
if (searchRegex.global) {
|
||||
mod += 'g';
|
||||
}
|
||||
searchRegex = new RegExp(searchRegex.source, mod);
|
||||
}
|
||||
|
||||
const model = this._editor.getModel();
|
||||
const modelText = model.getValue(editorCommon.EndOfLinePreference.LF);
|
||||
const fullModelRange = model.getFullModelRange();
|
||||
@@ -416,11 +428,11 @@ export class FindModelBoundToEditorModel {
|
||||
const replacePattern = this._getReplacePattern();
|
||||
let resultText: string;
|
||||
if (replacePattern.hasReplacementPatterns) {
|
||||
resultText = modelText.replace(searchData.regex, function () {
|
||||
resultText = modelText.replace(searchRegex, function () {
|
||||
return replacePattern.buildReplaceString(<string[]><any>arguments);
|
||||
});
|
||||
} else {
|
||||
resultText = modelText.replace(searchData.regex, replacePattern.buildReplaceString(null));
|
||||
resultText = modelText.replace(searchRegex, replacePattern.buildReplaceString(null));
|
||||
}
|
||||
|
||||
let command = new ReplaceCommandThatPreservesSelection(fullModelRange, resultText, this._editor.getSelection());
|
||||
|
||||
@@ -25,17 +25,36 @@ export interface FindReplaceStateChangedEvent {
|
||||
currentMatch: boolean;
|
||||
}
|
||||
|
||||
export const enum FindOptionOverride {
|
||||
NotSet = 0,
|
||||
True = 1,
|
||||
False = 2
|
||||
}
|
||||
|
||||
export interface INewFindReplaceState {
|
||||
searchString?: string;
|
||||
replaceString?: string;
|
||||
isRevealed?: boolean;
|
||||
isReplaceRevealed?: boolean;
|
||||
isRegex?: boolean;
|
||||
isRegexOverride?: FindOptionOverride;
|
||||
wholeWord?: boolean;
|
||||
wholeWordOverride?: FindOptionOverride;
|
||||
matchCase?: boolean;
|
||||
matchCaseOverride?: FindOptionOverride;
|
||||
searchScope?: Range;
|
||||
}
|
||||
|
||||
function effectiveOptionValue(override: FindOptionOverride, value: boolean): boolean {
|
||||
if (override === FindOptionOverride.True) {
|
||||
return true;
|
||||
}
|
||||
if (override === FindOptionOverride.False) {
|
||||
return false;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export class FindReplaceState implements IDisposable {
|
||||
|
||||
private static _CHANGED_EVENT = 'changed';
|
||||
@@ -45,8 +64,11 @@ export class FindReplaceState implements IDisposable {
|
||||
private _isRevealed: boolean;
|
||||
private _isReplaceRevealed: boolean;
|
||||
private _isRegex: boolean;
|
||||
private _isRegexOverride: FindOptionOverride;
|
||||
private _wholeWord: boolean;
|
||||
private _wholeWordOverride: FindOptionOverride;
|
||||
private _matchCase: boolean;
|
||||
private _matchCaseOverride: FindOptionOverride;
|
||||
private _searchScope: Range;
|
||||
private _matchesPosition: number;
|
||||
private _matchesCount: number;
|
||||
@@ -57,9 +79,9 @@ export class FindReplaceState implements IDisposable {
|
||||
public get replaceString(): string { return this._replaceString; }
|
||||
public get isRevealed(): boolean { return this._isRevealed; }
|
||||
public get isReplaceRevealed(): boolean { return this._isReplaceRevealed; }
|
||||
public get isRegex(): boolean { return this._isRegex; }
|
||||
public get wholeWord(): boolean { return this._wholeWord; }
|
||||
public get matchCase(): boolean { return this._matchCase; }
|
||||
public get isRegex(): boolean { return effectiveOptionValue(this._isRegexOverride, this._isRegex); }
|
||||
public get wholeWord(): boolean { return effectiveOptionValue(this._wholeWordOverride, this._wholeWord); }
|
||||
public get matchCase(): boolean { return effectiveOptionValue(this._matchCaseOverride, this._matchCase); }
|
||||
public get searchScope(): Range { return this._searchScope; }
|
||||
public get matchesPosition(): number { return this._matchesPosition; }
|
||||
public get matchesCount(): number { return this._matchesCount; }
|
||||
@@ -71,8 +93,11 @@ export class FindReplaceState implements IDisposable {
|
||||
this._isRevealed = false;
|
||||
this._isReplaceRevealed = false;
|
||||
this._isRegex = false;
|
||||
this._isRegexOverride = FindOptionOverride.NotSet;
|
||||
this._wholeWord = false;
|
||||
this._wholeWordOverride = FindOptionOverride.NotSet;
|
||||
this._matchCase = false;
|
||||
this._matchCaseOverride = FindOptionOverride.NotSet;
|
||||
this._searchScope = null;
|
||||
this._matchesPosition = 0;
|
||||
this._matchesCount = 0;
|
||||
@@ -155,6 +180,10 @@ export class FindReplaceState implements IDisposable {
|
||||
};
|
||||
let somethingChanged = false;
|
||||
|
||||
const oldEffectiveIsRegex = this.isRegex;
|
||||
const oldEffectiveWholeWords = this.wholeWord;
|
||||
const oldEffectiveMatchCase = this.matchCase;
|
||||
|
||||
if (typeof newState.searchString !== 'undefined') {
|
||||
if (this._searchString !== newState.searchString) {
|
||||
this._searchString = newState.searchString;
|
||||
@@ -184,25 +213,13 @@ export class FindReplaceState implements IDisposable {
|
||||
}
|
||||
}
|
||||
if (typeof newState.isRegex !== 'undefined') {
|
||||
if (this._isRegex !== newState.isRegex) {
|
||||
this._isRegex = newState.isRegex;
|
||||
changeEvent.isRegex = true;
|
||||
somethingChanged = true;
|
||||
}
|
||||
this._isRegex = newState.isRegex;
|
||||
}
|
||||
if (typeof newState.wholeWord !== 'undefined') {
|
||||
if (this._wholeWord !== newState.wholeWord) {
|
||||
this._wholeWord = newState.wholeWord;
|
||||
changeEvent.wholeWord = true;
|
||||
somethingChanged = true;
|
||||
}
|
||||
this._wholeWord = newState.wholeWord;
|
||||
}
|
||||
if (typeof newState.matchCase !== 'undefined') {
|
||||
if (this._matchCase !== newState.matchCase) {
|
||||
this._matchCase = newState.matchCase;
|
||||
changeEvent.matchCase = true;
|
||||
somethingChanged = true;
|
||||
}
|
||||
this._matchCase = newState.matchCase;
|
||||
}
|
||||
if (typeof newState.searchScope !== 'undefined') {
|
||||
if (!Range.equalsRange(this._searchScope, newState.searchScope)) {
|
||||
@@ -212,6 +229,24 @@ export class FindReplaceState implements IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
// Overrides get set when they explicitly come in and get reset anytime something else changes
|
||||
this._isRegexOverride = (typeof newState.isRegexOverride !== 'undefined' ? newState.isRegexOverride : FindOptionOverride.NotSet);
|
||||
this._wholeWordOverride = (typeof newState.wholeWordOverride !== 'undefined' ? newState.wholeWordOverride : FindOptionOverride.NotSet);
|
||||
this._matchCaseOverride = (typeof newState.matchCaseOverride !== 'undefined' ? newState.matchCaseOverride : FindOptionOverride.NotSet);
|
||||
|
||||
if (oldEffectiveIsRegex !== this.isRegex) {
|
||||
somethingChanged = true;
|
||||
changeEvent.isRegex = true;
|
||||
}
|
||||
if (oldEffectiveWholeWords !== this.wholeWord) {
|
||||
somethingChanged = true;
|
||||
changeEvent.wholeWord = true;
|
||||
}
|
||||
if (oldEffectiveMatchCase !== this.matchCase) {
|
||||
somethingChanged = true;
|
||||
changeEvent.matchCase = true;
|
||||
}
|
||||
|
||||
if (somethingChanged) {
|
||||
this._eventEmitter.emit(FindReplaceState._CHANGED_EVENT, changeEvent);
|
||||
}
|
||||
|
||||
@@ -11,20 +11,16 @@ import { EditOperation } from 'vs/editor/common/core/editOperation';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { EndOfLineSequence, ICommonCodeEditor, Handler } from 'vs/editor/common/editorCommon';
|
||||
import {
|
||||
CommonFindController, FindStartFocusAction, IFindStartOptions,
|
||||
NextMatchFindAction, StartFindAction, SelectHighlightsAction,
|
||||
AddSelectionToNextFindMatchAction
|
||||
} from 'vs/editor/contrib/find/common/findController';
|
||||
import { MockCodeEditor, withMockCodeEditor } from 'vs/editor/test/common/mocks/mockCodeEditor';
|
||||
import { ICommonCodeEditor } from 'vs/editor/common/editorCommon';
|
||||
import { CommonFindController, FindStartFocusAction, IFindStartOptions, NextMatchFindAction, StartFindAction } from 'vs/editor/contrib/find/common/findController';
|
||||
import { withMockCodeEditor } from 'vs/editor/test/common/mocks/mockCodeEditor';
|
||||
import { HistoryNavigator } from 'vs/base/common/history';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { Delayer } from 'vs/base/common/async';
|
||||
|
||||
class TestFindController extends CommonFindController {
|
||||
export class TestFindController extends CommonFindController {
|
||||
|
||||
public hasFocus: boolean;
|
||||
public delayUpdateHistory: boolean = false;
|
||||
@@ -188,60 +184,6 @@ suite('FindController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
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<TestFindController>(TestFindController);
|
||||
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]);
|
||||
|
||||
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<TestFindController>(TestFindController);
|
||||
let selectHighlightsAction = new SelectHighlightsAction();
|
||||
|
||||
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');
|
||||
|
||||
findController.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
test('issue #9043: Clear search scope when find widget is hidden', () => {
|
||||
withMockCodeEditor([
|
||||
'var x = (3 * 5)',
|
||||
@@ -380,119 +322,6 @@ suite('FindController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('AddSelectionToNextFindMatchAction can work with multiline', () => {
|
||||
withMockCodeEditor([
|
||||
'',
|
||||
'qwe',
|
||||
'rty',
|
||||
'',
|
||||
'qwe',
|
||||
'',
|
||||
'rty',
|
||||
'qwe',
|
||||
'rty'
|
||||
], { serviceCollection: serviceCollection }, (editor, cursor) => {
|
||||
|
||||
let findController = editor.registerAndInstantiateContribution<TestFindController>(TestFindController);
|
||||
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]);
|
||||
|
||||
findController.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
test('issue #6661: AddSelectionToNextFindMatchAction can work with touching ranges', () => {
|
||||
withMockCodeEditor([
|
||||
'abcabc',
|
||||
'abc',
|
||||
'abcabc',
|
||||
], { serviceCollection: serviceCollection }, (editor, cursor) => {
|
||||
|
||||
let findController = editor.registerAndInstantiateContribution<TestFindController>(TestFindController);
|
||||
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'));
|
||||
|
||||
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<TestFindController>(TestFindController);
|
||||
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]);
|
||||
|
||||
findController.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
test('issue #18111: Regex replace with single space replaces with no space', () => {
|
||||
withMockCodeEditor([
|
||||
'HRESULT OnAmbientPropertyChange(DISPID dispid);'
|
||||
@@ -555,237 +384,6 @@ suite('FindController', () => {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function testAddSelectionToNextFindMatchAction(text: string[], callback: (editor: MockCodeEditor, action: AddSelectionToNextFindMatchAction, findController: TestFindController) => void): void {
|
||||
withMockCodeEditor(text, { serviceCollection: serviceCollection }, (editor, cursor) => {
|
||||
|
||||
let findController = editor.registerAndInstantiateContribution<TestFindController>(TestFindController);
|
||||
|
||||
let action = new AddSelectionToNextFindMatchAction();
|
||||
|
||||
callback(editor, action, findController);
|
||||
|
||||
findController.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
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('FindController query options persistence', () => {
|
||||
|
||||
@@ -2004,6 +2004,29 @@ suite('FindModel', () => {
|
||||
findState.dispose();
|
||||
});
|
||||
|
||||
findTest('issue #32522 replaceAll with ^ on more than 1000 matches', (editor, cursor) => {
|
||||
let initialText = '';
|
||||
for (let i = 0; i < 1100; i++) {
|
||||
initialText += 'line' + i + '\n';
|
||||
}
|
||||
editor.getModel().setValue(initialText);
|
||||
let findState = new FindReplaceState();
|
||||
findState.change({ searchString: '^', replaceString: 'a ', isRegex: true }, false);
|
||||
let findModel = new FindModelBoundToEditorModel(editor, findState);
|
||||
|
||||
findModel.replaceAll();
|
||||
|
||||
let expectedText = '';
|
||||
for (let i = 0; i < 1100; i++) {
|
||||
expectedText += 'a line' + i + '\n';
|
||||
}
|
||||
expectedText += 'a ';
|
||||
assert.equal(editor.getModel().getValue(), expectedText);
|
||||
|
||||
findModel.dispose();
|
||||
findState.dispose();
|
||||
});
|
||||
|
||||
findTest('issue #19740 Find and replace capture group/backreference inserts `undefined` instead of empty string', (editor, cursor) => {
|
||||
let findState = new FindReplaceState();
|
||||
findState.change({ searchString: 'hello(z)?', replaceString: 'hi$1', isRegex: true, matchCase: true }, false);
|
||||
|
||||
Reference in New Issue
Block a user