未验证 提交 5f8d2ef5 编写于 作者: P Peng Lyu 提交者: GitHub

Merge pull request #60311 from JulienMalige/preserve-case

#42382 Preserve case while search and replace
......@@ -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);
......
......@@ -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);
......
......@@ -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);
}
......
......@@ -79,6 +79,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 +233,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 {
......
......@@ -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;
......@@ -913,6 +916,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 +953,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);
......
......@@ -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 = '';
......
......@@ -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');
});
});
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册