Path: blob/main/src/vs/editor/contrib/find/browser/findWidget.ts
5272 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as dom from '../../../../base/browser/dom.js';6import { IKeyboardEvent } from '../../../../base/browser/keyboardEvent.js';7import { IMouseEvent } from '../../../../base/browser/mouseEvent.js';8import { alert as alertFn } from '../../../../base/browser/ui/aria/aria.js';9import { Toggle } from '../../../../base/browser/ui/toggle/toggle.js';10import { IContextViewProvider } from '../../../../base/browser/ui/contextview/contextview.js';11import { FindInput } from '../../../../base/browser/ui/findinput/findInput.js';12import { ReplaceInput } from '../../../../base/browser/ui/findinput/replaceInput.js';13import { IMessage as InputBoxMessage } from '../../../../base/browser/ui/inputbox/inputBox.js';14import { ISashEvent, IVerticalSashLayoutProvider, Orientation, Sash } from '../../../../base/browser/ui/sash/sash.js';15import { Widget } from '../../../../base/browser/ui/widget.js';16import { Delayer, disposableTimeout } from '../../../../base/common/async.js';17import { Codicon } from '../../../../base/common/codicons.js';18import { onUnexpectedError } from '../../../../base/common/errors.js';19import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';20import { toDisposable, IDisposable } from '../../../../base/common/lifecycle.js';21import * as platform from '../../../../base/common/platform.js';22import * as strings from '../../../../base/common/strings.js';23import './findWidget.css';24import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, IViewZone, OverlayWidgetPositionPreference } from '../../../browser/editorBrowser.js';25import { ConfigurationChangedEvent, EditorOption } from '../../../common/config/editorOptions.js';26import { Range } from '../../../common/core/range.js';27import { CONTEXT_FIND_INPUT_FOCUSED, CONTEXT_FIND_WIDGET_FOCUSED, CONTEXT_REPLACE_INPUT_FOCUSED, FIND_IDS, MATCHES_LIMIT } from './findModel.js';28import { FindReplaceState, FindReplaceStateChangedEvent } from './findState.js';29import * as nls from '../../../../nls.js';30import { AccessibilitySupport, IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js';31import { ContextScopedFindInput, ContextScopedReplaceInput } from '../../../../platform/history/browser/contextScopedHistoryWidget.js';32import { showHistoryKeybindingHint } from '../../../../platform/history/browser/historyWidgetKeybindingHint.js';33import { IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';34import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';35import { asCssVariable, contrastBorder, editorFindMatchForeground, editorFindMatchHighlightBorder, editorFindMatchHighlightForeground, editorFindRangeHighlightBorder, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground } from '../../../../platform/theme/common/colorRegistry.js';36import { registerIcon, widgetClose } from '../../../../platform/theme/common/iconRegistry.js';37import { registerThemingParticipant } from '../../../../platform/theme/common/themeService.js';38import { ThemeIcon } from '../../../../base/common/themables.js';39import { isHighContrast } from '../../../../platform/theme/common/theme.js';40import { assertReturnsDefined } from '../../../../base/common/types.js';41import { defaultInputBoxStyles, defaultToggleStyles } from '../../../../platform/theme/browser/defaultStyles.js';42import { Selection } from '../../../common/core/selection.js';43import { IHoverService } from '../../../../platform/hover/browser/hover.js';44import { IHistory } from '../../../../base/common/history.js';45import { HoverStyle, type IHoverLifecycleOptions } from '../../../../base/browser/ui/hover/hover.js';46import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';4748const findCollapsedIcon = registerIcon('find-collapsed', Codicon.chevronRight, nls.localize('findCollapsedIcon', 'Icon to indicate that the editor find widget is collapsed.'));49const findExpandedIcon = registerIcon('find-expanded', Codicon.chevronDown, nls.localize('findExpandedIcon', 'Icon to indicate that the editor find widget is expanded.'));5051export const findSelectionIcon = registerIcon('find-selection', Codicon.selection, nls.localize('findSelectionIcon', 'Icon for \'Find in Selection\' in the editor find widget.'));52export const findReplaceIcon = registerIcon('find-replace', Codicon.replace, nls.localize('findReplaceIcon', 'Icon for \'Replace\' in the editor find widget.'));53export const findReplaceAllIcon = registerIcon('find-replace-all', Codicon.replaceAll, nls.localize('findReplaceAllIcon', 'Icon for \'Replace All\' in the editor find widget.'));54export const findPreviousMatchIcon = registerIcon('find-previous-match', Codicon.arrowUp, nls.localize('findPreviousMatchIcon', 'Icon for \'Find Previous\' in the editor find widget.'));55export const findNextMatchIcon = registerIcon('find-next-match', Codicon.arrowDown, nls.localize('findNextMatchIcon', 'Icon for \'Find Next\' in the editor find widget.'));5657export interface IFindController {58replace(): void;59replaceAll(): void;60getGlobalBufferTerm(): Promise<string>;61}6263const NLS_FIND_DIALOG_LABEL = nls.localize('label.findDialog', "Find / Replace");64const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find");65const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find");66const NLS_PREVIOUS_MATCH_BTN_LABEL = nls.localize('label.previousMatchButton', "Previous Match");67const NLS_NEXT_MATCH_BTN_LABEL = nls.localize('label.nextMatchButton', "Next Match");68const NLS_TOGGLE_SELECTION_FIND_TITLE = nls.localize('label.toggleSelectionFind', "Find in Selection");69const NLS_CLOSE_BTN_LABEL = nls.localize('label.closeButton', "Close");70const NLS_REPLACE_INPUT_LABEL = nls.localize('label.replace', "Replace");71const NLS_REPLACE_INPUT_PLACEHOLDER = nls.localize('placeholder.replace', "Replace");72const NLS_REPLACE_BTN_LABEL = nls.localize('label.replaceButton', "Replace");73const NLS_REPLACE_ALL_BTN_LABEL = nls.localize('label.replaceAllButton', "Replace All");74const NLS_TOGGLE_REPLACE_MODE_BTN_LABEL = nls.localize('label.toggleReplaceButton', "Toggle Replace");75const NLS_MATCHES_COUNT_LIMIT_TITLE = nls.localize('title.matchesCountLimit', "Only the first {0} results are highlighted, but all find operations work on the entire text.", MATCHES_LIMIT);76export const NLS_MATCHES_LOCATION = nls.localize('label.matchesLocation', "{0} of {1}");77export const NLS_NO_RESULTS = nls.localize('label.noResults', "No results");7879const FIND_WIDGET_INITIAL_WIDTH = 419;80const PART_WIDTH = 275;81const FIND_INPUT_AREA_WIDTH = PART_WIDTH - 54;8283let MAX_MATCHES_COUNT_WIDTH = 69;84// let FIND_ALL_CONTROLS_WIDTH = 17/** Find Input margin-left */ + (MAX_MATCHES_COUNT_WIDTH + 3 + 1) /** Match Results */ + 23 /** Button */ * 4 + 2/** sash */;8586const FIND_INPUT_AREA_HEIGHT = 33; // The height of Find Widget when Replace Input is not visible.8788const ctrlKeyMod = (platform.isMacintosh ? KeyMod.WinCtrl : KeyMod.CtrlCmd);89export class FindWidgetViewZone implements IViewZone {90public readonly afterLineNumber: number;91public heightInPx: number;92public readonly suppressMouseDown: boolean;93public readonly domNode: HTMLElement;9495constructor(afterLineNumber: number) {96this.afterLineNumber = afterLineNumber;9798this.heightInPx = FIND_INPUT_AREA_HEIGHT;99this.suppressMouseDown = false;100this.domNode = document.createElement('div');101this.domNode.className = 'dock-find-viewzone';102}103}104105function stopPropagationForMultiLineUpwards(event: IKeyboardEvent, value: string, textarea: HTMLTextAreaElement | null) {106const isMultiline = !!value.match(/\n/);107if (textarea && isMultiline && textarea.selectionStart > 0) {108event.stopPropagation();109return;110}111}112113function stopPropagationForMultiLineDownwards(event: IKeyboardEvent, value: string, textarea: HTMLTextAreaElement | null) {114const isMultiline = !!value.match(/\n/);115if (textarea && isMultiline && textarea.selectionEnd < textarea.value.length) {116event.stopPropagation();117return;118}119}120121export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashLayoutProvider {122private static readonly ID = 'editor.contrib.findWidget';123private readonly _codeEditor: ICodeEditor;124private readonly _state: FindReplaceState;125private readonly _controller: IFindController;126private readonly _contextViewProvider: IContextViewProvider;127private readonly _keybindingService: IKeybindingService;128private readonly _contextKeyService: IContextKeyService;129130private _domNode!: HTMLElement;131private _cachedHeight: number | null = null;132private _findInput!: FindInput;133private _replaceInput!: ReplaceInput;134135private _toggleReplaceBtn!: SimpleButton;136private _matchesCount!: HTMLElement;137private _prevBtn!: SimpleButton;138private _nextBtn!: SimpleButton;139private _toggleSelectionFind!: Toggle;140private _closeBtn!: SimpleButton;141private _replaceBtn!: SimpleButton;142private _replaceAllBtn!: SimpleButton;143144private _isVisible: boolean;145private _isReplaceVisible: boolean;146private _ignoreChangeEvent: boolean;147private _accessibilityHelpHintAnnounced: boolean;148private _labelResetTimeout: IDisposable | undefined;149private _lastFocusedInputWasReplace: boolean = false;150151private readonly _findFocusTracker: dom.IFocusTracker;152private readonly _findInputFocused: IContextKey<boolean>;153private readonly _replaceFocusTracker: dom.IFocusTracker;154private readonly _replaceInputFocused: IContextKey<boolean>;155private _widgetFocusTracker: dom.IFocusTracker | undefined;156private readonly _findWidgetFocused: IContextKey<boolean>;157private _lastFocusedElement: HTMLElement | null = null;158private _viewZone?: FindWidgetViewZone;159private _viewZoneId?: string;160161private _resizeSash!: Sash;162private _resized!: boolean;163private readonly _updateHistoryDelayer: Delayer<void>;164165constructor(166codeEditor: ICodeEditor,167controller: IFindController,168state: FindReplaceState,169contextViewProvider: IContextViewProvider,170keybindingService: IKeybindingService,171contextKeyService: IContextKeyService,172private readonly _hoverService: IHoverService,173private readonly _findWidgetSearchHistory: IHistory<string> | undefined,174private readonly _replaceWidgetHistory: IHistory<string> | undefined,175private readonly _configurationService: IConfigurationService,176private readonly _accessibilityService: IAccessibilityService,177) {178super();179this._codeEditor = codeEditor;180this._controller = controller;181this._state = state;182this._contextViewProvider = contextViewProvider;183this._keybindingService = keybindingService;184this._contextKeyService = contextKeyService;185186this._isVisible = false;187this._isReplaceVisible = false;188this._ignoreChangeEvent = false;189this._accessibilityHelpHintAnnounced = false;190191this._updateHistoryDelayer = new Delayer<void>(500);192this._register(toDisposable(() => this._updateHistoryDelayer.cancel()));193this._register(this._state.onFindReplaceStateChange((e) => this._onStateChanged(e)));194this._buildDomNode();195this._updateButtons();196this._tryUpdateWidgetWidth();197this._findInput.inputBox.layout();198199this._register(this._codeEditor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => {200if (e.hasChanged(EditorOption.readOnly)) {201if (this._codeEditor.getOption(EditorOption.readOnly)) {202// Hide replace part if editor becomes read only203this._state.change({ isReplaceRevealed: false }, false);204}205this._updateButtons();206}207if (e.hasChanged(EditorOption.layoutInfo)) {208this._tryUpdateWidgetWidth();209}210211if (e.hasChanged(EditorOption.accessibilitySupport)) {212this.updateAccessibilitySupport();213}214215if (e.hasChanged(EditorOption.find)) {216const supportLoop = this._codeEditor.getOption(EditorOption.find).loop;217this._state.change({ loop: supportLoop }, false);218const addExtraSpaceOnTop = this._codeEditor.getOption(EditorOption.find).addExtraSpaceOnTop;219if (addExtraSpaceOnTop && !this._viewZone) {220this._viewZone = new FindWidgetViewZone(0);221this._showViewZone();222}223if (!addExtraSpaceOnTop && this._viewZone) {224this._removeViewZone();225}226}227}));228this.updateAccessibilitySupport();229this._register(this._codeEditor.onDidChangeCursorSelection(() => {230if (this._isVisible) {231this._updateToggleSelectionFindButton();232}233}));234this._register(this._codeEditor.onDidFocusEditorWidget(async () => {235if (this._isVisible) {236const globalBufferTerm = await this._controller.getGlobalBufferTerm();237if (globalBufferTerm && globalBufferTerm !== this._state.searchString) {238this._state.change({ searchString: globalBufferTerm }, false);239this._findInput.select();240}241}242}));243this._findInputFocused = CONTEXT_FIND_INPUT_FOCUSED.bindTo(contextKeyService);244this._findFocusTracker = this._register(dom.trackFocus(this._findInput.inputBox.inputElement));245this._register(this._findFocusTracker.onDidFocus(() => {246this._findInputFocused.set(true);247this._lastFocusedInputWasReplace = false;248this._updateSearchScope();249}));250this._register(this._findFocusTracker.onDidBlur(() => {251this._findInputFocused.set(false);252}));253254this._replaceInputFocused = CONTEXT_REPLACE_INPUT_FOCUSED.bindTo(contextKeyService);255this._replaceFocusTracker = this._register(dom.trackFocus(this._replaceInput.inputBox.inputElement));256this._register(this._replaceFocusTracker.onDidFocus(() => {257this._replaceInputFocused.set(true);258this._lastFocusedInputWasReplace = true;259this._updateSearchScope();260}));261this._register(this._replaceFocusTracker.onDidBlur(() => {262this._replaceInputFocused.set(false);263}));264265// Track focus on the entire Find widget for accessibility help266this._findWidgetFocused = CONTEXT_FIND_WIDGET_FOCUSED.bindTo(contextKeyService);267this._widgetFocusTracker = this._register(dom.trackFocus(this._domNode));268this._register(this._widgetFocusTracker.onDidFocus(() => {269this._findWidgetFocused.set(true);270}));271this._register(this._widgetFocusTracker.onDidBlur(() => {272this._findWidgetFocused.set(false);273}));274275// Track which element was last focused within the widget using focusin (which bubbles)276this._register(dom.addDisposableListener(this._domNode, 'focusin', (e: FocusEvent) => {277if (dom.isHTMLElement(e.target)) {278this._lastFocusedElement = e.target;279}280}));281282this._codeEditor.addOverlayWidget(this);283if (this._codeEditor.getOption(EditorOption.find).addExtraSpaceOnTop) {284this._viewZone = new FindWidgetViewZone(0); // Put it before the first line then users can scroll beyond the first line.285}286287this._register(this._codeEditor.onDidChangeModel(() => {288if (!this._isVisible) {289return;290}291this._viewZoneId = undefined;292}));293294295this._register(this._codeEditor.onDidScrollChange((e) => {296if (e.scrollTopChanged) {297this._layoutViewZone();298return;299}300301// for other scroll changes, layout the viewzone in next tick to avoid ruining current rendering.302setTimeout(() => {303this._layoutViewZone();304}, 0);305}));306}307308// ----- IOverlayWidget API309310public getId(): string {311return FindWidget.ID;312}313314public getDomNode(): HTMLElement {315return this._domNode;316}317318/**319* Returns whether the Replace input was the last focused input in the find widget.320* This persists even after focus leaves the widget, allowing external code to know321* which input to restore focus to.322*/323public get lastFocusedInputWasReplace(): boolean {324return this._lastFocusedInputWasReplace;325}326327/**328* Returns the last focused element within the Find widget.329* This is useful for restoring focus to the exact element after330* accessibility help or other overlays are dismissed.331*/332public get lastFocusedElement(): HTMLElement | null {333return this._lastFocusedElement;334}335336/**337* Focuses the last focused element in the Find widget.338* Falls back to the Find or Replace input based on lastFocusedInputWasReplace.339*/340public focusLastElement(): void {341if (!this._isVisible) {342return;343}344if (this._lastFocusedElement && this._domNode.contains(this._lastFocusedElement) && dom.getWindow(this._lastFocusedElement).document.body.contains(this._lastFocusedElement)) {345this._lastFocusedElement.focus();346} else if (this._lastFocusedInputWasReplace) {347this.focusReplaceInput();348} else {349this.focusFindInput();350}351}352353public getPosition(): IOverlayWidgetPosition | null {354if (this._isVisible) {355return {356preference: OverlayWidgetPositionPreference.TOP_RIGHT_CORNER357};358}359return null;360}361362// ----- React to state changes363364private _onStateChanged(e: FindReplaceStateChangedEvent): void {365if (e.searchString) {366try {367this._ignoreChangeEvent = true;368this._findInput.setValue(this._state.searchString);369} finally {370this._ignoreChangeEvent = false;371}372this._updateButtons();373}374if (e.replaceString) {375this._replaceInput.inputBox.value = this._state.replaceString;376}377if (e.isRevealed) {378if (this._state.isRevealed) {379this._reveal();380} else {381this._hide(true);382}383}384if (e.isReplaceRevealed) {385if (this._state.isReplaceRevealed) {386if (!this._codeEditor.getOption(EditorOption.readOnly) && !this._isReplaceVisible) {387this._isReplaceVisible = true;388this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode);389this._updateButtons();390this._replaceInput.inputBox.layout();391}392} else {393if (this._isReplaceVisible) {394this._isReplaceVisible = false;395this._updateButtons();396}397}398}399if ((e.isRevealed || e.isReplaceRevealed) && (this._state.isRevealed || this._state.isReplaceRevealed)) {400if (this._tryUpdateHeight()) {401this._showViewZone();402}403}404405if (e.isRegex) {406this._findInput.setRegex(this._state.isRegex);407}408if (e.wholeWord) {409this._findInput.setWholeWords(this._state.wholeWord);410}411if (e.matchCase) {412this._findInput.setCaseSensitive(this._state.matchCase);413}414if (e.preserveCase) {415this._replaceInput.setPreserveCase(this._state.preserveCase);416}417if (e.searchScope) {418if (this._state.searchScope) {419this._toggleSelectionFind.checked = true;420} else {421this._toggleSelectionFind.checked = false;422}423this._updateToggleSelectionFindButton();424}425if (e.searchString || e.matchesCount || e.matchesPosition) {426const showRedOutline = (this._state.searchString.length > 0 && this._state.matchesCount === 0);427this._domNode.classList.toggle('no-results', showRedOutline);428429this._updateMatchesCount();430this._updateButtons();431}432if (e.searchString || e.currentMatch) {433this._layoutViewZone();434}435if (e.updateHistory) {436this._delayedUpdateHistory();437}438if (e.loop) {439this._updateButtons();440}441}442443private _delayedUpdateHistory() {444this._updateHistoryDelayer.trigger(this._updateHistory.bind(this)).then(undefined, onUnexpectedError);445}446447private _updateHistory() {448if (this._state.searchString) {449this._findInput.inputBox.addToHistory();450}451if (this._state.replaceString) {452this._replaceInput.inputBox.addToHistory();453}454}455456private _updateMatchesCount(): void {457this._matchesCount.style.minWidth = MAX_MATCHES_COUNT_WIDTH + 'px';458if (this._state.matchesCount >= MATCHES_LIMIT) {459this._matchesCount.title = NLS_MATCHES_COUNT_LIMIT_TITLE;460} else {461this._matchesCount.title = '';462}463464// remove previous content465this._matchesCount.firstChild?.remove();466467let label: string;468if (this._state.matchesCount > 0) {469let matchesCount: string = String(this._state.matchesCount);470if (this._state.matchesCount >= MATCHES_LIMIT) {471matchesCount += '+';472}473let matchesPosition: string = String(this._state.matchesPosition);474if (matchesPosition === '0') {475matchesPosition = '?';476}477label = strings.format(NLS_MATCHES_LOCATION, matchesPosition, matchesCount);478} else {479label = NLS_NO_RESULTS;480}481482this._matchesCount.appendChild(document.createTextNode(label));483484alertFn(this._getAriaLabel(label, this._state.currentMatch, this._state.searchString));485MAX_MATCHES_COUNT_WIDTH = Math.max(MAX_MATCHES_COUNT_WIDTH, this._matchesCount.clientWidth);486}487488// ----- actions489490private _getAriaLabel(label: string, currentMatch: Range | null, searchString: string): string {491let result: string;492if (label === NLS_NO_RESULTS) {493result = searchString === ''494? nls.localize('ariaSearchNoResultEmpty', "{0} found", label)495: nls.localize('ariaSearchNoResult', "{0} found for '{1}'", label, searchString);496} else if (currentMatch) {497const ariaLabel = nls.localize('ariaSearchNoResultWithLineNum', "{0} found for '{1}', at {2}", label, searchString, currentMatch.startLineNumber + ':' + currentMatch.startColumn);498const model = this._codeEditor.getModel();499if (model && (currentMatch.startLineNumber <= model.getLineCount()) && (currentMatch.startLineNumber >= 1)) {500const lineContent = model.getLineContent(currentMatch.startLineNumber);501result = `${lineContent}, ${ariaLabel}`;502} else {503result = ariaLabel;504}505} else {506result = nls.localize('ariaSearchNoResultWithLineNumNoCurrentMatch', "{0} found for '{1}'", label, searchString);507}508509return result;510}511512/**513* If 'selection find' is ON we should not disable the button (its function is to cancel 'selection find').514* If 'selection find' is OFF we enable the button only if there is a selection.515*/516private _updateToggleSelectionFindButton(): void {517const selection = this._codeEditor.getSelection();518const isSelection = selection ? (selection.startLineNumber !== selection.endLineNumber || selection.startColumn !== selection.endColumn) : false;519const isChecked = this._toggleSelectionFind.checked;520521if (this._isVisible && (isChecked || isSelection)) {522this._toggleSelectionFind.enable();523} else {524this._toggleSelectionFind.disable();525}526}527528private _updateButtons(): void {529this._findInput.setEnabled(this._isVisible);530this._replaceInput.setEnabled(this._isVisible && this._isReplaceVisible);531this._updateToggleSelectionFindButton();532this._closeBtn.setEnabled(this._isVisible);533534const findInputIsNonEmpty = (this._state.searchString.length > 0);535const matchesCount = this._state.matchesCount ? true : false;536this._prevBtn.setEnabled(this._isVisible && findInputIsNonEmpty && matchesCount && this._state.canNavigateBack());537this._nextBtn.setEnabled(this._isVisible && findInputIsNonEmpty && matchesCount && this._state.canNavigateForward());538this._replaceBtn.setEnabled(this._isVisible && this._isReplaceVisible && findInputIsNonEmpty);539this._replaceAllBtn.setEnabled(this._isVisible && this._isReplaceVisible && findInputIsNonEmpty);540541this._domNode.classList.toggle('replaceToggled', this._isReplaceVisible);542this._toggleReplaceBtn.setExpanded(this._isReplaceVisible);543544const canReplace = !this._codeEditor.getOption(EditorOption.readOnly);545this._toggleReplaceBtn.setEnabled(this._isVisible && canReplace);546}547548private _revealTimeouts: Timeout[] = [];549550private _reveal(): void {551this._revealTimeouts.forEach(e => {552clearTimeout(e);553});554555this._revealTimeouts = [];556557if (!this._isVisible) {558this._isVisible = true;559560const selection = this._codeEditor.getSelection();561562switch (this._codeEditor.getOption(EditorOption.find).autoFindInSelection) {563case 'always':564this._toggleSelectionFind.checked = true;565break;566case 'never':567this._toggleSelectionFind.checked = false;568break;569case 'multiline': {570const isSelectionMultipleLine = !!selection && selection.startLineNumber !== selection.endLineNumber;571this._toggleSelectionFind.checked = isSelectionMultipleLine;572break;573}574default:575break;576}577578this._tryUpdateWidgetWidth();579this._updateButtons();580581this._revealTimeouts.push(setTimeout(() => {582this._domNode.classList.add('visible');583this._domNode.setAttribute('aria-hidden', 'false');584this._updateFindInputAriaLabel();585}, 0));586587// validate query again as it's being dismissed when we hide the find widget.588this._revealTimeouts.push(setTimeout(() => {589this._findInput.validate();590}, 200));591592this._codeEditor.layoutOverlayWidget(this);593594let adjustEditorScrollTop = true;595if (this._codeEditor.getOption(EditorOption.find).seedSearchStringFromSelection && selection) {596const domNode = this._codeEditor.getDomNode();597if (domNode) {598const editorCoords = dom.getDomNodePagePosition(domNode);599const startCoords = this._codeEditor.getScrolledVisiblePosition(selection.getStartPosition());600const startLeft = editorCoords.left + (startCoords ? startCoords.left : 0);601const startTop = startCoords ? startCoords.top : 0;602603if (this._viewZone && startTop < this._viewZone.heightInPx) {604if (selection.endLineNumber > selection.startLineNumber) {605adjustEditorScrollTop = false;606}607608const leftOfFindWidget = dom.getTopLeftOffset(this._domNode).left;609if (startLeft > leftOfFindWidget) {610adjustEditorScrollTop = false;611}612const endCoords = this._codeEditor.getScrolledVisiblePosition(selection.getEndPosition());613const endLeft = editorCoords.left + (endCoords ? endCoords.left : 0);614if (endLeft > leftOfFindWidget) {615adjustEditorScrollTop = false;616}617}618}619}620this._showViewZone(adjustEditorScrollTop);621}622}623624private _hide(focusTheEditor: boolean): void {625this._revealTimeouts.forEach(e => {626clearTimeout(e);627});628629this._revealTimeouts = [];630631if (this._isVisible) {632this._isVisible = false;633this._accessibilityHelpHintAnnounced = false;634635this._updateButtons();636637this._domNode.classList.remove('visible');638this._domNode.setAttribute('aria-hidden', 'true');639this._findInput.clearMessage();640if (focusTheEditor) {641this._codeEditor.focus();642}643this._codeEditor.layoutOverlayWidget(this);644this._removeViewZone();645}646}647648private _layoutViewZone(targetScrollTop?: number) {649const addExtraSpaceOnTop = this._codeEditor.getOption(EditorOption.find).addExtraSpaceOnTop;650651if (!addExtraSpaceOnTop) {652this._removeViewZone();653return;654}655656if (!this._isVisible) {657return;658}659const viewZone = this._viewZone;660if (this._viewZoneId !== undefined || !viewZone) {661return;662}663664this._codeEditor.changeViewZones((accessor) => {665viewZone.heightInPx = this._getHeight();666this._viewZoneId = accessor.addZone(viewZone);667// scroll top adjust to make sure the editor doesn't scroll when adding viewzone at the beginning.668this._codeEditor.setScrollTop(targetScrollTop || this._codeEditor.getScrollTop() + viewZone.heightInPx);669});670}671672private _showViewZone(adjustScroll: boolean = true) {673if (!this._isVisible) {674return;675}676677const addExtraSpaceOnTop = this._codeEditor.getOption(EditorOption.find).addExtraSpaceOnTop;678679if (!addExtraSpaceOnTop) {680return;681}682683if (this._viewZone === undefined) {684this._viewZone = new FindWidgetViewZone(0);685}686687const viewZone = this._viewZone;688689this._codeEditor.changeViewZones((accessor) => {690if (this._viewZoneId !== undefined) {691// the view zone already exists, we need to update the height692const newHeight = this._getHeight();693if (newHeight === viewZone.heightInPx) {694return;695}696697const scrollAdjustment = newHeight - viewZone.heightInPx;698viewZone.heightInPx = newHeight;699accessor.layoutZone(this._viewZoneId);700701if (adjustScroll) {702this._codeEditor.setScrollTop(this._codeEditor.getScrollTop() + scrollAdjustment);703}704705return;706} else {707let scrollAdjustment = this._getHeight();708709// if the editor has top padding, factor that into the zone height710scrollAdjustment -= this._codeEditor.getOption(EditorOption.padding).top;711if (scrollAdjustment <= 0) {712return;713}714715viewZone.heightInPx = scrollAdjustment;716this._viewZoneId = accessor.addZone(viewZone);717718if (adjustScroll) {719this._codeEditor.setScrollTop(this._codeEditor.getScrollTop() + scrollAdjustment);720}721}722});723}724725private _removeViewZone() {726this._codeEditor.changeViewZones((accessor) => {727if (this._viewZoneId !== undefined) {728accessor.removeZone(this._viewZoneId);729this._viewZoneId = undefined;730if (this._viewZone) {731this._codeEditor.setScrollTop(this._codeEditor.getScrollTop() - this._viewZone.heightInPx);732this._viewZone = undefined;733}734}735});736}737738private _tryUpdateWidgetWidth() {739if (!this._isVisible) {740return;741}742if (!this._domNode.isConnected) {743// the widget is not in the DOM744return;745}746747const layoutInfo = this._codeEditor.getLayoutInfo();748const editorContentWidth = layoutInfo.contentWidth;749750if (editorContentWidth <= 0) {751// for example, diff view original editor752this._domNode.classList.add('hiddenEditor');753return;754} else if (this._domNode.classList.contains('hiddenEditor')) {755this._domNode.classList.remove('hiddenEditor');756}757758const editorWidth = layoutInfo.width;759const minimapWidth = layoutInfo.minimap.minimapWidth;760let collapsedFindWidget = false;761let reducedFindWidget = false;762let narrowFindWidget = false;763764if (this._resized) {765const widgetWidth = dom.getTotalWidth(this._domNode);766767if (widgetWidth > FIND_WIDGET_INITIAL_WIDTH) {768// as the widget is resized by users, we may need to change the max width of the widget as the editor width changes.769this._domNode.style.maxWidth = `${editorWidth - 28 - minimapWidth - 15}px`;770this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode);771return;772}773}774775if (FIND_WIDGET_INITIAL_WIDTH + 28 + minimapWidth >= editorWidth) {776reducedFindWidget = true;777}778if (FIND_WIDGET_INITIAL_WIDTH + 28 + minimapWidth - MAX_MATCHES_COUNT_WIDTH >= editorWidth) {779narrowFindWidget = true;780}781if (FIND_WIDGET_INITIAL_WIDTH + 28 + minimapWidth - MAX_MATCHES_COUNT_WIDTH >= editorWidth + 50) {782collapsedFindWidget = true;783}784this._domNode.classList.toggle('collapsed-find-widget', collapsedFindWidget);785this._domNode.classList.toggle('narrow-find-widget', narrowFindWidget);786this._domNode.classList.toggle('reduced-find-widget', reducedFindWidget);787788if (!narrowFindWidget && !collapsedFindWidget) {789// the minimal left offset of findwidget is 15px.790this._domNode.style.maxWidth = `${editorWidth - 28 - minimapWidth - 15}px`;791}792793this._findInput.layout({ collapsedFindWidget, narrowFindWidget, reducedFindWidget });794if (this._resized) {795const findInputWidth = this._findInput.inputBox.element.clientWidth;796if (findInputWidth > 0) {797this._replaceInput.width = findInputWidth;798}799} else if (this._isReplaceVisible) {800this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode);801}802}803804private _getHeight(): number {805let totalheight = 0;806807// find input margin top808totalheight += 4;809810// find input height811totalheight += this._findInput.inputBox.height + 2 /** input box border */;812813if (this._isReplaceVisible) {814// replace input margin815totalheight += 4;816817totalheight += this._replaceInput.inputBox.height + 2 /** input box border */;818}819820// margin bottom821totalheight += 4;822return totalheight;823}824825private _tryUpdateHeight(): boolean {826const totalHeight = this._getHeight();827if (this._cachedHeight !== null && this._cachedHeight === totalHeight) {828return false;829}830831this._cachedHeight = totalHeight;832this._domNode.style.height = `${totalHeight}px`;833834return true;835}836837// ----- Public838839public focusFindInput(): void {840this._findInput.select();841// Edge browser requires focus() in addition to select()842this._findInput.focus();843}844845public focusReplaceInput(): void {846this._replaceInput.select();847// Edge browser requires focus() in addition to select()848this._replaceInput.focus();849}850851public highlightFindOptions(): void {852this._findInput.highlightFindOptions();853}854855private _updateSearchScope(): void {856if (!this._codeEditor.hasModel()) {857return;858}859860if (this._toggleSelectionFind.checked) {861const selections = this._codeEditor.getSelections();862863selections.map(selection => {864if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) {865selection = selection.setEndPosition(866selection.endLineNumber - 1,867this._codeEditor.getModel()!.getLineMaxColumn(selection.endLineNumber - 1)868);869}870const currentMatch = this._state.currentMatch;871if (selection.startLineNumber !== selection.endLineNumber) {872if (!Range.equalsRange(selection, currentMatch)) {873return selection;874}875}876return null;877}).filter(element => !!element);878879if (selections.length) {880this._state.change({ searchScope: selections as Range[] }, true);881}882}883}884885private _onFindInputMouseDown(e: IMouseEvent): void {886// on linux, middle key does pasting.887if (e.middleButton) {888e.stopPropagation();889}890}891892private _onFindInputKeyDown(e: IKeyboardEvent): void {893if (e.equals(ctrlKeyMod | KeyCode.Enter)) {894if (this._keybindingService.dispatchEvent(e, e.target)) {895e.preventDefault();896return;897} else {898this._findInput.inputBox.insertAtCursor('\n');899e.preventDefault();900return;901}902}903904if (e.equals(KeyCode.Tab)) {905if (this._isReplaceVisible) {906this._replaceInput.focus();907} else {908this._findInput.focusOnCaseSensitive();909}910e.preventDefault();911return;912}913914if (e.equals(KeyMod.CtrlCmd | KeyCode.DownArrow)) {915this._codeEditor.focus();916e.preventDefault();917return;918}919920if (e.equals(KeyCode.UpArrow)) {921// eslint-disable-next-line no-restricted-syntax922return stopPropagationForMultiLineUpwards(e, this._findInput.getValue(), this._findInput.domNode.querySelector('textarea'));923}924925if (e.equals(KeyCode.DownArrow)) {926// eslint-disable-next-line no-restricted-syntax927return stopPropagationForMultiLineDownwards(e, this._findInput.getValue(), this._findInput.domNode.querySelector('textarea'));928}929}930931private _onReplaceInputKeyDown(e: IKeyboardEvent): void {932if (e.equals(ctrlKeyMod | KeyCode.Enter)) {933if (this._keybindingService.dispatchEvent(e, e.target)) {934e.preventDefault();935return;936} else {937this._replaceInput.inputBox.insertAtCursor('\n');938e.preventDefault();939return;940}941942}943944if (e.equals(KeyCode.Tab)) {945this._findInput.focusOnCaseSensitive();946e.preventDefault();947return;948}949950if (e.equals(KeyMod.Shift | KeyCode.Tab)) {951this._findInput.focus();952e.preventDefault();953return;954}955956if (e.equals(KeyMod.CtrlCmd | KeyCode.DownArrow)) {957this._codeEditor.focus();958e.preventDefault();959return;960}961962if (e.equals(KeyCode.UpArrow)) {963// eslint-disable-next-line no-restricted-syntax964return stopPropagationForMultiLineUpwards(e, this._replaceInput.inputBox.value, this._replaceInput.inputBox.element.querySelector('textarea'));965}966967if (e.equals(KeyCode.DownArrow)) {968// eslint-disable-next-line no-restricted-syntax969return stopPropagationForMultiLineDownwards(e, this._replaceInput.inputBox.value, this._replaceInput.inputBox.element.querySelector('textarea'));970}971}972973// ----- sash974public getVerticalSashLeft(_sash: Sash): number {975return 0;976}977// ----- initialization978979private _keybindingLabelFor(actionId: string): string {980return this._keybindingService.appendKeybinding('', actionId);981}982983private _buildDomNode(): void {984const flexibleHeight = true;985const flexibleWidth = true;986// Find input987const findSearchHistoryConfig = this._codeEditor.getOption(EditorOption.find).history;988const replaceHistoryConfig = this._codeEditor.getOption(EditorOption.find).replaceHistory;989this._findInput = this._register(new ContextScopedFindInput(null, this._contextViewProvider, {990width: FIND_INPUT_AREA_WIDTH,991label: NLS_FIND_INPUT_LABEL,992placeholder: NLS_FIND_INPUT_PLACEHOLDER,993appendCaseSensitiveLabel: this._keybindingLabelFor(FIND_IDS.ToggleCaseSensitiveCommand),994appendWholeWordsLabel: this._keybindingLabelFor(FIND_IDS.ToggleWholeWordCommand),995appendRegexLabel: this._keybindingLabelFor(FIND_IDS.ToggleRegexCommand),996validation: (value: string): InputBoxMessage | null => {997if (value.length === 0 || !this._findInput.getRegex()) {998return null;999}1000try {1001// use `g` and `u` which are also used by the TextModel search1002new RegExp(value, 'gu');1003return null;1004} catch (e) {1005return { content: e.message };1006}1007},1008flexibleHeight,1009flexibleWidth,1010flexibleMaxHeight: 118,1011showCommonFindToggles: true,1012showHistoryHint: () => showHistoryKeybindingHint(this._keybindingService),1013inputBoxStyles: defaultInputBoxStyles,1014toggleStyles: defaultToggleStyles,1015history: findSearchHistoryConfig === 'workspace' ? this._findWidgetSearchHistory : new Set([]),1016}, this._contextKeyService));1017this._findInput.setRegex(!!this._state.isRegex);1018this._findInput.setCaseSensitive(!!this._state.matchCase);1019this._findInput.setWholeWords(!!this._state.wholeWord);1020this._register(this._findInput.onKeyDown((e) => {1021if (e.equals(KeyCode.Enter) && !this._codeEditor.getOption(EditorOption.find).findOnType) {1022this._state.change({ searchString: this._findInput.getValue() }, true);1023}1024this._onFindInputKeyDown(e);1025}));1026this._register(this._findInput.inputBox.onDidChange(() => {1027if (this._ignoreChangeEvent || !this._codeEditor.getOption(EditorOption.find).findOnType) {1028return;1029}1030this._state.change({ searchString: this._findInput.getValue() }, true);1031}));1032this._register(this._findInput.onDidOptionChange(() => {1033this._state.change({1034isRegex: this._findInput.getRegex(),1035wholeWord: this._findInput.getWholeWords(),1036matchCase: this._findInput.getCaseSensitive()1037}, true);1038}));1039this._register(this._findInput.onCaseSensitiveKeyDown((e) => {1040if (e.equals(KeyMod.Shift | KeyCode.Tab)) {1041if (this._isReplaceVisible) {1042this._replaceInput.focus();1043e.preventDefault();1044}1045}1046}));1047this._register(this._findInput.onRegexKeyDown((e) => {1048if (e.equals(KeyCode.Tab)) {1049if (this._isReplaceVisible) {1050this._replaceInput.focusOnPreserve();1051e.preventDefault();1052}1053}1054}));1055this._register(this._findInput.inputBox.onDidHeightChange((e) => {1056if (this._tryUpdateHeight()) {1057this._showViewZone();1058}1059}));1060if (platform.isLinux) {1061this._register(this._findInput.onMouseDown((e) => this._onFindInputMouseDown(e)));1062}10631064this._matchesCount = document.createElement('div');1065this._matchesCount.className = 'matchesCount';1066this._updateMatchesCount();10671068const hoverLifecycleOptions: IHoverLifecycleOptions = { groupId: 'find-widget' };10691070// Previous button1071this._prevBtn = this._register(new SimpleButton({1072label: NLS_PREVIOUS_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.PreviousMatchFindAction),1073icon: findPreviousMatchIcon,1074hoverLifecycleOptions,1075onTrigger: () => {1076assertReturnsDefined(this._codeEditor.getAction(FIND_IDS.PreviousMatchFindAction)).run().then(undefined, onUnexpectedError);1077}1078}, this._hoverService));10791080// Next button1081this._nextBtn = this._register(new SimpleButton({1082label: NLS_NEXT_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.NextMatchFindAction),1083icon: findNextMatchIcon,1084hoverLifecycleOptions,1085onTrigger: () => {1086assertReturnsDefined(this._codeEditor.getAction(FIND_IDS.NextMatchFindAction)).run().then(undefined, onUnexpectedError);1087}1088}, this._hoverService));10891090const findPart = document.createElement('div');1091findPart.className = 'find-part';1092findPart.appendChild(this._findInput.domNode);1093const actionsContainer = document.createElement('div');1094actionsContainer.className = 'find-actions';1095findPart.appendChild(actionsContainer);1096actionsContainer.appendChild(this._matchesCount);1097actionsContainer.appendChild(this._prevBtn.domNode);1098actionsContainer.appendChild(this._nextBtn.domNode);10991100// Toggle selection button1101this._toggleSelectionFind = this._register(new Toggle({1102icon: findSelectionIcon,1103title: NLS_TOGGLE_SELECTION_FIND_TITLE + this._keybindingLabelFor(FIND_IDS.ToggleSearchScopeCommand),1104isChecked: false,1105hoverLifecycleOptions,1106inputActiveOptionBackground: asCssVariable(inputActiveOptionBackground),1107inputActiveOptionBorder: asCssVariable(inputActiveOptionBorder),1108inputActiveOptionForeground: asCssVariable(inputActiveOptionForeground),1109}));11101111this._register(this._toggleSelectionFind.onChange(() => {1112if (this._toggleSelectionFind.checked) {1113if (this._codeEditor.hasModel()) {1114let selections = this._codeEditor.getSelections();1115selections = selections.map(selection => {1116if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) {1117selection = selection.setEndPosition(selection.endLineNumber - 1, this._codeEditor.getModel()!.getLineMaxColumn(selection.endLineNumber - 1));1118}1119if (!selection.isEmpty()) {1120return selection;1121}1122return null;1123}).filter((element): element is Selection => !!element);11241125if (selections.length) {1126this._state.change({ searchScope: selections as Range[] }, true);1127}1128}1129} else {1130this._state.change({ searchScope: null }, true);1131}1132}));11331134actionsContainer.appendChild(this._toggleSelectionFind.domNode);11351136// Close button1137this._closeBtn = this._register(new SimpleButton({1138label: NLS_CLOSE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.CloseFindWidgetCommand),1139icon: widgetClose,1140hoverLifecycleOptions,1141onTrigger: () => {1142this._state.change({ isRevealed: false, searchScope: null }, false);1143},1144onKeyDown: (e) => {1145if (e.equals(KeyCode.Tab)) {1146if (this._isReplaceVisible) {1147if (this._replaceBtn.isEnabled()) {1148this._replaceBtn.focus();1149} else {1150this._codeEditor.focus();1151}1152e.preventDefault();1153}1154}1155}1156}, this._hoverService));11571158// Replace input1159this._replaceInput = this._register(new ContextScopedReplaceInput(null, undefined, {1160label: NLS_REPLACE_INPUT_LABEL,1161placeholder: NLS_REPLACE_INPUT_PLACEHOLDER,1162appendPreserveCaseLabel: this._keybindingLabelFor(FIND_IDS.TogglePreserveCaseCommand),1163history: replaceHistoryConfig === 'workspace' ? this._replaceWidgetHistory : new Set([]),1164flexibleHeight,1165flexibleWidth,1166flexibleMaxHeight: 118,1167showHistoryHint: () => showHistoryKeybindingHint(this._keybindingService),1168inputBoxStyles: defaultInputBoxStyles,1169toggleStyles: defaultToggleStyles,1170hoverLifecycleOptions,1171}, this._contextKeyService, true));1172this._replaceInput.setPreserveCase(!!this._state.preserveCase);1173this._register(this._replaceInput.onKeyDown((e) => this._onReplaceInputKeyDown(e)));1174this._register(this._replaceInput.inputBox.onDidChange(() => {1175this._state.change({ replaceString: this._replaceInput.inputBox.value }, false);1176}));1177this._register(this._replaceInput.inputBox.onDidHeightChange((e) => {1178if (this._isReplaceVisible && this._tryUpdateHeight()) {1179this._showViewZone();1180}1181}));1182this._register(this._replaceInput.onDidOptionChange(() => {1183this._state.change({1184preserveCase: this._replaceInput.getPreserveCase()1185}, true);1186}));1187this._register(this._replaceInput.onPreserveCaseKeyDown((e) => {1188if (e.equals(KeyCode.Tab)) {1189if (this._prevBtn.isEnabled()) {1190this._prevBtn.focus();1191} else if (this._nextBtn.isEnabled()) {1192this._nextBtn.focus();1193} else if (this._toggleSelectionFind.enabled) {1194this._toggleSelectionFind.focus();1195} else if (this._closeBtn.isEnabled()) {1196this._closeBtn.focus();1197}11981199e.preventDefault();1200}1201}));12021203// Replace one button1204this._replaceBtn = this._register(new SimpleButton({1205label: NLS_REPLACE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.ReplaceOneAction),1206icon: findReplaceIcon,1207hoverLifecycleOptions,1208onTrigger: () => {1209this._controller.replace();1210},1211onKeyDown: (e) => {1212if (e.equals(KeyMod.Shift | KeyCode.Tab)) {1213this._closeBtn.focus();1214e.preventDefault();1215}1216}1217}, this._hoverService));12181219// Replace all button1220this._replaceAllBtn = this._register(new SimpleButton({1221label: NLS_REPLACE_ALL_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.ReplaceAllAction),1222icon: findReplaceAllIcon,1223hoverLifecycleOptions,1224onTrigger: () => {1225this._controller.replaceAll();1226}1227}, this._hoverService));12281229const replacePart = document.createElement('div');1230replacePart.className = 'replace-part';1231replacePart.appendChild(this._replaceInput.domNode);12321233const replaceActionsContainer = document.createElement('div');1234replaceActionsContainer.className = 'replace-actions';1235replacePart.appendChild(replaceActionsContainer);12361237replaceActionsContainer.appendChild(this._replaceBtn.domNode);1238replaceActionsContainer.appendChild(this._replaceAllBtn.domNode);12391240// Toggle replace button1241this._toggleReplaceBtn = this._register(new SimpleButton({1242label: NLS_TOGGLE_REPLACE_MODE_BTN_LABEL,1243className: 'codicon toggle left',1244onTrigger: () => {1245this._state.change({ isReplaceRevealed: !this._isReplaceVisible }, false);1246if (this._isReplaceVisible) {1247this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode);1248this._replaceInput.inputBox.layout();1249}1250this._showViewZone();1251}1252}, this._hoverService));1253this._toggleReplaceBtn.setExpanded(this._isReplaceVisible);12541255// Widget1256this._domNode = document.createElement('div');1257this._domNode.className = 'editor-widget find-widget';1258this._domNode.setAttribute('aria-hidden', 'true');1259this._domNode.ariaLabel = NLS_FIND_DIALOG_LABEL;1260this._domNode.role = 'dialog';12611262// We need to set this explicitly, otherwise on IE11, the width inheritence of flex doesn't work.1263this._domNode.style.width = `${FIND_WIDGET_INITIAL_WIDTH}px`;12641265this._domNode.appendChild(this._toggleReplaceBtn.domNode);1266this._domNode.appendChild(findPart);1267this._domNode.appendChild(this._closeBtn.domNode);1268this._domNode.appendChild(replacePart);12691270this._resizeSash = this._register(new Sash(this._domNode, this, { orientation: Orientation.VERTICAL, size: 2 }));1271this._resized = false;1272let originalWidth = FIND_WIDGET_INITIAL_WIDTH;12731274this._register(this._resizeSash.onDidStart(() => {1275originalWidth = dom.getTotalWidth(this._domNode);1276}));12771278this._register(this._resizeSash.onDidChange((evt: ISashEvent) => {1279this._resized = true;1280const width = originalWidth + evt.startX - evt.currentX;12811282if (width < FIND_WIDGET_INITIAL_WIDTH) {1283// narrow down the find widget should be handled by CSS.1284return;1285}12861287const maxWidth = parseFloat(dom.getComputedStyle(this._domNode).maxWidth) || 0;1288if (width > maxWidth) {1289return;1290}1291this._domNode.style.width = `${width}px`;1292if (this._isReplaceVisible) {1293this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode);1294}12951296this._findInput.inputBox.layout();1297this._tryUpdateHeight();1298}));12991300this._register(this._resizeSash.onDidReset(() => {1301// users double click on the sash1302const currentWidth = dom.getTotalWidth(this._domNode);13031304if (currentWidth < FIND_WIDGET_INITIAL_WIDTH) {1305// The editor is narrow and the width of the find widget is controlled fully by CSS.1306return;1307}13081309let width = FIND_WIDGET_INITIAL_WIDTH;13101311if (!this._resized || currentWidth === FIND_WIDGET_INITIAL_WIDTH) {1312// 1. never resized before, double click should maximizes it1313// 2. users resized it already but its width is the same as default1314const layoutInfo = this._codeEditor.getLayoutInfo();1315width = layoutInfo.width - 28 - layoutInfo.minimap.minimapWidth - 15;1316this._resized = true;1317} else {1318/**1319* no op, the find widget should be shrinked to its default size.1320*/1321}132213231324this._domNode.style.width = `${width}px`;1325if (this._isReplaceVisible) {1326this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode);1327}13281329this._findInput.inputBox.layout();1330}));1331}13321333private updateAccessibilitySupport(): void {1334const value = this._codeEditor.getOption(EditorOption.accessibilitySupport);1335this._findInput.setFocusInputOnOptionClick(value !== AccessibilitySupport.Enabled);1336this._updateFindInputAriaLabel();1337}13381339private _updateFindInputAriaLabel(): void {1340let findLabel = NLS_FIND_INPUT_LABEL;1341let replaceLabel = NLS_REPLACE_INPUT_LABEL;1342if (!this._accessibilityHelpHintAnnounced && this._configurationService.getValue('accessibility.verbosity.find') && this._accessibilityService.isScreenReaderOptimized()) {1343const accessibilityHelpKeybinding = this._keybindingService.lookupKeybinding('editor.action.accessibilityHelp')?.getAriaLabel();1344if (accessibilityHelpKeybinding) {1345const hint = nls.localize('accessibilityHelpHintInLabel', "Press {0} for accessibility help", accessibilityHelpKeybinding);1346findLabel = nls.localize('findInputAriaLabelWithHint', "{0}, {1}", findLabel, hint);1347replaceLabel = nls.localize('replaceInputAriaLabelWithHint', "{0}, {1}", replaceLabel, hint);1348}1349this._accessibilityHelpHintAnnounced = true;1350// Schedule reset to plain labels after initial announcement1351this._labelResetTimeout?.dispose();1352this._labelResetTimeout = disposableTimeout(() => {1353if (this._isVisible) {1354this._findInput.inputBox.setAriaLabel(NLS_FIND_INPUT_LABEL);1355this._replaceInput.inputBox.setAriaLabel(NLS_REPLACE_INPUT_LABEL);1356}1357}, 1000);1358}1359this._findInput.inputBox.setAriaLabel(findLabel);1360this._replaceInput.inputBox.setAriaLabel(replaceLabel);1361}13621363getViewState() {1364let widgetViewZoneVisible = false;1365if (this._viewZone && this._viewZoneId) {1366widgetViewZoneVisible = this._viewZone.heightInPx > this._codeEditor.getScrollTop();1367}13681369return {1370widgetViewZoneVisible,1371scrollTop: this._codeEditor.getScrollTop()1372};1373}13741375setViewState(state?: { widgetViewZoneVisible: boolean; scrollTop: number }) {1376if (!state) {1377return;1378}13791380if (state.widgetViewZoneVisible) {1381// we should add the view zone1382this._layoutViewZone(state.scrollTop);1383}1384}1385}13861387export interface ISimpleButtonOpts {1388readonly label: string;1389readonly className?: string;1390readonly icon?: ThemeIcon;1391readonly hoverLifecycleOptions?: IHoverLifecycleOptions;1392readonly onTrigger: () => void;1393readonly onKeyDown?: (e: IKeyboardEvent) => void;1394}13951396export class SimpleButton extends Widget {13971398private readonly _opts: ISimpleButtonOpts;1399private readonly _domNode: HTMLElement;14001401constructor(1402opts: ISimpleButtonOpts,1403hoverService: IHoverService1404) {1405super();1406this._opts = opts;14071408let className = 'button';1409if (this._opts.className) {1410className = className + ' ' + this._opts.className;1411}1412if (this._opts.icon) {1413className = className + ' ' + ThemeIcon.asClassName(this._opts.icon);1414}14151416this._domNode = document.createElement('div');1417this._domNode.tabIndex = 0;1418this._domNode.className = className;1419this._domNode.setAttribute('role', 'button');1420this._domNode.setAttribute('aria-label', this._opts.label);1421this._register(hoverService.setupDelayedHover(this._domNode, {1422content: this._opts.label,1423style: HoverStyle.Pointer,1424}, opts.hoverLifecycleOptions));14251426this.onclick(this._domNode, (e) => {1427this._opts.onTrigger();1428e.preventDefault();1429});14301431this.onkeydown(this._domNode, (e) => {1432if (e.equals(KeyCode.Space) || e.equals(KeyCode.Enter)) {1433this._opts.onTrigger();1434e.preventDefault();1435return;1436}1437this._opts.onKeyDown?.(e);1438});1439}14401441public get domNode(): HTMLElement {1442return this._domNode;1443}14441445public isEnabled(): boolean {1446return (this._domNode.tabIndex >= 0);1447}14481449public focus(): void {1450this._domNode.focus();1451}14521453public setEnabled(enabled: boolean): void {1454this._domNode.classList.toggle('disabled', !enabled);1455this._domNode.setAttribute('aria-disabled', String(!enabled));1456this._domNode.tabIndex = enabled ? 0 : -1;1457}14581459public setExpanded(expanded: boolean): void {1460this._domNode.setAttribute('aria-expanded', String(!!expanded));1461if (expanded) {1462this._domNode.classList.remove(...ThemeIcon.asClassNameArray(findCollapsedIcon));1463this._domNode.classList.add(...ThemeIcon.asClassNameArray(findExpandedIcon));1464} else {1465this._domNode.classList.remove(...ThemeIcon.asClassNameArray(findExpandedIcon));1466this._domNode.classList.add(...ThemeIcon.asClassNameArray(findCollapsedIcon));1467}1468}1469}14701471// theming14721473registerThemingParticipant((theme, collector) => {1474const findMatchHighlightBorder = theme.getColor(editorFindMatchHighlightBorder);1475if (findMatchHighlightBorder) {1476collector.addRule(`.monaco-editor .findMatch { border: 1px ${isHighContrast(theme.type) ? 'dotted' : 'solid'} ${findMatchHighlightBorder}; box-sizing: border-box; }`);1477}14781479const findRangeHighlightBorder = theme.getColor(editorFindRangeHighlightBorder);1480if (findRangeHighlightBorder) {1481collector.addRule(`.monaco-editor .findScope { border: 1px ${isHighContrast(theme.type) ? 'dashed' : 'solid'} ${findRangeHighlightBorder}; }`);1482}14831484const hcBorder = theme.getColor(contrastBorder);1485if (hcBorder) {1486collector.addRule(`.monaco-editor .find-widget { border: 1px solid ${hcBorder}; }`);1487}1488const findMatchForeground = theme.getColor(editorFindMatchForeground);1489if (findMatchForeground) {1490collector.addRule(`.monaco-editor .findMatchInline { color: ${findMatchForeground}; }`);1491}1492const findMatchHighlightForeground = theme.getColor(editorFindMatchHighlightForeground);1493if (findMatchHighlightForeground) {1494collector.addRule(`.monaco-editor .currentFindMatchInline { color: ${findMatchHighlightForeground}; }`);1495}1496});149714981499