Path: blob/main/src/vs/editor/contrib/find/browser/findWidget.ts
3296 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 } 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 } 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_REPLACE_INPUT_FOCUSED, FIND_IDS, MATCHES_LIMIT } from './findModel.js';28import { FindReplaceState, FindReplaceStateChangedEvent } from './findState.js';29import * as nls from '../../../../nls.js';30import { AccessibilitySupport } 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 { createInstantHoverDelegate, getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js';44import { IHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegate.js';45import { IHoverService } from '../../../../platform/hover/browser/hover.js';46import { IHistory } from '../../../../base/common/history.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;147148private readonly _findFocusTracker: dom.IFocusTracker;149private readonly _findInputFocused: IContextKey<boolean>;150private readonly _replaceFocusTracker: dom.IFocusTracker;151private readonly _replaceInputFocused: IContextKey<boolean>;152private _viewZone?: FindWidgetViewZone;153private _viewZoneId?: string;154155private _resizeSash!: Sash;156private _resized!: boolean;157private readonly _updateHistoryDelayer: Delayer<void>;158159constructor(160codeEditor: ICodeEditor,161controller: IFindController,162state: FindReplaceState,163contextViewProvider: IContextViewProvider,164keybindingService: IKeybindingService,165contextKeyService: IContextKeyService,166private readonly _hoverService: IHoverService,167private readonly _findWidgetSearchHistory: IHistory<string> | undefined,168private readonly _replaceWidgetHistory: IHistory<string> | undefined,169) {170super();171this._codeEditor = codeEditor;172this._controller = controller;173this._state = state;174this._contextViewProvider = contextViewProvider;175this._keybindingService = keybindingService;176this._contextKeyService = contextKeyService;177178this._isVisible = false;179this._isReplaceVisible = false;180this._ignoreChangeEvent = false;181182this._updateHistoryDelayer = new Delayer<void>(500);183this._register(toDisposable(() => this._updateHistoryDelayer.cancel()));184this._register(this._state.onFindReplaceStateChange((e) => this._onStateChanged(e)));185this._buildDomNode();186this._updateButtons();187this._tryUpdateWidgetWidth();188this._findInput.inputBox.layout();189190this._register(this._codeEditor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => {191if (e.hasChanged(EditorOption.readOnly)) {192if (this._codeEditor.getOption(EditorOption.readOnly)) {193// Hide replace part if editor becomes read only194this._state.change({ isReplaceRevealed: false }, false);195}196this._updateButtons();197}198if (e.hasChanged(EditorOption.layoutInfo)) {199this._tryUpdateWidgetWidth();200}201202if (e.hasChanged(EditorOption.accessibilitySupport)) {203this.updateAccessibilitySupport();204}205206if (e.hasChanged(EditorOption.find)) {207const supportLoop = this._codeEditor.getOption(EditorOption.find).loop;208this._state.change({ loop: supportLoop }, false);209const addExtraSpaceOnTop = this._codeEditor.getOption(EditorOption.find).addExtraSpaceOnTop;210if (addExtraSpaceOnTop && !this._viewZone) {211this._viewZone = new FindWidgetViewZone(0);212this._showViewZone();213}214if (!addExtraSpaceOnTop && this._viewZone) {215this._removeViewZone();216}217}218}));219this.updateAccessibilitySupport();220this._register(this._codeEditor.onDidChangeCursorSelection(() => {221if (this._isVisible) {222this._updateToggleSelectionFindButton();223}224}));225this._register(this._codeEditor.onDidFocusEditorWidget(async () => {226if (this._isVisible) {227const globalBufferTerm = await this._controller.getGlobalBufferTerm();228if (globalBufferTerm && globalBufferTerm !== this._state.searchString) {229this._state.change({ searchString: globalBufferTerm }, false);230this._findInput.select();231}232}233}));234this._findInputFocused = CONTEXT_FIND_INPUT_FOCUSED.bindTo(contextKeyService);235this._findFocusTracker = this._register(dom.trackFocus(this._findInput.inputBox.inputElement));236this._register(this._findFocusTracker.onDidFocus(() => {237this._findInputFocused.set(true);238this._updateSearchScope();239}));240this._register(this._findFocusTracker.onDidBlur(() => {241this._findInputFocused.set(false);242}));243244this._replaceInputFocused = CONTEXT_REPLACE_INPUT_FOCUSED.bindTo(contextKeyService);245this._replaceFocusTracker = this._register(dom.trackFocus(this._replaceInput.inputBox.inputElement));246this._register(this._replaceFocusTracker.onDidFocus(() => {247this._replaceInputFocused.set(true);248this._updateSearchScope();249}));250this._register(this._replaceFocusTracker.onDidBlur(() => {251this._replaceInputFocused.set(false);252}));253254this._codeEditor.addOverlayWidget(this);255if (this._codeEditor.getOption(EditorOption.find).addExtraSpaceOnTop) {256this._viewZone = new FindWidgetViewZone(0); // Put it before the first line then users can scroll beyond the first line.257}258259this._register(this._codeEditor.onDidChangeModel(() => {260if (!this._isVisible) {261return;262}263this._viewZoneId = undefined;264}));265266267this._register(this._codeEditor.onDidScrollChange((e) => {268if (e.scrollTopChanged) {269this._layoutViewZone();270return;271}272273// for other scroll changes, layout the viewzone in next tick to avoid ruining current rendering.274setTimeout(() => {275this._layoutViewZone();276}, 0);277}));278}279280// ----- IOverlayWidget API281282public getId(): string {283return FindWidget.ID;284}285286public getDomNode(): HTMLElement {287return this._domNode;288}289290public getPosition(): IOverlayWidgetPosition | null {291if (this._isVisible) {292return {293preference: OverlayWidgetPositionPreference.TOP_RIGHT_CORNER294};295}296return null;297}298299// ----- React to state changes300301private _onStateChanged(e: FindReplaceStateChangedEvent): void {302if (e.searchString) {303try {304this._ignoreChangeEvent = true;305this._findInput.setValue(this._state.searchString);306} finally {307this._ignoreChangeEvent = false;308}309this._updateButtons();310}311if (e.replaceString) {312this._replaceInput.inputBox.value = this._state.replaceString;313}314if (e.isRevealed) {315if (this._state.isRevealed) {316this._reveal();317} else {318this._hide(true);319}320}321if (e.isReplaceRevealed) {322if (this._state.isReplaceRevealed) {323if (!this._codeEditor.getOption(EditorOption.readOnly) && !this._isReplaceVisible) {324this._isReplaceVisible = true;325this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode);326this._updateButtons();327this._replaceInput.inputBox.layout();328}329} else {330if (this._isReplaceVisible) {331this._isReplaceVisible = false;332this._updateButtons();333}334}335}336if ((e.isRevealed || e.isReplaceRevealed) && (this._state.isRevealed || this._state.isReplaceRevealed)) {337if (this._tryUpdateHeight()) {338this._showViewZone();339}340}341342if (e.isRegex) {343this._findInput.setRegex(this._state.isRegex);344}345if (e.wholeWord) {346this._findInput.setWholeWords(this._state.wholeWord);347}348if (e.matchCase) {349this._findInput.setCaseSensitive(this._state.matchCase);350}351if (e.preserveCase) {352this._replaceInput.setPreserveCase(this._state.preserveCase);353}354if (e.searchScope) {355if (this._state.searchScope) {356this._toggleSelectionFind.checked = true;357} else {358this._toggleSelectionFind.checked = false;359}360this._updateToggleSelectionFindButton();361}362if (e.searchString || e.matchesCount || e.matchesPosition) {363const showRedOutline = (this._state.searchString.length > 0 && this._state.matchesCount === 0);364this._domNode.classList.toggle('no-results', showRedOutline);365366this._updateMatchesCount();367this._updateButtons();368}369if (e.searchString || e.currentMatch) {370this._layoutViewZone();371}372if (e.updateHistory) {373this._delayedUpdateHistory();374}375if (e.loop) {376this._updateButtons();377}378}379380private _delayedUpdateHistory() {381this._updateHistoryDelayer.trigger(this._updateHistory.bind(this)).then(undefined, onUnexpectedError);382}383384private _updateHistory() {385if (this._state.searchString) {386this._findInput.inputBox.addToHistory();387}388if (this._state.replaceString) {389this._replaceInput.inputBox.addToHistory();390}391}392393private _updateMatchesCount(): void {394this._matchesCount.style.minWidth = MAX_MATCHES_COUNT_WIDTH + 'px';395if (this._state.matchesCount >= MATCHES_LIMIT) {396this._matchesCount.title = NLS_MATCHES_COUNT_LIMIT_TITLE;397} else {398this._matchesCount.title = '';399}400401// remove previous content402this._matchesCount.firstChild?.remove();403404let label: string;405if (this._state.matchesCount > 0) {406let matchesCount: string = String(this._state.matchesCount);407if (this._state.matchesCount >= MATCHES_LIMIT) {408matchesCount += '+';409}410let matchesPosition: string = String(this._state.matchesPosition);411if (matchesPosition === '0') {412matchesPosition = '?';413}414label = strings.format(NLS_MATCHES_LOCATION, matchesPosition, matchesCount);415} else {416label = NLS_NO_RESULTS;417}418419this._matchesCount.appendChild(document.createTextNode(label));420421alertFn(this._getAriaLabel(label, this._state.currentMatch, this._state.searchString));422MAX_MATCHES_COUNT_WIDTH = Math.max(MAX_MATCHES_COUNT_WIDTH, this._matchesCount.clientWidth);423}424425// ----- actions426427private _getAriaLabel(label: string, currentMatch: Range | null, searchString: string): string {428if (label === NLS_NO_RESULTS) {429return searchString === ''430? nls.localize('ariaSearchNoResultEmpty', "{0} found", label)431: nls.localize('ariaSearchNoResult', "{0} found for '{1}'", label, searchString);432}433if (currentMatch) {434const ariaLabel = nls.localize('ariaSearchNoResultWithLineNum', "{0} found for '{1}', at {2}", label, searchString, currentMatch.startLineNumber + ':' + currentMatch.startColumn);435const model = this._codeEditor.getModel();436if (model && (currentMatch.startLineNumber <= model.getLineCount()) && (currentMatch.startLineNumber >= 1)) {437const lineContent = model.getLineContent(currentMatch.startLineNumber);438return `${lineContent}, ${ariaLabel}`;439}440441return ariaLabel;442}443444return nls.localize('ariaSearchNoResultWithLineNumNoCurrentMatch', "{0} found for '{1}'", label, searchString);445}446447/**448* If 'selection find' is ON we should not disable the button (its function is to cancel 'selection find').449* If 'selection find' is OFF we enable the button only if there is a selection.450*/451private _updateToggleSelectionFindButton(): void {452const selection = this._codeEditor.getSelection();453const isSelection = selection ? (selection.startLineNumber !== selection.endLineNumber || selection.startColumn !== selection.endColumn) : false;454const isChecked = this._toggleSelectionFind.checked;455456if (this._isVisible && (isChecked || isSelection)) {457this._toggleSelectionFind.enable();458} else {459this._toggleSelectionFind.disable();460}461}462463private _updateButtons(): void {464this._findInput.setEnabled(this._isVisible);465this._replaceInput.setEnabled(this._isVisible && this._isReplaceVisible);466this._updateToggleSelectionFindButton();467this._closeBtn.setEnabled(this._isVisible);468469const findInputIsNonEmpty = (this._state.searchString.length > 0);470const matchesCount = this._state.matchesCount ? true : false;471this._prevBtn.setEnabled(this._isVisible && findInputIsNonEmpty && matchesCount && this._state.canNavigateBack());472this._nextBtn.setEnabled(this._isVisible && findInputIsNonEmpty && matchesCount && this._state.canNavigateForward());473this._replaceBtn.setEnabled(this._isVisible && this._isReplaceVisible && findInputIsNonEmpty);474this._replaceAllBtn.setEnabled(this._isVisible && this._isReplaceVisible && findInputIsNonEmpty);475476this._domNode.classList.toggle('replaceToggled', this._isReplaceVisible);477this._toggleReplaceBtn.setExpanded(this._isReplaceVisible);478479const canReplace = !this._codeEditor.getOption(EditorOption.readOnly);480this._toggleReplaceBtn.setEnabled(this._isVisible && canReplace);481}482483private _revealTimeouts: Timeout[] = [];484485private _reveal(): void {486this._revealTimeouts.forEach(e => {487clearTimeout(e);488});489490this._revealTimeouts = [];491492if (!this._isVisible) {493this._isVisible = true;494495const selection = this._codeEditor.getSelection();496497switch (this._codeEditor.getOption(EditorOption.find).autoFindInSelection) {498case 'always':499this._toggleSelectionFind.checked = true;500break;501case 'never':502this._toggleSelectionFind.checked = false;503break;504case 'multiline': {505const isSelectionMultipleLine = !!selection && selection.startLineNumber !== selection.endLineNumber;506this._toggleSelectionFind.checked = isSelectionMultipleLine;507break;508}509default:510break;511}512513this._tryUpdateWidgetWidth();514this._updateButtons();515516this._revealTimeouts.push(setTimeout(() => {517this._domNode.classList.add('visible');518this._domNode.setAttribute('aria-hidden', 'false');519}, 0));520521// validate query again as it's being dismissed when we hide the find widget.522this._revealTimeouts.push(setTimeout(() => {523this._findInput.validate();524}, 200));525526this._codeEditor.layoutOverlayWidget(this);527528let adjustEditorScrollTop = true;529if (this._codeEditor.getOption(EditorOption.find).seedSearchStringFromSelection && selection) {530const domNode = this._codeEditor.getDomNode();531if (domNode) {532const editorCoords = dom.getDomNodePagePosition(domNode);533const startCoords = this._codeEditor.getScrolledVisiblePosition(selection.getStartPosition());534const startLeft = editorCoords.left + (startCoords ? startCoords.left : 0);535const startTop = startCoords ? startCoords.top : 0;536537if (this._viewZone && startTop < this._viewZone.heightInPx) {538if (selection.endLineNumber > selection.startLineNumber) {539adjustEditorScrollTop = false;540}541542const leftOfFindWidget = dom.getTopLeftOffset(this._domNode).left;543if (startLeft > leftOfFindWidget) {544adjustEditorScrollTop = false;545}546const endCoords = this._codeEditor.getScrolledVisiblePosition(selection.getEndPosition());547const endLeft = editorCoords.left + (endCoords ? endCoords.left : 0);548if (endLeft > leftOfFindWidget) {549adjustEditorScrollTop = false;550}551}552}553}554this._showViewZone(adjustEditorScrollTop);555}556}557558private _hide(focusTheEditor: boolean): void {559this._revealTimeouts.forEach(e => {560clearTimeout(e);561});562563this._revealTimeouts = [];564565if (this._isVisible) {566this._isVisible = false;567568this._updateButtons();569570this._domNode.classList.remove('visible');571this._domNode.setAttribute('aria-hidden', 'true');572this._findInput.clearMessage();573if (focusTheEditor) {574this._codeEditor.focus();575}576this._codeEditor.layoutOverlayWidget(this);577this._removeViewZone();578}579}580581private _layoutViewZone(targetScrollTop?: number) {582const addExtraSpaceOnTop = this._codeEditor.getOption(EditorOption.find).addExtraSpaceOnTop;583584if (!addExtraSpaceOnTop) {585this._removeViewZone();586return;587}588589if (!this._isVisible) {590return;591}592const viewZone = this._viewZone;593if (this._viewZoneId !== undefined || !viewZone) {594return;595}596597this._codeEditor.changeViewZones((accessor) => {598viewZone.heightInPx = this._getHeight();599this._viewZoneId = accessor.addZone(viewZone);600// scroll top adjust to make sure the editor doesn't scroll when adding viewzone at the beginning.601this._codeEditor.setScrollTop(targetScrollTop || this._codeEditor.getScrollTop() + viewZone.heightInPx);602});603}604605private _showViewZone(adjustScroll: boolean = true) {606if (!this._isVisible) {607return;608}609610const addExtraSpaceOnTop = this._codeEditor.getOption(EditorOption.find).addExtraSpaceOnTop;611612if (!addExtraSpaceOnTop) {613return;614}615616if (this._viewZone === undefined) {617this._viewZone = new FindWidgetViewZone(0);618}619620const viewZone = this._viewZone;621622this._codeEditor.changeViewZones((accessor) => {623if (this._viewZoneId !== undefined) {624// the view zone already exists, we need to update the height625const newHeight = this._getHeight();626if (newHeight === viewZone.heightInPx) {627return;628}629630const scrollAdjustment = newHeight - viewZone.heightInPx;631viewZone.heightInPx = newHeight;632accessor.layoutZone(this._viewZoneId);633634if (adjustScroll) {635this._codeEditor.setScrollTop(this._codeEditor.getScrollTop() + scrollAdjustment);636}637638return;639} else {640let scrollAdjustment = this._getHeight();641642// if the editor has top padding, factor that into the zone height643scrollAdjustment -= this._codeEditor.getOption(EditorOption.padding).top;644if (scrollAdjustment <= 0) {645return;646}647648viewZone.heightInPx = scrollAdjustment;649this._viewZoneId = accessor.addZone(viewZone);650651if (adjustScroll) {652this._codeEditor.setScrollTop(this._codeEditor.getScrollTop() + scrollAdjustment);653}654}655});656}657658private _removeViewZone() {659this._codeEditor.changeViewZones((accessor) => {660if (this._viewZoneId !== undefined) {661accessor.removeZone(this._viewZoneId);662this._viewZoneId = undefined;663if (this._viewZone) {664this._codeEditor.setScrollTop(this._codeEditor.getScrollTop() - this._viewZone.heightInPx);665this._viewZone = undefined;666}667}668});669}670671private _tryUpdateWidgetWidth() {672if (!this._isVisible) {673return;674}675if (!this._domNode.isConnected) {676// the widget is not in the DOM677return;678}679680const layoutInfo = this._codeEditor.getLayoutInfo();681const editorContentWidth = layoutInfo.contentWidth;682683if (editorContentWidth <= 0) {684// for example, diff view original editor685this._domNode.classList.add('hiddenEditor');686return;687} else if (this._domNode.classList.contains('hiddenEditor')) {688this._domNode.classList.remove('hiddenEditor');689}690691const editorWidth = layoutInfo.width;692const minimapWidth = layoutInfo.minimap.minimapWidth;693let collapsedFindWidget = false;694let reducedFindWidget = false;695let narrowFindWidget = false;696697if (this._resized) {698const widgetWidth = dom.getTotalWidth(this._domNode);699700if (widgetWidth > FIND_WIDGET_INITIAL_WIDTH) {701// as the widget is resized by users, we may need to change the max width of the widget as the editor width changes.702this._domNode.style.maxWidth = `${editorWidth - 28 - minimapWidth - 15}px`;703this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode);704return;705}706}707708if (FIND_WIDGET_INITIAL_WIDTH + 28 + minimapWidth >= editorWidth) {709reducedFindWidget = true;710}711if (FIND_WIDGET_INITIAL_WIDTH + 28 + minimapWidth - MAX_MATCHES_COUNT_WIDTH >= editorWidth) {712narrowFindWidget = true;713}714if (FIND_WIDGET_INITIAL_WIDTH + 28 + minimapWidth - MAX_MATCHES_COUNT_WIDTH >= editorWidth + 50) {715collapsedFindWidget = true;716}717this._domNode.classList.toggle('collapsed-find-widget', collapsedFindWidget);718this._domNode.classList.toggle('narrow-find-widget', narrowFindWidget);719this._domNode.classList.toggle('reduced-find-widget', reducedFindWidget);720721if (!narrowFindWidget && !collapsedFindWidget) {722// the minimal left offset of findwidget is 15px.723this._domNode.style.maxWidth = `${editorWidth - 28 - minimapWidth - 15}px`;724}725726this._findInput.layout({ collapsedFindWidget, narrowFindWidget, reducedFindWidget });727if (this._resized) {728const findInputWidth = this._findInput.inputBox.element.clientWidth;729if (findInputWidth > 0) {730this._replaceInput.width = findInputWidth;731}732} else if (this._isReplaceVisible) {733this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode);734}735}736737private _getHeight(): number {738let totalheight = 0;739740// find input margin top741totalheight += 4;742743// find input height744totalheight += this._findInput.inputBox.height + 2 /** input box border */;745746if (this._isReplaceVisible) {747// replace input margin748totalheight += 4;749750totalheight += this._replaceInput.inputBox.height + 2 /** input box border */;751}752753// margin bottom754totalheight += 4;755return totalheight;756}757758private _tryUpdateHeight(): boolean {759const totalHeight = this._getHeight();760if (this._cachedHeight !== null && this._cachedHeight === totalHeight) {761return false;762}763764this._cachedHeight = totalHeight;765this._domNode.style.height = `${totalHeight}px`;766767return true;768}769770// ----- Public771772public focusFindInput(): void {773this._findInput.select();774// Edge browser requires focus() in addition to select()775this._findInput.focus();776}777778public focusReplaceInput(): void {779this._replaceInput.select();780// Edge browser requires focus() in addition to select()781this._replaceInput.focus();782}783784public highlightFindOptions(): void {785this._findInput.highlightFindOptions();786}787788private _updateSearchScope(): void {789if (!this._codeEditor.hasModel()) {790return;791}792793if (this._toggleSelectionFind.checked) {794const selections = this._codeEditor.getSelections();795796selections.map(selection => {797if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) {798selection = selection.setEndPosition(799selection.endLineNumber - 1,800this._codeEditor.getModel()!.getLineMaxColumn(selection.endLineNumber - 1)801);802}803const currentMatch = this._state.currentMatch;804if (selection.startLineNumber !== selection.endLineNumber) {805if (!Range.equalsRange(selection, currentMatch)) {806return selection;807}808}809return null;810}).filter(element => !!element);811812if (selections.length) {813this._state.change({ searchScope: selections as Range[] }, true);814}815}816}817818private _onFindInputMouseDown(e: IMouseEvent): void {819// on linux, middle key does pasting.820if (e.middleButton) {821e.stopPropagation();822}823}824825private _onFindInputKeyDown(e: IKeyboardEvent): void {826if (e.equals(ctrlKeyMod | KeyCode.Enter)) {827if (this._keybindingService.dispatchEvent(e, e.target)) {828e.preventDefault();829return;830} else {831this._findInput.inputBox.insertAtCursor('\n');832e.preventDefault();833return;834}835}836837if (e.equals(KeyCode.Tab)) {838if (this._isReplaceVisible) {839this._replaceInput.focus();840} else {841this._findInput.focusOnCaseSensitive();842}843e.preventDefault();844return;845}846847if (e.equals(KeyMod.CtrlCmd | KeyCode.DownArrow)) {848this._codeEditor.focus();849e.preventDefault();850return;851}852853if (e.equals(KeyCode.UpArrow)) {854return stopPropagationForMultiLineUpwards(e, this._findInput.getValue(), this._findInput.domNode.querySelector('textarea'));855}856857if (e.equals(KeyCode.DownArrow)) {858return stopPropagationForMultiLineDownwards(e, this._findInput.getValue(), this._findInput.domNode.querySelector('textarea'));859}860}861862private _onReplaceInputKeyDown(e: IKeyboardEvent): void {863if (e.equals(ctrlKeyMod | KeyCode.Enter)) {864if (this._keybindingService.dispatchEvent(e, e.target)) {865e.preventDefault();866return;867} else {868this._replaceInput.inputBox.insertAtCursor('\n');869e.preventDefault();870return;871}872873}874875if (e.equals(KeyCode.Tab)) {876this._findInput.focusOnCaseSensitive();877e.preventDefault();878return;879}880881if (e.equals(KeyMod.Shift | KeyCode.Tab)) {882this._findInput.focus();883e.preventDefault();884return;885}886887if (e.equals(KeyMod.CtrlCmd | KeyCode.DownArrow)) {888this._codeEditor.focus();889e.preventDefault();890return;891}892893if (e.equals(KeyCode.UpArrow)) {894return stopPropagationForMultiLineUpwards(e, this._replaceInput.inputBox.value, this._replaceInput.inputBox.element.querySelector('textarea'));895}896897if (e.equals(KeyCode.DownArrow)) {898return stopPropagationForMultiLineDownwards(e, this._replaceInput.inputBox.value, this._replaceInput.inputBox.element.querySelector('textarea'));899}900}901902// ----- sash903public getVerticalSashLeft(_sash: Sash): number {904return 0;905}906// ----- initialization907908private _keybindingLabelFor(actionId: string): string {909const kb = this._keybindingService.lookupKeybinding(actionId);910if (!kb) {911return '';912}913return ` (${kb.getLabel()})`;914}915916private _buildDomNode(): void {917const flexibleHeight = true;918const flexibleWidth = true;919// Find input920const findSearchHistoryConfig = this._codeEditor.getOption(EditorOption.find).history;921const replaceHistoryConfig = this._codeEditor.getOption(EditorOption.find).replaceHistory;922this._findInput = this._register(new ContextScopedFindInput(null, this._contextViewProvider, {923width: FIND_INPUT_AREA_WIDTH,924label: NLS_FIND_INPUT_LABEL,925placeholder: NLS_FIND_INPUT_PLACEHOLDER,926appendCaseSensitiveLabel: this._keybindingLabelFor(FIND_IDS.ToggleCaseSensitiveCommand),927appendWholeWordsLabel: this._keybindingLabelFor(FIND_IDS.ToggleWholeWordCommand),928appendRegexLabel: this._keybindingLabelFor(FIND_IDS.ToggleRegexCommand),929validation: (value: string): InputBoxMessage | null => {930if (value.length === 0 || !this._findInput.getRegex()) {931return null;932}933try {934// use `g` and `u` which are also used by the TextModel search935new RegExp(value, 'gu');936return null;937} catch (e) {938return { content: e.message };939}940},941flexibleHeight,942flexibleWidth,943flexibleMaxHeight: 118,944showCommonFindToggles: true,945showHistoryHint: () => showHistoryKeybindingHint(this._keybindingService),946inputBoxStyles: defaultInputBoxStyles,947toggleStyles: defaultToggleStyles,948history: findSearchHistoryConfig === 'workspace' ? this._findWidgetSearchHistory : new Set([]),949}, this._contextKeyService));950this._findInput.setRegex(!!this._state.isRegex);951this._findInput.setCaseSensitive(!!this._state.matchCase);952this._findInput.setWholeWords(!!this._state.wholeWord);953this._register(this._findInput.onKeyDown((e) => {954if (e.equals(KeyCode.Enter) && !this._codeEditor.getOption(EditorOption.find).findOnType) {955this._state.change({ searchString: this._findInput.getValue() }, true);956}957this._onFindInputKeyDown(e);958}));959this._register(this._findInput.inputBox.onDidChange(() => {960if (this._ignoreChangeEvent || !this._codeEditor.getOption(EditorOption.find).findOnType) {961return;962}963this._state.change({ searchString: this._findInput.getValue() }, true);964}));965this._register(this._findInput.onDidOptionChange(() => {966this._state.change({967isRegex: this._findInput.getRegex(),968wholeWord: this._findInput.getWholeWords(),969matchCase: this._findInput.getCaseSensitive()970}, true);971}));972this._register(this._findInput.onCaseSensitiveKeyDown((e) => {973if (e.equals(KeyMod.Shift | KeyCode.Tab)) {974if (this._isReplaceVisible) {975this._replaceInput.focus();976e.preventDefault();977}978}979}));980this._register(this._findInput.onRegexKeyDown((e) => {981if (e.equals(KeyCode.Tab)) {982if (this._isReplaceVisible) {983this._replaceInput.focusOnPreserve();984e.preventDefault();985}986}987}));988this._register(this._findInput.inputBox.onDidHeightChange((e) => {989if (this._tryUpdateHeight()) {990this._showViewZone();991}992}));993if (platform.isLinux) {994this._register(this._findInput.onMouseDown((e) => this._onFindInputMouseDown(e)));995}996997this._matchesCount = document.createElement('div');998this._matchesCount.className = 'matchesCount';999this._updateMatchesCount();10001001// Create a scoped hover delegate for all find related buttons1002const hoverDelegate = this._register(createInstantHoverDelegate());10031004// Previous button1005this._prevBtn = this._register(new SimpleButton({1006label: NLS_PREVIOUS_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.PreviousMatchFindAction),1007icon: findPreviousMatchIcon,1008hoverDelegate,1009onTrigger: () => {1010assertReturnsDefined(this._codeEditor.getAction(FIND_IDS.PreviousMatchFindAction)).run().then(undefined, onUnexpectedError);1011}1012}, this._hoverService));10131014// Next button1015this._nextBtn = this._register(new SimpleButton({1016label: NLS_NEXT_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.NextMatchFindAction),1017icon: findNextMatchIcon,1018hoverDelegate,1019onTrigger: () => {1020assertReturnsDefined(this._codeEditor.getAction(FIND_IDS.NextMatchFindAction)).run().then(undefined, onUnexpectedError);1021}1022}, this._hoverService));10231024const findPart = document.createElement('div');1025findPart.className = 'find-part';1026findPart.appendChild(this._findInput.domNode);1027const actionsContainer = document.createElement('div');1028actionsContainer.className = 'find-actions';1029findPart.appendChild(actionsContainer);1030actionsContainer.appendChild(this._matchesCount);1031actionsContainer.appendChild(this._prevBtn.domNode);1032actionsContainer.appendChild(this._nextBtn.domNode);10331034// Toggle selection button1035this._toggleSelectionFind = this._register(new Toggle({1036icon: findSelectionIcon,1037title: NLS_TOGGLE_SELECTION_FIND_TITLE + this._keybindingLabelFor(FIND_IDS.ToggleSearchScopeCommand),1038isChecked: false,1039hoverDelegate: hoverDelegate,1040inputActiveOptionBackground: asCssVariable(inputActiveOptionBackground),1041inputActiveOptionBorder: asCssVariable(inputActiveOptionBorder),1042inputActiveOptionForeground: asCssVariable(inputActiveOptionForeground),1043}));10441045this._register(this._toggleSelectionFind.onChange(() => {1046if (this._toggleSelectionFind.checked) {1047if (this._codeEditor.hasModel()) {1048let selections = this._codeEditor.getSelections();1049selections = selections.map(selection => {1050if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) {1051selection = selection.setEndPosition(selection.endLineNumber - 1, this._codeEditor.getModel()!.getLineMaxColumn(selection.endLineNumber - 1));1052}1053if (!selection.isEmpty()) {1054return selection;1055}1056return null;1057}).filter((element): element is Selection => !!element);10581059if (selections.length) {1060this._state.change({ searchScope: selections as Range[] }, true);1061}1062}1063} else {1064this._state.change({ searchScope: null }, true);1065}1066}));10671068actionsContainer.appendChild(this._toggleSelectionFind.domNode);10691070// Close button1071this._closeBtn = this._register(new SimpleButton({1072label: NLS_CLOSE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.CloseFindWidgetCommand),1073icon: widgetClose,1074hoverDelegate,1075onTrigger: () => {1076this._state.change({ isRevealed: false, searchScope: null }, false);1077},1078onKeyDown: (e) => {1079if (e.equals(KeyCode.Tab)) {1080if (this._isReplaceVisible) {1081if (this._replaceBtn.isEnabled()) {1082this._replaceBtn.focus();1083} else {1084this._codeEditor.focus();1085}1086e.preventDefault();1087}1088}1089}1090}, this._hoverService));10911092// Replace input1093this._replaceInput = this._register(new ContextScopedReplaceInput(null, undefined, {1094label: NLS_REPLACE_INPUT_LABEL,1095placeholder: NLS_REPLACE_INPUT_PLACEHOLDER,1096appendPreserveCaseLabel: this._keybindingLabelFor(FIND_IDS.TogglePreserveCaseCommand),1097history: replaceHistoryConfig === 'workspace' ? this._replaceWidgetHistory : new Set([]),1098flexibleHeight,1099flexibleWidth,1100flexibleMaxHeight: 118,1101showHistoryHint: () => showHistoryKeybindingHint(this._keybindingService),1102inputBoxStyles: defaultInputBoxStyles,1103toggleStyles: defaultToggleStyles,1104}, this._contextKeyService, true));1105this._replaceInput.setPreserveCase(!!this._state.preserveCase);1106this._register(this._replaceInput.onKeyDown((e) => this._onReplaceInputKeyDown(e)));1107this._register(this._replaceInput.inputBox.onDidChange(() => {1108this._state.change({ replaceString: this._replaceInput.inputBox.value }, false);1109}));1110this._register(this._replaceInput.inputBox.onDidHeightChange((e) => {1111if (this._isReplaceVisible && this._tryUpdateHeight()) {1112this._showViewZone();1113}1114}));1115this._register(this._replaceInput.onDidOptionChange(() => {1116this._state.change({1117preserveCase: this._replaceInput.getPreserveCase()1118}, true);1119}));1120this._register(this._replaceInput.onPreserveCaseKeyDown((e) => {1121if (e.equals(KeyCode.Tab)) {1122if (this._prevBtn.isEnabled()) {1123this._prevBtn.focus();1124} else if (this._nextBtn.isEnabled()) {1125this._nextBtn.focus();1126} else if (this._toggleSelectionFind.enabled) {1127this._toggleSelectionFind.focus();1128} else if (this._closeBtn.isEnabled()) {1129this._closeBtn.focus();1130}11311132e.preventDefault();1133}1134}));11351136// Create scoped hover delegate for replace actions1137const replaceHoverDelegate = this._register(createInstantHoverDelegate());11381139// Replace one button1140this._replaceBtn = this._register(new SimpleButton({1141label: NLS_REPLACE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.ReplaceOneAction),1142icon: findReplaceIcon,1143hoverDelegate: replaceHoverDelegate,1144onTrigger: () => {1145this._controller.replace();1146},1147onKeyDown: (e) => {1148if (e.equals(KeyMod.Shift | KeyCode.Tab)) {1149this._closeBtn.focus();1150e.preventDefault();1151}1152}1153}, this._hoverService));11541155// Replace all button1156this._replaceAllBtn = this._register(new SimpleButton({1157label: NLS_REPLACE_ALL_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.ReplaceAllAction),1158icon: findReplaceAllIcon,1159hoverDelegate: replaceHoverDelegate,1160onTrigger: () => {1161this._controller.replaceAll();1162}1163}, this._hoverService));11641165const replacePart = document.createElement('div');1166replacePart.className = 'replace-part';1167replacePart.appendChild(this._replaceInput.domNode);11681169const replaceActionsContainer = document.createElement('div');1170replaceActionsContainer.className = 'replace-actions';1171replacePart.appendChild(replaceActionsContainer);11721173replaceActionsContainer.appendChild(this._replaceBtn.domNode);1174replaceActionsContainer.appendChild(this._replaceAllBtn.domNode);11751176// Toggle replace button1177this._toggleReplaceBtn = this._register(new SimpleButton({1178label: NLS_TOGGLE_REPLACE_MODE_BTN_LABEL,1179className: 'codicon toggle left',1180onTrigger: () => {1181this._state.change({ isReplaceRevealed: !this._isReplaceVisible }, false);1182if (this._isReplaceVisible) {1183this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode);1184this._replaceInput.inputBox.layout();1185}1186this._showViewZone();1187}1188}, this._hoverService));1189this._toggleReplaceBtn.setExpanded(this._isReplaceVisible);11901191// Widget1192this._domNode = document.createElement('div');1193this._domNode.className = 'editor-widget find-widget';1194this._domNode.setAttribute('aria-hidden', 'true');1195this._domNode.ariaLabel = NLS_FIND_DIALOG_LABEL;1196this._domNode.role = 'dialog';11971198// We need to set this explicitly, otherwise on IE11, the width inheritence of flex doesn't work.1199this._domNode.style.width = `${FIND_WIDGET_INITIAL_WIDTH}px`;12001201this._domNode.appendChild(this._toggleReplaceBtn.domNode);1202this._domNode.appendChild(findPart);1203this._domNode.appendChild(this._closeBtn.domNode);1204this._domNode.appendChild(replacePart);12051206this._resizeSash = this._register(new Sash(this._domNode, this, { orientation: Orientation.VERTICAL, size: 2 }));1207this._resized = false;1208let originalWidth = FIND_WIDGET_INITIAL_WIDTH;12091210this._register(this._resizeSash.onDidStart(() => {1211originalWidth = dom.getTotalWidth(this._domNode);1212}));12131214this._register(this._resizeSash.onDidChange((evt: ISashEvent) => {1215this._resized = true;1216const width = originalWidth + evt.startX - evt.currentX;12171218if (width < FIND_WIDGET_INITIAL_WIDTH) {1219// narrow down the find widget should be handled by CSS.1220return;1221}12221223const maxWidth = parseFloat(dom.getComputedStyle(this._domNode).maxWidth) || 0;1224if (width > maxWidth) {1225return;1226}1227this._domNode.style.width = `${width}px`;1228if (this._isReplaceVisible) {1229this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode);1230}12311232this._findInput.inputBox.layout();1233this._tryUpdateHeight();1234}));12351236this._register(this._resizeSash.onDidReset(() => {1237// users double click on the sash1238const currentWidth = dom.getTotalWidth(this._domNode);12391240if (currentWidth < FIND_WIDGET_INITIAL_WIDTH) {1241// The editor is narrow and the width of the find widget is controlled fully by CSS.1242return;1243}12441245let width = FIND_WIDGET_INITIAL_WIDTH;12461247if (!this._resized || currentWidth === FIND_WIDGET_INITIAL_WIDTH) {1248// 1. never resized before, double click should maximizes it1249// 2. users resized it already but its width is the same as default1250const layoutInfo = this._codeEditor.getLayoutInfo();1251width = layoutInfo.width - 28 - layoutInfo.minimap.minimapWidth - 15;1252this._resized = true;1253} else {1254/**1255* no op, the find widget should be shrinked to its default size.1256*/1257}125812591260this._domNode.style.width = `${width}px`;1261if (this._isReplaceVisible) {1262this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode);1263}12641265this._findInput.inputBox.layout();1266}));1267}12681269private updateAccessibilitySupport(): void {1270const value = this._codeEditor.getOption(EditorOption.accessibilitySupport);1271this._findInput.setFocusInputOnOptionClick(value !== AccessibilitySupport.Enabled);1272}12731274getViewState() {1275let widgetViewZoneVisible = false;1276if (this._viewZone && this._viewZoneId) {1277widgetViewZoneVisible = this._viewZone.heightInPx > this._codeEditor.getScrollTop();1278}12791280return {1281widgetViewZoneVisible,1282scrollTop: this._codeEditor.getScrollTop()1283};1284}12851286setViewState(state?: { widgetViewZoneVisible: boolean; scrollTop: number }) {1287if (!state) {1288return;1289}12901291if (state.widgetViewZoneVisible) {1292// we should add the view zone1293this._layoutViewZone(state.scrollTop);1294}1295}1296}12971298export interface ISimpleButtonOpts {1299readonly label: string;1300readonly className?: string;1301readonly icon?: ThemeIcon;1302readonly hoverDelegate?: IHoverDelegate;1303readonly onTrigger: () => void;1304readonly onKeyDown?: (e: IKeyboardEvent) => void;1305}13061307export class SimpleButton extends Widget {13081309private readonly _opts: ISimpleButtonOpts;1310private readonly _domNode: HTMLElement;13111312constructor(1313opts: ISimpleButtonOpts,1314hoverService: IHoverService1315) {1316super();1317this._opts = opts;13181319let className = 'button';1320if (this._opts.className) {1321className = className + ' ' + this._opts.className;1322}1323if (this._opts.icon) {1324className = className + ' ' + ThemeIcon.asClassName(this._opts.icon);1325}13261327this._domNode = document.createElement('div');1328this._domNode.tabIndex = 0;1329this._domNode.className = className;1330this._domNode.setAttribute('role', 'button');1331this._domNode.setAttribute('aria-label', this._opts.label);1332this._register(hoverService.setupManagedHover(opts.hoverDelegate ?? getDefaultHoverDelegate('element'), this._domNode, this._opts.label));13331334this.onclick(this._domNode, (e) => {1335this._opts.onTrigger();1336e.preventDefault();1337});13381339this.onkeydown(this._domNode, (e) => {1340if (e.equals(KeyCode.Space) || e.equals(KeyCode.Enter)) {1341this._opts.onTrigger();1342e.preventDefault();1343return;1344}1345this._opts.onKeyDown?.(e);1346});1347}13481349public get domNode(): HTMLElement {1350return this._domNode;1351}13521353public isEnabled(): boolean {1354return (this._domNode.tabIndex >= 0);1355}13561357public focus(): void {1358this._domNode.focus();1359}13601361public setEnabled(enabled: boolean): void {1362this._domNode.classList.toggle('disabled', !enabled);1363this._domNode.setAttribute('aria-disabled', String(!enabled));1364this._domNode.tabIndex = enabled ? 0 : -1;1365}13661367public setExpanded(expanded: boolean): void {1368this._domNode.setAttribute('aria-expanded', String(!!expanded));1369if (expanded) {1370this._domNode.classList.remove(...ThemeIcon.asClassNameArray(findCollapsedIcon));1371this._domNode.classList.add(...ThemeIcon.asClassNameArray(findExpandedIcon));1372} else {1373this._domNode.classList.remove(...ThemeIcon.asClassNameArray(findExpandedIcon));1374this._domNode.classList.add(...ThemeIcon.asClassNameArray(findCollapsedIcon));1375}1376}1377}13781379// theming13801381registerThemingParticipant((theme, collector) => {1382const findMatchHighlightBorder = theme.getColor(editorFindMatchHighlightBorder);1383if (findMatchHighlightBorder) {1384collector.addRule(`.monaco-editor .findMatch { border: 1px ${isHighContrast(theme.type) ? 'dotted' : 'solid'} ${findMatchHighlightBorder}; box-sizing: border-box; }`);1385}13861387const findRangeHighlightBorder = theme.getColor(editorFindRangeHighlightBorder);1388if (findRangeHighlightBorder) {1389collector.addRule(`.monaco-editor .findScope { border: 1px ${isHighContrast(theme.type) ? 'dashed' : 'solid'} ${findRangeHighlightBorder}; }`);1390}13911392const hcBorder = theme.getColor(contrastBorder);1393if (hcBorder) {1394collector.addRule(`.monaco-editor .find-widget { border: 1px solid ${hcBorder}; }`);1395}1396const findMatchForeground = theme.getColor(editorFindMatchForeground);1397if (findMatchForeground) {1398collector.addRule(`.monaco-editor .findMatchInline { color: ${findMatchForeground}; }`);1399}1400const findMatchHighlightForeground = theme.getColor(editorFindMatchHighlightForeground);1401if (findMatchHighlightForeground) {1402collector.addRule(`.monaco-editor .currentFindMatchInline { color: ${findMatchHighlightForeground}; }`);1403}1404});140514061407