未验证 提交 8b15ad78 编写于 作者: R Rob Lourens 提交者: GitHub

Merge pull request #79111 from skprabhanjan/fix-78397

Fix-78397 Implement case preservation in search as well
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as strings from './strings';
export function buildReplaceStringWithCasePreserved(matches: string[] | null, pattern: string): string {
if (matches && (matches[0] !== '')) {
if (matches[0].toUpperCase() === matches[0]) {
return pattern.toUpperCase();
} else if (matches[0].toLowerCase() === matches[0]) {
return pattern.toLowerCase();
} else if (strings.containsUppercaseCharacter(matches[0][0])) {
return pattern[0].toUpperCase() + pattern.substr(1);
} else {
// we don't understand its pattern yet.
return pattern;
}
} else {
return pattern;
}
}
......@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { CharCode } from 'vs/base/common/charCode';
import { containsUppercaseCharacter } from 'vs/base/common/strings';
import { buildReplaceStringWithCasePreserved } from 'vs/base/common/search';
const enum ReplacePatternKind {
StaticValue = 0,
......@@ -51,17 +51,8 @@ export class ReplacePattern {
public buildReplaceString(matches: string[] | null, preserveCase?: boolean): string {
if (this._state.kind === ReplacePatternKind.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;
}
if (preserveCase) {
return buildReplaceStringWithCasePreserved(matches, this._state.staticValue);
} else {
return this._state.staticValue;
}
......
......@@ -5,6 +5,7 @@
import * as assert from 'assert';
import { ReplacePattern, ReplacePiece, parseReplaceString } from 'vs/editor/contrib/find/replacePattern';
import { buildReplaceStringWithCasePreserved } from 'vs/base/common/search';
suite('Replace Pattern test', () => {
......@@ -154,6 +155,29 @@ suite('Replace Pattern test', () => {
assert.equal(actual, 'a{}');
});
test('buildReplaceStringWithCasePreserved test', () => {
let replacePattern = 'Def';
let actual: string | string[] = 'abc';
assert.equal(buildReplaceStringWithCasePreserved([actual], replacePattern), 'def');
actual = 'Abc';
assert.equal(buildReplaceStringWithCasePreserved([actual], replacePattern), 'Def');
actual = 'ABC';
assert.equal(buildReplaceStringWithCasePreserved([actual], replacePattern), 'DEF');
actual = ['abc', 'Abc'];
assert.equal(buildReplaceStringWithCasePreserved(actual, replacePattern), 'def');
actual = ['Abc', 'abc'];
assert.equal(buildReplaceStringWithCasePreserved(actual, replacePattern), 'Def');
actual = ['ABC', 'abc'];
assert.equal(buildReplaceStringWithCasePreserved(actual, replacePattern), 'DEF');
actual = ['AbC'];
assert.equal(buildReplaceStringWithCasePreserved(actual, replacePattern), 'Def');
actual = ['aBC'];
assert.equal(buildReplaceStringWithCasePreserved(actual, replacePattern), 'Def');
});
test('preserve case', () => {
let replacePattern = parseReplaceString('Def');
let actual = replacePattern.buildReplaceString(['abc'], true);
......
......@@ -60,6 +60,20 @@
display: inline-flex;
}
.search-view .search-widget .replace-input {
position: relative;
display: flex;
display: -webkit-flex;
vertical-align: middle;
width: auto !important;
}
.search-view .search-widget .replace-input > .controls {
position: absolute;
top: 3px;
right: 2px;
}
.search-view .search-widget .replace-container.disabled {
display: none;
}
......
......@@ -364,6 +364,7 @@ export class SearchView extends ViewletPanel {
const searchHistory = history.search || this.viewletState['query.searchHistory'] || [];
const replaceHistory = history.replace || this.viewletState['query.replaceHistory'] || [];
const showReplace = typeof this.viewletState['view.showReplace'] === 'boolean' ? this.viewletState['view.showReplace'] : true;
const preserveCase = this.viewletState['query.preserveCase'] === true;
this.searchWidget = this._register(this.instantiationService.createInstance(SearchWidget, container, <ISearchWidgetOptions>{
value: contentPattern,
......@@ -372,7 +373,8 @@ export class SearchView extends ViewletPanel {
isCaseSensitive: isCaseSensitive,
isWholeWords: isWholeWords,
searchHistory: searchHistory,
replaceHistory: replaceHistory
replaceHistory: replaceHistory,
preserveCase: preserveCase
}));
if (showReplace) {
......@@ -390,6 +392,12 @@ export class SearchView extends ViewletPanel {
this.viewModel.replaceActive = state;
this.refreshTree();
}));
this._register(this.searchWidget.onPreserveCaseChange((state) => {
this.viewModel.preserveCase = state;
this.refreshTree();
}));
this._register(this.searchWidget.onReplaceValueChanged((value) => {
this.viewModel.replaceString = this.searchWidget.getReplaceValue();
this.delayedRefresh.trigger(() => this.refreshTree());
......@@ -1641,6 +1649,7 @@ export class SearchView extends ViewletPanel {
const patternExcludes = this.inputPatternExcludes.getValue().trim();
const patternIncludes = this.inputPatternIncludes.getValue().trim();
const useExcludesAndIgnoreFiles = this.inputPatternExcludes.useExcludesAndIgnoreFiles();
const preserveCase = this.viewModel.preserveCase;
this.viewletState['query.contentPattern'] = contentPattern;
this.viewletState['query.regex'] = isRegex;
......@@ -1649,6 +1658,7 @@ export class SearchView extends ViewletPanel {
this.viewletState['query.folderExclusions'] = patternExcludes;
this.viewletState['query.folderIncludes'] = patternIncludes;
this.viewletState['query.useExcludesAndIgnoreFiles'] = useExcludesAndIgnoreFiles;
this.viewletState['query.preserveCase'] = preserveCase;
const isReplaceShown = this.searchAndReplaceWidget.isReplaceShown();
this.viewletState['view.showReplace'] = isReplaceShown;
......
......@@ -33,6 +33,7 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox';
export interface ISearchWidgetOptions {
value?: string;
......@@ -42,6 +43,7 @@ export interface ISearchWidgetOptions {
isWholeWords?: boolean;
searchHistory?: string[];
replaceHistory?: string[];
preserveCase?: boolean;
}
class ReplaceAllAction extends Action {
......@@ -97,6 +99,7 @@ export class SearchWidget extends Widget {
replaceInputFocusTracker: dom.IFocusTracker;
private replaceInputBoxFocused: IContextKey<boolean>;
private _replaceHistoryDelayer: Delayer<void>;
private _preserveCase: Checkbox;
private ignoreGlobalFindBufferOnNextFocus = false;
private previousGlobalFindBufferValue: string;
......@@ -113,6 +116,9 @@ export class SearchWidget extends Widget {
private _onReplaceStateChange = this._register(new Emitter<boolean>());
readonly onReplaceStateChange: Event<boolean> = this._onReplaceStateChange.event;
private _onPreserveCaseChange = this._register(new Emitter<boolean>());
readonly onPreserveCaseChange: Event<boolean> = this._onPreserveCaseChange.event;
private _onReplaceValueChanged = this._register(new Emitter<void>());
readonly onReplaceValueChanged: Event<void> = this._onReplaceValueChanged.event;
......@@ -333,13 +339,34 @@ export class SearchWidget extends Widget {
private renderReplaceInput(parent: HTMLElement, options: ISearchWidgetOptions): void {
this.replaceContainer = dom.append(parent, dom.$('.replace-container.disabled'));
const replaceBox = dom.append(this.replaceContainer, dom.$('.input-box'));
const replaceBox = dom.append(this.replaceContainer, dom.$('.replace-input'));
this.replaceInput = this._register(new ContextScopedHistoryInputBox(replaceBox, this.contextViewService, {
ariaLabel: nls.localize('label.Replace', 'Replace: Type replace term and press Enter to preview or Escape to cancel'),
placeholder: nls.localize('search.replace.placeHolder', "Replace"),
history: options.replaceHistory || [],
flexibleHeight: true
}, this.contextKeyService));
this._preserveCase = this._register(new Checkbox({
actionClassName: 'monaco-preserve-case',
title: nls.localize('label.preserveCaseCheckbox', "Preserve Case"),
isChecked: !!options.preserveCase,
}));
this._register(this._preserveCase.onChange(viaKeyboard => {
if (!viaKeyboard) {
this.replaceInput.focus();
this._onPreserveCaseChange.fire(this._preserveCase.checked);
}
}));
let controls = document.createElement('div');
controls.className = 'controls';
controls.style.display = 'block';
controls.appendChild(this._preserveCase.domNode);
replaceBox.appendChild(controls);
this._register(attachInputBoxStyler(this.replaceInput, this.themeService));
this.onkeydown(this.replaceInput.inputElement, (keyboardEvent) => this.onReplaceInputKeyDown(keyboardEvent));
this.replaceInput.value = options.replaceValue || '';
......
......@@ -103,17 +103,17 @@ export class Match {
}
const fullMatchText = this.fullMatchText();
let replaceString = searchModel.replacePattern.getReplaceString(fullMatchText);
let replaceString = searchModel.replacePattern.getReplaceString(fullMatchText, searchModel.preserveCase);
// If match string is not matching then regex pattern has a lookahead expression
if (replaceString === null) {
const fullMatchTextWithTrailingContent = this.fullMatchText(true);
replaceString = searchModel.replacePattern.getReplaceString(fullMatchTextWithTrailingContent);
replaceString = searchModel.replacePattern.getReplaceString(fullMatchTextWithTrailingContent, searchModel.preserveCase);
// Search/find normalize line endings - check whether \r prevents regex from matching
if (replaceString === null) {
const fullMatchTextWithoutCR = fullMatchTextWithTrailingContent.replace(/\r\n/g, '\n');
replaceString = searchModel.replacePattern.getReplaceString(fullMatchTextWithoutCR);
replaceString = searchModel.replacePattern.getReplaceString(fullMatchTextWithoutCR, searchModel.preserveCase);
}
}
......@@ -895,6 +895,7 @@ export class SearchModel extends Disposable {
private _replaceActive: boolean = false;
private _replaceString: string | null = null;
private _replacePattern: ReplacePattern | null = null;
private _preserveCase: boolean = false;
private readonly _onReplaceTermChanged: Emitter<void> = this._register(new Emitter<void>());
readonly onReplaceTermChanged: Event<void> = this._onReplaceTermChanged.event;
......@@ -926,6 +927,14 @@ export class SearchModel extends Disposable {
return this._replaceString || '';
}
set preserveCase(value: boolean) {
this._preserveCase = value;
}
get preserveCase(): boolean {
return this._preserveCase;
}
set replaceString(replaceString: string) {
this._replaceString = replaceString;
if (this._searchQuery) {
......
......@@ -6,6 +6,7 @@
import * as strings from 'vs/base/common/strings';
import { IPatternInfo } from 'vs/workbench/services/search/common/search';
import { CharCode } from 'vs/base/common/charCode';
import { buildReplaceStringWithCasePreserved } from 'vs/base/common/search';
export class ReplacePattern {
......@@ -54,7 +55,7 @@ export class ReplacePattern {
* Returns the replace string for the first match in the given text.
* If text has no matches then returns null.
*/
getReplaceString(text: string): string | null {
getReplaceString(text: string, preserveCase?: boolean): string | null {
this._regExp.lastIndex = 0;
let match = this._regExp.exec(text);
if (match) {
......@@ -65,12 +66,20 @@ export class ReplacePattern {
let replaceString = text.replace(this._regExp, this.pattern);
return replaceString.substr(match.index, match[0].length - (text.length - replaceString.length));
}
return this.pattern;
return this.buildReplaceString(match, preserveCase);
}
return null;
}
public buildReplaceString(matches: string[] | null, preserveCase?: boolean): string {
if (preserveCase) {
return buildReplaceStringWithCasePreserved(matches, this._replacePattern);
} else {
return this._replacePattern;
}
}
/**
* \n => LF
* \t => TAB
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册