in patched-vscode/src/vs/editor/contrib/find/browser/findWidget.ts [940:1283]
private _buildDomNode(): void {
const flexibleHeight = true;
const flexibleWidth = true;
// Find input
this._findInput = this._register(new ContextScopedFindInput(null, this._contextViewProvider, {
width: FIND_INPUT_AREA_WIDTH,
label: NLS_FIND_INPUT_LABEL,
placeholder: NLS_FIND_INPUT_PLACEHOLDER,
appendCaseSensitiveLabel: this._keybindingLabelFor(FIND_IDS.ToggleCaseSensitiveCommand),
appendWholeWordsLabel: this._keybindingLabelFor(FIND_IDS.ToggleWholeWordCommand),
appendRegexLabel: this._keybindingLabelFor(FIND_IDS.ToggleRegexCommand),
validation: (value: string): InputBoxMessage | null => {
if (value.length === 0 || !this._findInput.getRegex()) {
return null;
}
try {
// use `g` and `u` which are also used by the TextModel search
new RegExp(value, 'gu');
return null;
} catch (e) {
return { content: e.message };
}
},
flexibleHeight,
flexibleWidth,
flexibleMaxHeight: 118,
showCommonFindToggles: true,
showHistoryHint: () => showHistoryKeybindingHint(this._keybindingService),
inputBoxStyles: defaultInputBoxStyles,
toggleStyles: defaultToggleStyles
}, this._contextKeyService));
this._findInput.setRegex(!!this._state.isRegex);
this._findInput.setCaseSensitive(!!this._state.matchCase);
this._findInput.setWholeWords(!!this._state.wholeWord);
this._register(this._findInput.onKeyDown((e) => this._onFindInputKeyDown(e)));
this._register(this._findInput.inputBox.onDidChange(() => {
if (this._ignoreChangeEvent) {
return;
}
this._state.change({ searchString: this._findInput.getValue() }, true);
}));
this._register(this._findInput.onDidOptionChange(() => {
this._state.change({
isRegex: this._findInput.getRegex(),
wholeWord: this._findInput.getWholeWords(),
matchCase: this._findInput.getCaseSensitive()
}, true);
}));
this._register(this._findInput.onCaseSensitiveKeyDown((e) => {
if (e.equals(KeyMod.Shift | KeyCode.Tab)) {
if (this._isReplaceVisible) {
this._replaceInput.focus();
e.preventDefault();
}
}
}));
this._register(this._findInput.onRegexKeyDown((e) => {
if (e.equals(KeyCode.Tab)) {
if (this._isReplaceVisible) {
this._replaceInput.focusOnPreserve();
e.preventDefault();
}
}
}));
this._register(this._findInput.inputBox.onDidHeightChange((e) => {
if (this._tryUpdateHeight()) {
this._showViewZone();
}
}));
if (platform.isLinux) {
this._register(this._findInput.onMouseDown((e) => this._onFindInputMouseDown(e)));
}
this._matchesCount = document.createElement('div');
this._matchesCount.className = 'matchesCount';
this._updateMatchesCount();
// Create a scoped hover delegate for all find related buttons
const hoverDelegate = this._register(createInstantHoverDelegate());
// Previous button
this._prevBtn = this._register(new SimpleButton({
label: NLS_PREVIOUS_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.PreviousMatchFindAction),
icon: findPreviousMatchIcon,
hoverDelegate,
onTrigger: () => {
assertIsDefined(this._codeEditor.getAction(FIND_IDS.PreviousMatchFindAction)).run().then(undefined, onUnexpectedError);
}
}, this._hoverService));
// Next button
this._nextBtn = this._register(new SimpleButton({
label: NLS_NEXT_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.NextMatchFindAction),
icon: findNextMatchIcon,
hoverDelegate,
onTrigger: () => {
assertIsDefined(this._codeEditor.getAction(FIND_IDS.NextMatchFindAction)).run().then(undefined, onUnexpectedError);
}
}, this._hoverService));
const findPart = document.createElement('div');
findPart.className = 'find-part';
findPart.appendChild(this._findInput.domNode);
const actionsContainer = document.createElement('div');
actionsContainer.className = 'find-actions';
findPart.appendChild(actionsContainer);
actionsContainer.appendChild(this._matchesCount);
actionsContainer.appendChild(this._prevBtn.domNode);
actionsContainer.appendChild(this._nextBtn.domNode);
// Toggle selection button
this._toggleSelectionFind = this._register(new Toggle({
icon: findSelectionIcon,
title: NLS_TOGGLE_SELECTION_FIND_TITLE + this._keybindingLabelFor(FIND_IDS.ToggleSearchScopeCommand),
isChecked: false,
hoverDelegate: hoverDelegate,
inputActiveOptionBackground: asCssVariable(inputActiveOptionBackground),
inputActiveOptionBorder: asCssVariable(inputActiveOptionBorder),
inputActiveOptionForeground: asCssVariable(inputActiveOptionForeground),
}));
this._register(this._toggleSelectionFind.onChange(() => {
if (this._toggleSelectionFind.checked) {
if (this._codeEditor.hasModel()) {
let selections = this._codeEditor.getSelections();
selections = selections.map(selection => {
if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) {
selection = selection.setEndPosition(selection.endLineNumber - 1, this._codeEditor.getModel()!.getLineMaxColumn(selection.endLineNumber - 1));
}
if (!selection.isEmpty()) {
return selection;
}
return null;
}).filter((element): element is Selection => !!element);
if (selections.length) {
this._state.change({ searchScope: selections as Range[] }, true);
}
}
} else {
this._state.change({ searchScope: null }, true);
}
}));
actionsContainer.appendChild(this._toggleSelectionFind.domNode);
// Close button
this._closeBtn = this._register(new SimpleButton({
label: NLS_CLOSE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.CloseFindWidgetCommand),
icon: widgetClose,
hoverDelegate,
onTrigger: () => {
this._state.change({ isRevealed: false, searchScope: null }, false);
},
onKeyDown: (e) => {
if (e.equals(KeyCode.Tab)) {
if (this._isReplaceVisible) {
if (this._replaceBtn.isEnabled()) {
this._replaceBtn.focus();
} else {
this._codeEditor.focus();
}
e.preventDefault();
}
}
}
}, this._hoverService));
// Replace input
this._replaceInput = this._register(new ContextScopedReplaceInput(null, undefined, {
label: NLS_REPLACE_INPUT_LABEL,
placeholder: NLS_REPLACE_INPUT_PLACEHOLDER,
appendPreserveCaseLabel: this._keybindingLabelFor(FIND_IDS.TogglePreserveCaseCommand),
history: [],
flexibleHeight,
flexibleWidth,
flexibleMaxHeight: 118,
showHistoryHint: () => showHistoryKeybindingHint(this._keybindingService),
inputBoxStyles: defaultInputBoxStyles,
toggleStyles: defaultToggleStyles
}, this._contextKeyService, true));
this._replaceInput.setPreserveCase(!!this._state.preserveCase);
this._register(this._replaceInput.onKeyDown((e) => this._onReplaceInputKeyDown(e)));
this._register(this._replaceInput.inputBox.onDidChange(() => {
this._state.change({ replaceString: this._replaceInput.inputBox.value }, false);
}));
this._register(this._replaceInput.inputBox.onDidHeightChange((e) => {
if (this._isReplaceVisible && this._tryUpdateHeight()) {
this._showViewZone();
}
}));
this._register(this._replaceInput.onDidOptionChange(() => {
this._state.change({
preserveCase: this._replaceInput.getPreserveCase()
}, true);
}));
this._register(this._replaceInput.onPreserveCaseKeyDown((e) => {
if (e.equals(KeyCode.Tab)) {
if (this._prevBtn.isEnabled()) {
this._prevBtn.focus();
} else if (this._nextBtn.isEnabled()) {
this._nextBtn.focus();
} else if (this._toggleSelectionFind.enabled) {
this._toggleSelectionFind.focus();
} else if (this._closeBtn.isEnabled()) {
this._closeBtn.focus();
}
e.preventDefault();
}
}));
// Create scoped hover delegate for replace actions
const replaceHoverDelegate = this._register(createInstantHoverDelegate());
// Replace one button
this._replaceBtn = this._register(new SimpleButton({
label: NLS_REPLACE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.ReplaceOneAction),
icon: findReplaceIcon,
hoverDelegate: replaceHoverDelegate,
onTrigger: () => {
this._controller.replace();
},
onKeyDown: (e) => {
if (e.equals(KeyMod.Shift | KeyCode.Tab)) {
this._closeBtn.focus();
e.preventDefault();
}
}
}, this._hoverService));
// Replace all button
this._replaceAllBtn = this._register(new SimpleButton({
label: NLS_REPLACE_ALL_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.ReplaceAllAction),
icon: findReplaceAllIcon,
hoverDelegate: replaceHoverDelegate,
onTrigger: () => {
this._controller.replaceAll();
}
}, this._hoverService));
const replacePart = document.createElement('div');
replacePart.className = 'replace-part';
replacePart.appendChild(this._replaceInput.domNode);
const replaceActionsContainer = document.createElement('div');
replaceActionsContainer.className = 'replace-actions';
replacePart.appendChild(replaceActionsContainer);
replaceActionsContainer.appendChild(this._replaceBtn.domNode);
replaceActionsContainer.appendChild(this._replaceAllBtn.domNode);
// Toggle replace button
this._toggleReplaceBtn = this._register(new SimpleButton({
label: NLS_TOGGLE_REPLACE_MODE_BTN_LABEL,
className: 'codicon toggle left',
onTrigger: () => {
this._state.change({ isReplaceRevealed: !this._isReplaceVisible }, false);
if (this._isReplaceVisible) {
this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode);
this._replaceInput.inputBox.layout();
}
this._showViewZone();
}
}, this._hoverService));
this._toggleReplaceBtn.setExpanded(this._isReplaceVisible);
// Widget
this._domNode = document.createElement('div');
this._domNode.className = 'editor-widget find-widget';
this._domNode.setAttribute('aria-hidden', 'true');
this._domNode.ariaLabel = NLS_FIND_DIALOG_LABEL;
this._domNode.role = 'dialog';
// We need to set this explicitly, otherwise on IE11, the width inheritence of flex doesn't work.
this._domNode.style.width = `${FIND_WIDGET_INITIAL_WIDTH}px`;
this._domNode.appendChild(this._toggleReplaceBtn.domNode);
this._domNode.appendChild(findPart);
this._domNode.appendChild(this._closeBtn.domNode);
this._domNode.appendChild(replacePart);
this._resizeSash = this._register(new Sash(this._domNode, this, { orientation: Orientation.VERTICAL, size: 2 }));
this._resized = false;
let originalWidth = FIND_WIDGET_INITIAL_WIDTH;
this._register(this._resizeSash.onDidStart(() => {
originalWidth = dom.getTotalWidth(this._domNode);
}));
this._register(this._resizeSash.onDidChange((evt: ISashEvent) => {
this._resized = true;
const width = originalWidth + evt.startX - evt.currentX;
if (width < FIND_WIDGET_INITIAL_WIDTH) {
// narrow down the find widget should be handled by CSS.
return;
}
const maxWidth = parseFloat(dom.getComputedStyle(this._domNode).maxWidth) || 0;
if (width > maxWidth) {
return;
}
this._domNode.style.width = `${width}px`;
if (this._isReplaceVisible) {
this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode);
}
this._findInput.inputBox.layout();
this._tryUpdateHeight();
}));
this._register(this._resizeSash.onDidReset(() => {
// users double click on the sash
const currentWidth = dom.getTotalWidth(this._domNode);
if (currentWidth < FIND_WIDGET_INITIAL_WIDTH) {
// The editor is narrow and the width of the find widget is controlled fully by CSS.
return;
}
let width = FIND_WIDGET_INITIAL_WIDTH;
if (!this._resized || currentWidth === FIND_WIDGET_INITIAL_WIDTH) {
// 1. never resized before, double click should maximizes it
// 2. users resized it already but its width is the same as default
const layoutInfo = this._codeEditor.getLayoutInfo();
width = layoutInfo.width - 28 - layoutInfo.minimap.minimapWidth - 15;
this._resized = true;
} else {
/**
* no op, the find widget should be shrinked to its default size.
*/
}
this._domNode.style.width = `${width}px`;
if (this._isReplaceVisible) {
this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode);
}
this._findInput.inputBox.layout();
}));
}