Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/vs/workbench/contrib/search/browser/media/searchview.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
12 changes: 11 additions & 1 deletion src/vs/workbench/contrib/search/browser/searchView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -372,7 +373,8 @@ export class SearchView extends ViewletPanel {
isCaseSensitive: isCaseSensitive,
isWholeWords: isWholeWords,
searchHistory: searchHistory,
replaceHistory: replaceHistory
replaceHistory: replaceHistory,
preserveCase: preserveCase
}));

if (showReplace) {
Expand All @@ -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());
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down
29 changes: 28 additions & 1 deletion src/vs/workbench/contrib/search/browser/searchWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -42,6 +43,7 @@ export interface ISearchWidgetOptions {
isWholeWords?: boolean;
searchHistory?: string[];
replaceHistory?: string[];
preserveCase?: boolean;
}

class ReplaceAllAction extends Action {
Expand Down Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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 || '';
Expand Down
15 changes: 12 additions & 3 deletions src/vs/workbench/contrib/search/common/searchModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
22 changes: 20 additions & 2 deletions src/vs/workbench/services/search/common/replace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,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) {
Expand All @@ -65,12 +65,30 @@ 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 && matches && (matches[0] !== '')) {
if (matches[0].toUpperCase() === matches[0]) {
return this._replacePattern.toUpperCase();
} else if (matches[0].toLowerCase() === matches[0]) {
return this._replacePattern.toLowerCase();
} else if (strings.containsUppercaseCharacter(matches[0][0])) {
return this._replacePattern[0].toUpperCase() + this._replacePattern.substr(1);
} else {
// we don't understand its pattern yet.
return this._replacePattern;
}
} else {
return this._replacePattern;
}
}

/**
* \n => LF
* \t => TAB
Expand Down