Merge from vscode 2cfc8172e533e50c90e6a3152f6bfb1f82f963f3 (#6516)

* Merge from vscode 2cfc8172e533e50c90e6a3152f6bfb1f82f963f3

* fix tests
This commit is contained in:
Anthony Dresser
2019-07-28 15:15:24 -07:00
committed by GitHub
parent aacf1e7f1c
commit 1d56a17f32
292 changed files with 19784 additions and 1873 deletions

View File

@@ -112,7 +112,8 @@ export class CommonFindController extends Disposable implements editorCommon.IEd
searchScope: null,
matchCase: this._storageService.getBoolean('editor.matchCase', StorageScope.WORKSPACE, false),
wholeWord: this._storageService.getBoolean('editor.wholeWord', StorageScope.WORKSPACE, false),
isRegex: this._storageService.getBoolean('editor.isRegex', StorageScope.WORKSPACE, false)
isRegex: this._storageService.getBoolean('editor.isRegex', StorageScope.WORKSPACE, false),
preserveCase: this._storageService.getBoolean('editor.preserveCase', StorageScope.WORKSPACE, false)
}, false);
if (shouldRestartFind) {
@@ -170,13 +171,17 @@ export class CommonFindController extends Disposable implements editorCommon.IEd
if (e.matchCase) {
this._storageService.store('editor.matchCase', this._state.actualMatchCase, StorageScope.WORKSPACE);
}
if (e.preserveCase) {
this._storageService.store('editor.preserveCase', this._state.actualPreserveCase, StorageScope.WORKSPACE);
}
}
private loadQueryState() {
this._state.change({
matchCase: this._storageService.getBoolean('editor.matchCase', StorageScope.WORKSPACE, this._state.matchCase),
wholeWord: this._storageService.getBoolean('editor.wholeWord', StorageScope.WORKSPACE, this._state.wholeWord),
isRegex: this._storageService.getBoolean('editor.isRegex', StorageScope.WORKSPACE, this._state.isRegex)
isRegex: this._storageService.getBoolean('editor.isRegex', StorageScope.WORKSPACE, this._state.isRegex),
preserveCase: this._storageService.getBoolean('editor.preserveCase', StorageScope.WORKSPACE, this._state.preserveCase)
}, false);
}
@@ -217,6 +222,11 @@ export class CommonFindController extends Disposable implements editorCommon.IEd
}
}
public togglePreserveCase(): void {
this._state.change({ preserveCase: !this._state.preserveCase }, false);
this.highlightFindOptions();
}
public toggleSearchScope(): void {
if (this._state.searchScope) {
this._state.change({ searchScope: null }, true);

View File

@@ -59,6 +59,7 @@ export const FIND_IDS = {
ToggleWholeWordCommand: 'toggleFindWholeWord',
ToggleRegexCommand: 'toggleFindRegex',
ToggleSearchScopeCommand: 'toggleFindInSelection',
TogglePreserveCaseCommand: 'togglePreserveCase',
ReplaceOneAction: 'editor.action.replaceOne',
ReplaceAllAction: 'editor.action.replaceAll',
SelectAllMatchesAction: 'editor.action.selectAllMatches'
@@ -416,11 +417,11 @@ export class FindModelBoundToEditorModel {
let replacePattern = this._getReplacePattern();
let selection = this._editor.getSelection();
let nextMatch = this._getNextMatch(selection.getStartPosition(), replacePattern.hasReplacementPatterns, false);
let nextMatch = this._getNextMatch(selection.getStartPosition(), true, false);
if (nextMatch) {
if (selection.equalsRange(nextMatch.range)) {
// selection sits on a find match => replace it!
let replaceString = replacePattern.buildReplaceString(nextMatch.matches);
let replaceString = replacePattern.buildReplaceString(nextMatch.matches, this._state.preserveCase);
let command = new ReplaceCommand(selection, replaceString);
@@ -482,12 +483,14 @@ export class FindModelBoundToEditorModel {
const replacePattern = this._getReplacePattern();
let resultText: string;
const preserveCase = this._state.preserveCase;
if (replacePattern.hasReplacementPatterns) {
resultText = modelText.replace(searchRegex, function () {
return replacePattern.buildReplaceString(<string[]><any>arguments);
return replacePattern.buildReplaceString(<string[]><any>arguments, preserveCase);
});
} else {
resultText = modelText.replace(searchRegex, replacePattern.buildReplaceString(null));
resultText = modelText.replace(searchRegex, replacePattern.buildReplaceString(null, preserveCase));
}
let command = new ReplaceCommandThatPreservesSelection(fullModelRange, resultText, this._editor.getSelection());
@@ -501,7 +504,7 @@ export class FindModelBoundToEditorModel {
let replaceStrings: string[] = [];
for (let i = 0, len = matches.length; i < len; i++) {
replaceStrings[i] = replacePattern.buildReplaceString(matches[i].matches);
replaceStrings[i] = replacePattern.buildReplaceString(matches[i].matches, this._state.preserveCase);
}
let command = new ReplaceAllCommand(this._editor.getSelection(), matches.map(m => m.range), replaceStrings);

View File

@@ -18,6 +18,7 @@ export interface FindReplaceStateChangedEvent {
isRegex: boolean;
wholeWord: boolean;
matchCase: boolean;
preserveCase: boolean;
searchScope: boolean;
matchesPosition: boolean;
matchesCount: boolean;
@@ -41,6 +42,8 @@ export interface INewFindReplaceState {
wholeWordOverride?: FindOptionOverride;
matchCase?: boolean;
matchCaseOverride?: FindOptionOverride;
preserveCase?: boolean;
preserveCaseOverride?: FindOptionOverride;
searchScope?: Range | null;
}
@@ -65,6 +68,8 @@ export class FindReplaceState implements IDisposable {
private _wholeWordOverride: FindOptionOverride;
private _matchCase: boolean;
private _matchCaseOverride: FindOptionOverride;
private _preserveCase: boolean;
private _preserveCaseOverride: FindOptionOverride;
private _searchScope: Range | null;
private _matchesPosition: number;
private _matchesCount: number;
@@ -78,10 +83,12 @@ export class FindReplaceState implements IDisposable {
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 preserveCase(): boolean { return effectiveOptionValue(this._preserveCaseOverride, this._preserveCase); }
public get actualIsRegex(): boolean { return this._isRegex; }
public get actualWholeWord(): boolean { return this._wholeWord; }
public get actualMatchCase(): boolean { return this._matchCase; }
public get actualPreserveCase(): boolean { return this._preserveCase; }
public get searchScope(): Range | null { return this._searchScope; }
public get matchesPosition(): number { return this._matchesPosition; }
@@ -100,6 +107,8 @@ export class FindReplaceState implements IDisposable {
this._wholeWordOverride = FindOptionOverride.NotSet;
this._matchCase = false;
this._matchCaseOverride = FindOptionOverride.NotSet;
this._preserveCase = false;
this._preserveCaseOverride = FindOptionOverride.NotSet;
this._searchScope = null;
this._matchesPosition = 0;
this._matchesCount = 0;
@@ -120,6 +129,7 @@ export class FindReplaceState implements IDisposable {
isRegex: false,
wholeWord: false,
matchCase: false,
preserveCase: false,
searchScope: false,
matchesPosition: false,
matchesCount: false,
@@ -169,6 +179,7 @@ export class FindReplaceState implements IDisposable {
isRegex: false,
wholeWord: false,
matchCase: false,
preserveCase: false,
searchScope: false,
matchesPosition: false,
matchesCount: false,
@@ -179,6 +190,7 @@ export class FindReplaceState implements IDisposable {
const oldEffectiveIsRegex = this.isRegex;
const oldEffectiveWholeWords = this.wholeWord;
const oldEffectiveMatchCase = this.matchCase;
const oldEffectivePreserveCase = this.preserveCase;
if (typeof newState.searchString !== 'undefined') {
if (this._searchString !== newState.searchString) {
@@ -217,6 +229,9 @@ export class FindReplaceState implements IDisposable {
if (typeof newState.matchCase !== 'undefined') {
this._matchCase = newState.matchCase;
}
if (typeof newState.preserveCase !== 'undefined') {
this._preserveCase = newState.preserveCase;
}
if (typeof newState.searchScope !== 'undefined') {
if (!Range.equalsRange(this._searchScope, newState.searchScope)) {
this._searchScope = newState.searchScope;
@@ -229,6 +244,7 @@ export class FindReplaceState implements IDisposable {
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);
this._preserveCaseOverride = (typeof newState.preserveCaseOverride !== 'undefined' ? newState.preserveCaseOverride : FindOptionOverride.NotSet);
if (oldEffectiveIsRegex !== this.isRegex) {
somethingChanged = true;
@@ -243,6 +259,11 @@ export class FindReplaceState implements IDisposable {
changeEvent.matchCase = true;
}
if (oldEffectivePreserveCase !== this.preserveCase) {
somethingChanged = true;
changeEvent.preserveCase = true;
}
if (somethingChanged) {
this._onFindReplaceStateChange.fire(changeEvent);
}

View File

@@ -39,6 +39,11 @@
transition: top 200ms linear;
padding: 0 4px;
}
.monaco-editor .find-widget.hiddenEditor {
display: none;
}
/* Find widget when replace is toggled on */
.monaco-editor .find-widget.replaceToggled {
top: -74px; /* find input height + replace input height + shadow (10px) */
@@ -79,6 +84,15 @@
height: 25px;
}
.monaco-editor .find-widget > .find-part .monaco-inputbox > .wrapper > .input {
width: 100% !important;
padding-right: 66px;
}
.monaco-editor .find-widget > .replace-part .monaco-inputbox > .wrapper > .input {
padding-right: 22px;
}
.monaco-editor .find-widget > .find-part .monaco-inputbox > .wrapper > .input,
.monaco-editor .find-widget > .replace-part .monaco-inputbox > .wrapper > .input {
padding-top: 2px;
@@ -224,12 +238,19 @@
}
.monaco-editor .find-widget > .replace-part > .replace-input {
position: relative;
display: flex;
display: -webkit-flex;
vertical-align: middle;
width: auto !important;
}
.monaco-editor .find-widget > .replace-part > .replace-input > .controls {
position: absolute;
top: 3px;
right: 2px;
}
/* REDUCED */
.monaco-editor .find-widget.reduced-find-widget .matchesCount,
.monaco-editor .find-widget.reduced-find-widget .monaco-checkbox {

View File

@@ -13,6 +13,7 @@ import { FindInput, IFindInputStyles } from 'vs/base/browser/ui/findinput/findIn
import { HistoryInputBox, IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox';
import { IHorizontalSashLayoutProvider, ISashEvent, Orientation, Sash } from 'vs/base/browser/ui/sash/sash';
import { Widget } from 'vs/base/browser/ui/widget';
import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox';
import { Delayer } from 'vs/base/common/async';
import { Color } from 'vs/base/common/color';
import { onUnexpectedError } from 'vs/base/common/errors';
@@ -47,6 +48,7 @@ const NLS_TOGGLE_SELECTION_FIND_TITLE = nls.localize('label.toggleSelectionFind'
const NLS_CLOSE_BTN_LABEL = nls.localize('label.closeButton', "Close");
const NLS_REPLACE_INPUT_LABEL = nls.localize('label.replace', "Replace");
const NLS_REPLACE_INPUT_PLACEHOLDER = nls.localize('placeholder.replace', "Replace");
const NLS_PRESERVE_CASE_LABEL = nls.localize('label.preserveCaseCheckbox', "Preserve Case");
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");
@@ -101,6 +103,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
private _nextBtn: SimpleButton;
private _toggleSelectionFind: SimpleCheckbox;
private _closeBtn: SimpleButton;
private _preserveCase: Checkbox;
private _replaceBtn: SimpleButton;
private _replaceAllBtn: SimpleButton;
@@ -590,14 +593,26 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
};
this._findInput.style(inputStyles);
this._replaceInputBox.style(inputStyles);
this._preserveCase.style(inputStyles);
}
private _tryUpdateWidgetWidth() {
if (!this._isVisible) {
return;
}
let editorWidth = this._codeEditor.getConfiguration().layoutInfo.width;
let minimapWidth = this._codeEditor.getConfiguration().layoutInfo.minimapWidth;
const editorContentWidth = this._codeEditor.getConfiguration().layoutInfo.contentWidth;
if (editorContentWidth <= 0) {
// for example, diff view original editor
dom.addClass(this._domNode, 'hiddenEditor');
return;
} else if (dom.hasClass(this._domNode, 'hiddenEditor')) {
dom.removeClass(this._domNode, 'hiddenEditor');
}
const editorWidth = this._codeEditor.getConfiguration().layoutInfo.width;
const minimapWidth = this._codeEditor.getConfiguration().layoutInfo.minimapWidth;
let collapsedFindWidget = false;
let reducedFindWidget = false;
let narrowFindWidget = false;
@@ -913,6 +928,19 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
this._state.change({ replaceString: this._replaceInputBox.value }, false);
}));
this._preserveCase = this._register(new Checkbox({
actionClassName: 'monaco-case-sensitive',
title: NLS_PRESERVE_CASE_LABEL,
isChecked: false,
}));
this._preserveCase.checked = !!this._state.preserveCase;
this._register(this._preserveCase.onChange(viaKeyboard => {
if (!viaKeyboard) {
this._state.change({ preserveCase: !this._state.preserveCase }, false);
this._replaceInputBox.focus();
}
}));
// Replace one button
this._replaceBtn = this._register(new SimpleButton({
label: NLS_REPLACE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.ReplaceOneAction),
@@ -937,6 +965,12 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
}
}));
let controls = document.createElement('div');
controls.className = 'controls';
controls.style.display = 'block';
controls.appendChild(this._preserveCase.domNode);
replaceInput.appendChild(controls);
let replacePart = document.createElement('div');
replacePart.className = 'replace-part';
replacePart.appendChild(replaceInput);

View File

@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { CharCode } from 'vs/base/common/charCode';
import { containsUppercaseCharacter } from 'vs/base/common/strings';
const enum ReplacePatternKind {
StaticValue = 0,
@@ -48,9 +49,22 @@ export class ReplacePattern {
}
}
public buildReplaceString(matches: string[] | null): string {
public buildReplaceString(matches: string[] | null, preserveCase?: boolean): string {
if (this._state.kind === ReplacePatternKind.StaticValue) {
return this._state.staticValue;
if (preserveCase && matches && (matches[0] !== '')) {
if (matches[0].toUpperCase() === matches[0]) {
return this._state.staticValue.toUpperCase();
} else if (matches[0].toLowerCase() === matches[0]) {
return this._state.staticValue.toLowerCase();
} else if (containsUppercaseCharacter(matches[0][0])) {
return this._state.staticValue[0].toUpperCase() + this._state.staticValue.substr(1);
} else {
// we don't understand its pattern yet.
return this._state.staticValue;
}
} else {
return this._state.staticValue;
}
}
let result = '';

View File

@@ -41,7 +41,8 @@ export abstract class SimpleFindWidget extends Widget {
@IContextViewService private readonly _contextViewService: IContextViewService,
@IContextKeyService contextKeyService: IContextKeyService,
private readonly _state: FindReplaceState = new FindReplaceState(),
showOptionButtons?: boolean
showOptionButtons?: boolean,
private readonly _invertDefaultDirection: boolean = false
) {
super();
@@ -93,13 +94,13 @@ export abstract class SimpleFindWidget extends Widget {
this._register(this._findInput.onKeyDown((e) => {
if (e.equals(KeyCode.Enter)) {
this.find(false);
this.find(this._invertDefaultDirection);
e.preventDefault();
return;
}
if (e.equals(KeyMod.Shift | KeyCode.Enter)) {
this.find(true);
this.find(!this._invertDefaultDirection);
e.preventDefault();
return;
}
@@ -295,4 +296,4 @@ registerThemingParticipant((theme, collector) => {
if (widgetShadowColor) {
collector.addRule(`.monaco-workbench .simple-find-part { box-shadow: 0 2px 8px ${widgetShadowColor}; }`);
}
});
});

View File

@@ -153,4 +153,26 @@ suite('Replace Pattern test', () => {
let actual = replacePattern.buildReplaceString(matches);
assert.equal(actual, 'a{}');
});
test('preserve case', () => {
let replacePattern = parseReplaceString('Def');
let actual = replacePattern.buildReplaceString(['abc'], true);
assert.equal(actual, 'def');
actual = replacePattern.buildReplaceString(['Abc'], true);
assert.equal(actual, 'Def');
actual = replacePattern.buildReplaceString(['ABC'], true);
assert.equal(actual, 'DEF');
actual = replacePattern.buildReplaceString(['abc', 'Abc'], true);
assert.equal(actual, 'def');
actual = replacePattern.buildReplaceString(['Abc', 'abc'], true);
assert.equal(actual, 'Def');
actual = replacePattern.buildReplaceString(['ABC', 'abc'], true);
assert.equal(actual, 'DEF');
actual = replacePattern.buildReplaceString(['AbC'], true);
assert.equal(actual, 'Def');
actual = replacePattern.buildReplaceString(['aBC'], true);
assert.equal(actual, 'Def');
});
});