Path: blob/main/src/vs/editor/contrib/find/browser/findController.ts
5251 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 { Delayer } from '../../../../base/common/async.js';6import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';7import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';8import * as strings from '../../../../base/common/strings.js';9import { ICodeEditor } from '../../../browser/editorBrowser.js';10import { EditorAction, EditorCommand, EditorContributionInstantiation, MultiEditorAction, registerEditorAction, registerEditorCommand, registerEditorContribution, registerMultiEditorAction, ServicesAccessor } from '../../../browser/editorExtensions.js';11import { EditorOption } from '../../../common/config/editorOptions.js';12import { overviewRulerRangeHighlight } from '../../../common/core/editorColorRegistry.js';13import { IRange } from '../../../common/core/range.js';14import { IEditorContribution } from '../../../common/editorCommon.js';15import { EditorContextKeys } from '../../../common/editorContextKeys.js';16import { OverviewRulerLane } from '../../../common/model.js';17import { CONTEXT_FIND_INPUT_FOCUSED, CONTEXT_FIND_WIDGET_VISIBLE, CONTEXT_REPLACE_INPUT_FOCUSED, FindModelBoundToEditorModel, FIND_IDS, ToggleCaseSensitiveKeybinding, TogglePreserveCaseKeybinding, ToggleRegexKeybinding, ToggleSearchScopeKeybinding, ToggleWholeWordKeybinding } from './findModel.js';18import { FindOptionsWidget } from './findOptionsWidget.js';19import { FindReplaceState, FindReplaceStateChangedEvent, INewFindReplaceState } from './findState.js';20import { FindWidget, IFindController } from './findWidget.js';21import * as nls from '../../../../nls.js';22import { MenuId } from '../../../../platform/actions/common/actions.js';23import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js';24import { ContextKeyExpr, IContextKey, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';25import { IContextViewService } from '../../../../platform/contextview/browser/contextView.js';26import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';27import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js';28import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js';29import { IQuickInputService } from '../../../../platform/quickinput/common/quickInput.js';30import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';31import { themeColorFromId } from '../../../../platform/theme/common/themeService.js';32import { Selection } from '../../../common/core/selection.js';33import { IHoverService } from '../../../../platform/hover/browser/hover.js';34import { FindWidgetSearchHistory } from './findWidgetSearchHistory.js';35import { ReplaceWidgetHistory } from './replaceWidgetHistory.js';36import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';37import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js';3839const SEARCH_STRING_MAX_LENGTH = 524288;4041export function getSelectionSearchString(editor: ICodeEditor, seedSearchStringFromSelection: 'single' | 'multiple' = 'single', seedSearchStringFromNonEmptySelection: boolean = false): string | null {42if (!editor.hasModel()) {43return null;44}4546const selection = editor.getSelection();47// if selection spans multiple lines, default search string to empty4849if ((seedSearchStringFromSelection === 'single' && selection.startLineNumber === selection.endLineNumber)50|| seedSearchStringFromSelection === 'multiple') {51if (selection.isEmpty()) {52const wordAtPosition = editor.getConfiguredWordAtPosition(selection.getStartPosition());53if (wordAtPosition && (false === seedSearchStringFromNonEmptySelection)) {54return wordAtPosition.word;55}56} else {57if (editor.getModel().getValueLengthInRange(selection) < SEARCH_STRING_MAX_LENGTH) {58return editor.getModel().getValueInRange(selection);59}60}61}6263return null;64}6566export const enum FindStartFocusAction {67NoFocusChange,68FocusFindInput,69FocusReplaceInput70}7172export interface IFindStartOptions {73forceRevealReplace: boolean;74seedSearchStringFromSelection: 'none' | 'single' | 'multiple';75seedSearchStringFromNonEmptySelection: boolean;76seedSearchStringFromGlobalClipboard: boolean;77shouldFocus: FindStartFocusAction;78shouldAnimate: boolean;79updateSearchScope: boolean;80loop: boolean;81}8283export interface IFindStartArguments {84searchString?: string;85replaceString?: string;86isRegex?: boolean;87matchWholeWord?: boolean;88isCaseSensitive?: boolean;89preserveCase?: boolean;90findInSelection?: boolean;91}9293export class CommonFindController extends Disposable implements IEditorContribution {9495public static readonly ID = 'editor.contrib.findController';9697protected _editor: ICodeEditor;98private readonly _findWidgetVisible: IContextKey<boolean>;99protected _state: FindReplaceState;100protected _updateHistoryDelayer: Delayer<void>;101private _model: FindModelBoundToEditorModel | null;102protected readonly _storageService: IStorageService;103private readonly _clipboardService: IClipboardService;104protected readonly _contextKeyService: IContextKeyService;105protected readonly _notificationService: INotificationService;106protected readonly _hoverService: IHoverService;107108get editor() {109return this._editor;110}111112public static get(editor: ICodeEditor): CommonFindController | null {113return editor.getContribution<CommonFindController>(CommonFindController.ID);114}115116constructor(117editor: ICodeEditor,118@IContextKeyService contextKeyService: IContextKeyService,119@IStorageService storageService: IStorageService,120@IClipboardService clipboardService: IClipboardService,121@INotificationService notificationService: INotificationService,122@IHoverService hoverService: IHoverService123) {124super();125this._editor = editor;126this._findWidgetVisible = CONTEXT_FIND_WIDGET_VISIBLE.bindTo(contextKeyService);127this._contextKeyService = contextKeyService;128this._storageService = storageService;129this._clipboardService = clipboardService;130this._notificationService = notificationService;131this._hoverService = hoverService;132133this._updateHistoryDelayer = this._register(new Delayer<void>(500));134this._state = this._register(new FindReplaceState());135this.loadQueryState();136this._register(this._state.onFindReplaceStateChange((e) => this._onStateChanged(e)));137138this._model = null;139140this._register(this._editor.onDidChangeModel(() => {141const shouldRestartFind = (this._editor.getModel() && this._state.isRevealed);142143this.disposeModel();144145this._state.change({146searchScope: null,147matchCase: this._storageService.getBoolean('editor.matchCase', StorageScope.WORKSPACE, false),148wholeWord: this._storageService.getBoolean('editor.wholeWord', StorageScope.WORKSPACE, false),149isRegex: this._storageService.getBoolean('editor.isRegex', StorageScope.WORKSPACE, false),150preserveCase: this._storageService.getBoolean('editor.preserveCase', StorageScope.WORKSPACE, false)151}, false);152153if (shouldRestartFind) {154this._start({155forceRevealReplace: false,156seedSearchStringFromSelection: 'none',157seedSearchStringFromNonEmptySelection: false,158seedSearchStringFromGlobalClipboard: false,159shouldFocus: FindStartFocusAction.NoFocusChange,160shouldAnimate: false,161updateSearchScope: false,162loop: this._editor.getOption(EditorOption.find).loop163});164}165}));166}167168public override dispose(): void {169this.disposeModel();170super.dispose();171}172173private disposeModel(): void {174if (this._model) {175this._model.dispose();176this._model = null;177}178}179180private _onStateChanged(e: FindReplaceStateChangedEvent): void {181this.saveQueryState(e);182183if (e.isRevealed) {184if (this._state.isRevealed) {185this._findWidgetVisible.set(true);186} else {187this._findWidgetVisible.reset();188this.disposeModel();189}190}191if (e.searchString) {192this.setGlobalBufferTerm(this._state.searchString);193}194}195196private saveQueryState(e: FindReplaceStateChangedEvent) {197if (e.isRegex) {198this._storageService.store('editor.isRegex', this._state.actualIsRegex, StorageScope.WORKSPACE, StorageTarget.MACHINE);199}200if (e.wholeWord) {201this._storageService.store('editor.wholeWord', this._state.actualWholeWord, StorageScope.WORKSPACE, StorageTarget.MACHINE);202}203if (e.matchCase) {204this._storageService.store('editor.matchCase', this._state.actualMatchCase, StorageScope.WORKSPACE, StorageTarget.MACHINE);205}206if (e.preserveCase) {207this._storageService.store('editor.preserveCase', this._state.actualPreserveCase, StorageScope.WORKSPACE, StorageTarget.MACHINE);208}209}210211private loadQueryState() {212this._state.change({213matchCase: this._storageService.getBoolean('editor.matchCase', StorageScope.WORKSPACE, this._state.matchCase),214wholeWord: this._storageService.getBoolean('editor.wholeWord', StorageScope.WORKSPACE, this._state.wholeWord),215isRegex: this._storageService.getBoolean('editor.isRegex', StorageScope.WORKSPACE, this._state.isRegex),216preserveCase: this._storageService.getBoolean('editor.preserveCase', StorageScope.WORKSPACE, this._state.preserveCase)217}, false);218}219220public isFindInputFocused(): boolean {221return !!CONTEXT_FIND_INPUT_FOCUSED.getValue(this._contextKeyService);222}223224/**225* Returns whether the Replace input was the last focused input in the find widget.226* Returns false by default; overridden in FindController.227*/228public wasReplaceInputLastFocused(): boolean {229return false;230}231232/**233* Focuses the last focused element in the find widget.234* Implemented by FindController; base implementation does nothing.235*/236public focusLastElement(): void {237// Base implementation - overridden in FindController238}239240public getState(): FindReplaceState {241return this._state;242}243244public closeFindWidget(): void {245this._state.change({246isRevealed: false,247searchScope: null248}, false);249this._editor.focus();250}251252public toggleCaseSensitive(): void {253this._state.change({ matchCase: !this._state.matchCase }, false);254if (!this._state.isRevealed) {255this.highlightFindOptions();256}257}258259public toggleWholeWords(): void {260this._state.change({ wholeWord: !this._state.wholeWord }, false);261if (!this._state.isRevealed) {262this.highlightFindOptions();263}264}265266public toggleRegex(): void {267this._state.change({ isRegex: !this._state.isRegex }, false);268if (!this._state.isRevealed) {269this.highlightFindOptions();270}271}272273public togglePreserveCase(): void {274this._state.change({ preserveCase: !this._state.preserveCase }, false);275if (!this._state.isRevealed) {276this.highlightFindOptions();277}278}279280public toggleSearchScope(): void {281if (this._state.searchScope) {282this._state.change({ searchScope: null }, true);283} else {284if (this._editor.hasModel()) {285let selections = this._editor.getSelections();286selections = selections.map(selection => {287if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) {288selection = selection.setEndPosition(289selection.endLineNumber - 1,290this._editor.getModel()!.getLineMaxColumn(selection.endLineNumber - 1)291);292}293if (!selection.isEmpty()) {294return selection;295}296return null;297}).filter((element): element is Selection => !!element);298299if (selections.length) {300this._state.change({ searchScope: selections }, true);301}302}303}304}305306public setSearchString(searchString: string): void {307if (this._state.isRegex) {308searchString = strings.escapeRegExpCharacters(searchString);309}310this._state.change({ searchString: searchString }, false);311}312313public highlightFindOptions(ignoreWhenVisible: boolean = false): void {314// overwritten in subclass315}316317protected async _start(opts: IFindStartOptions, newState?: INewFindReplaceState): Promise<void> {318this.disposeModel();319320if (!this._editor.hasModel()) {321// cannot do anything with an editor that doesn't have a model...322return;323}324325const stateChanges: INewFindReplaceState = {326...newState,327isRevealed: true328};329330if (opts.seedSearchStringFromSelection === 'single') {331const selectionSearchString = getSelectionSearchString(this._editor, opts.seedSearchStringFromSelection, opts.seedSearchStringFromNonEmptySelection);332if (selectionSearchString) {333if (this._state.isRegex) {334stateChanges.searchString = strings.escapeRegExpCharacters(selectionSearchString);335} else {336stateChanges.searchString = selectionSearchString;337}338}339} else if (opts.seedSearchStringFromSelection === 'multiple' && !opts.updateSearchScope) {340const selectionSearchString = getSelectionSearchString(this._editor, opts.seedSearchStringFromSelection);341if (selectionSearchString) {342stateChanges.searchString = selectionSearchString;343}344}345346if (!stateChanges.searchString && opts.seedSearchStringFromGlobalClipboard) {347const selectionSearchString = await this.getGlobalBufferTerm();348349if (!this._editor.hasModel()) {350// the editor has lost its model in the meantime351return;352}353354if (selectionSearchString) {355stateChanges.searchString = selectionSearchString;356}357}358359// Overwrite isReplaceRevealed360if (opts.forceRevealReplace || stateChanges.isReplaceRevealed) {361stateChanges.isReplaceRevealed = true;362} else if (!this._findWidgetVisible.get()) {363stateChanges.isReplaceRevealed = false;364}365366if (opts.updateSearchScope) {367const currentSelections = this._editor.getSelections();368if (currentSelections.some(selection => !selection.isEmpty())) {369stateChanges.searchScope = currentSelections;370}371}372373stateChanges.loop = opts.loop;374375this._state.change(stateChanges, false);376377if (!this._model) {378this._model = new FindModelBoundToEditorModel(this._editor, this._state);379}380}381382public start(opts: IFindStartOptions, newState?: INewFindReplaceState): Promise<void> {383return this._start(opts, newState);384}385386public moveToNextMatch(): boolean {387if (this._model) {388this._model.moveToNextMatch();389return true;390}391return false;392}393394public moveToPrevMatch(): boolean {395if (this._model) {396this._model.moveToPrevMatch();397return true;398}399return false;400}401402public goToMatch(index: number): boolean {403if (this._model) {404this._model.moveToMatch(index);405return true;406}407return false;408}409410public replace(): boolean {411if (this._model) {412this._model.replace();413return true;414}415return false;416}417418public replaceAll(): boolean {419if (this._model) {420if (this._editor.getModel()?.isTooLargeForHeapOperation()) {421this._notificationService.warn(nls.localize('too.large.for.replaceall', "The file is too large to perform a replace all operation."));422return false;423}424this._model.replaceAll();425return true;426}427return false;428}429430public selectAllMatches(): boolean {431if (this._model) {432this._model.selectAllMatches();433this._editor.focus();434return true;435}436return false;437}438439public async getGlobalBufferTerm(): Promise<string> {440if (this._editor.getOption(EditorOption.find).globalFindClipboard441&& this._editor.hasModel()442&& !this._editor.getModel().isTooLargeForSyncing()443) {444return this._clipboardService.readFindText();445}446return '';447}448449public setGlobalBufferTerm(text: string): void {450if (this._editor.getOption(EditorOption.find).globalFindClipboard451&& this._editor.hasModel()452&& !this._editor.getModel().isTooLargeForSyncing()453) {454// intentionally not awaited455this._clipboardService.writeFindText(text);456}457}458}459460export class FindController extends CommonFindController implements IFindController {461462private _widget: FindWidget | null;463private _findOptionsWidget: FindOptionsWidget | null;464private _findWidgetSearchHistory: FindWidgetSearchHistory;465private _replaceWidgetHistory: ReplaceWidgetHistory;466467constructor(468editor: ICodeEditor,469@IContextViewService private readonly _contextViewService: IContextViewService,470@IContextKeyService _contextKeyService: IContextKeyService,471@IKeybindingService private readonly _keybindingService: IKeybindingService,472@INotificationService notificationService: INotificationService,473@IStorageService _storageService: IStorageService,474@IClipboardService clipboardService: IClipboardService,475@IHoverService hoverService: IHoverService,476@IConfigurationService private readonly _configurationService: IConfigurationService,477@IAccessibilityService private readonly _accessibilityService: IAccessibilityService,478) {479super(editor, _contextKeyService, _storageService, clipboardService, notificationService, hoverService);480this._widget = null;481this._findOptionsWidget = null;482this._findWidgetSearchHistory = FindWidgetSearchHistory.getOrCreate(_storageService);483this._replaceWidgetHistory = ReplaceWidgetHistory.getOrCreate(_storageService);484}485486protected override async _start(opts: IFindStartOptions, newState?: INewFindReplaceState): Promise<void> {487if (!this._widget) {488this._createFindWidget();489}490491const selection = this._editor.getSelection();492let updateSearchScope = false;493494switch (this._editor.getOption(EditorOption.find).autoFindInSelection) {495case 'always':496updateSearchScope = true;497break;498case 'never':499updateSearchScope = false;500break;501case 'multiline': {502const isSelectionMultipleLine = !!selection && selection.startLineNumber !== selection.endLineNumber;503updateSearchScope = isSelectionMultipleLine;504break;505}506default:507break;508}509510opts.updateSearchScope = opts.updateSearchScope || updateSearchScope;511512await super._start(opts, newState);513514if (this._widget) {515if (opts.shouldFocus === FindStartFocusAction.FocusReplaceInput) {516this._widget.focusReplaceInput();517} else if (opts.shouldFocus === FindStartFocusAction.FocusFindInput) {518this._widget.focusFindInput();519}520}521}522523public override highlightFindOptions(ignoreWhenVisible: boolean = false): void {524if (!this._widget) {525this._createFindWidget();526}527if (this._state.isRevealed && !ignoreWhenVisible) {528this._widget!.highlightFindOptions();529} else {530this._findOptionsWidget!.highlightFindOptions();531}532}533534private _createFindWidget() {535this._widget = this._register(new FindWidget(this._editor, this, this._state, this._contextViewService, this._keybindingService, this._contextKeyService, this._hoverService, this._findWidgetSearchHistory, this._replaceWidgetHistory, this._configurationService, this._accessibilityService));536this._findOptionsWidget = this._register(new FindOptionsWidget(this._editor, this._state, this._keybindingService));537}538539/**540* Returns whether the Replace input was the last focused input in the find widget.541*/542public override wasReplaceInputLastFocused(): boolean {543return this._widget?.lastFocusedInputWasReplace ?? false;544}545546/**547* Focuses the last focused element in the find widget.548* This is more precise than just focusing the Find or Replace input,549* as it can restore focus to checkboxes, buttons, etc.550*/551public override focusLastElement(): void {552this._widget?.focusLastElement();553}554555saveViewState(): any {556return this._widget?.getViewState();557}558559restoreViewState(state: any): void {560this._widget?.setViewState(state);561}562}563564export const StartFindAction = registerMultiEditorAction(new MultiEditorAction({565id: FIND_IDS.StartFindAction,566label: nls.localize2('startFindAction', "Find"),567precondition: ContextKeyExpr.or(EditorContextKeys.focus, ContextKeyExpr.has('editorIsOpen')),568kbOpts: {569kbExpr: null,570primary: KeyMod.CtrlCmd | KeyCode.KeyF,571weight: KeybindingWeight.EditorContrib572},573menuOpts: {574menuId: MenuId.MenubarEditMenu,575group: '3_find',576title: nls.localize({ key: 'miFind', comment: ['&& denotes a mnemonic'] }, "&&Find"),577order: 1578}579}));580581StartFindAction.addImplementation(0, (accessor: ServicesAccessor, editor: ICodeEditor, args: any): boolean | Promise<void> => {582const controller = CommonFindController.get(editor);583if (!controller) {584return false;585}586return controller.start({587forceRevealReplace: false,588seedSearchStringFromSelection: editor.getOption(EditorOption.find).seedSearchStringFromSelection !== 'never' ? 'single' : 'none',589seedSearchStringFromNonEmptySelection: editor.getOption(EditorOption.find).seedSearchStringFromSelection === 'selection',590seedSearchStringFromGlobalClipboard: editor.getOption(EditorOption.find).globalFindClipboard,591shouldFocus: FindStartFocusAction.FocusFindInput,592shouldAnimate: true,593updateSearchScope: false,594loop: editor.getOption(EditorOption.find).loop595});596});597598const findArgDescription = {599description: 'Open a new In-Editor Find Widget.',600args: [{601name: 'Open a new In-Editor Find Widget args',602schema: {603properties: {604searchString: { type: 'string' },605replaceString: { type: 'string' },606isRegex: { type: 'boolean' },607matchWholeWord: { type: 'boolean' },608isCaseSensitive: { type: 'boolean' },609preserveCase: { type: 'boolean' },610findInSelection: { type: 'boolean' },611}612}613}]614} as const;615616export class StartFindWithArgsAction extends EditorAction {617618constructor() {619super({620id: FIND_IDS.StartFindWithArgs,621label: nls.localize2('startFindWithArgsAction', "Find with Arguments"),622precondition: undefined,623kbOpts: {624kbExpr: null,625primary: 0,626weight: KeybindingWeight.EditorContrib627},628metadata: findArgDescription629});630}631632public async run(accessor: ServicesAccessor, editor: ICodeEditor, args?: IFindStartArguments): Promise<void> {633const controller = CommonFindController.get(editor);634if (controller) {635const newState: INewFindReplaceState = args ? {636searchString: args.searchString,637replaceString: args.replaceString,638isReplaceRevealed: args.replaceString !== undefined,639isRegex: args.isRegex,640// isRegexOverride: args.regexOverride,641wholeWord: args.matchWholeWord,642// wholeWordOverride: args.wholeWordOverride,643matchCase: args.isCaseSensitive,644// matchCaseOverride: args.matchCaseOverride,645preserveCase: args.preserveCase,646// preserveCaseOverride: args.preserveCaseOverride,647} : {};648649await controller.start({650forceRevealReplace: false,651seedSearchStringFromSelection: (controller.getState().searchString.length === 0) && editor.getOption(EditorOption.find).seedSearchStringFromSelection !== 'never' ? 'single' : 'none',652seedSearchStringFromNonEmptySelection: editor.getOption(EditorOption.find).seedSearchStringFromSelection === 'selection',653seedSearchStringFromGlobalClipboard: true,654shouldFocus: FindStartFocusAction.FocusFindInput,655shouldAnimate: true,656updateSearchScope: args?.findInSelection || false,657loop: editor.getOption(EditorOption.find).loop658}, newState);659660controller.setGlobalBufferTerm(controller.getState().searchString);661}662}663}664665export class StartFindWithSelectionAction extends EditorAction {666667constructor() {668super({669id: FIND_IDS.StartFindWithSelection,670label: nls.localize2('startFindWithSelectionAction', "Find with Selection"),671precondition: undefined,672kbOpts: {673kbExpr: null,674primary: 0,675mac: {676primary: KeyMod.CtrlCmd | KeyCode.KeyE,677},678weight: KeybindingWeight.EditorContrib679}680});681}682683public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {684const controller = CommonFindController.get(editor);685if (controller) {686await controller.start({687forceRevealReplace: false,688seedSearchStringFromSelection: 'multiple',689seedSearchStringFromNonEmptySelection: false,690seedSearchStringFromGlobalClipboard: false,691shouldFocus: FindStartFocusAction.NoFocusChange,692shouldAnimate: true,693updateSearchScope: false,694loop: editor.getOption(EditorOption.find).loop695});696697controller.setGlobalBufferTerm(controller.getState().searchString);698}699}700}701export abstract class MatchFindAction extends EditorAction {702public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {703const controller = CommonFindController.get(editor);704if (controller && !this._run(controller)) {705await controller.start({706forceRevealReplace: false,707seedSearchStringFromSelection: (controller.getState().searchString.length === 0) && editor.getOption(EditorOption.find).seedSearchStringFromSelection !== 'never' ? 'single' : 'none',708seedSearchStringFromNonEmptySelection: editor.getOption(EditorOption.find).seedSearchStringFromSelection === 'selection',709seedSearchStringFromGlobalClipboard: true,710shouldFocus: FindStartFocusAction.NoFocusChange,711shouldAnimate: true,712updateSearchScope: false,713loop: editor.getOption(EditorOption.find).loop714});715this._run(controller);716}717}718719protected abstract _run(controller: CommonFindController): boolean;720}721722async function matchFindAction(editor: ICodeEditor, next: boolean): Promise<void> {723const controller = CommonFindController.get(editor);724if (!controller) {725return;726}727728const runMatch = (): boolean => {729const result = next ? controller.moveToNextMatch() : controller.moveToPrevMatch();730if (result) {731controller.editor.pushUndoStop();732return true;733}734return false;735};736737if (!runMatch()) {738await controller.start({739forceRevealReplace: false,740seedSearchStringFromSelection: (controller.getState().searchString.length === 0) && editor.getOption(EditorOption.find).seedSearchStringFromSelection !== 'never' ? 'single' : 'none',741seedSearchStringFromNonEmptySelection: editor.getOption(EditorOption.find).seedSearchStringFromSelection === 'selection',742seedSearchStringFromGlobalClipboard: true,743shouldFocus: FindStartFocusAction.NoFocusChange,744shouldAnimate: true,745updateSearchScope: false,746loop: editor.getOption(EditorOption.find).loop747});748runMatch();749}750}751752export const NextMatchFindAction = registerMultiEditorAction(new MultiEditorAction({753id: FIND_IDS.NextMatchFindAction,754label: nls.localize2('findNextMatchAction', "Find Next"),755precondition: undefined,756kbOpts: [{757kbExpr: EditorContextKeys.focus,758primary: KeyCode.F3,759mac: { primary: KeyMod.CtrlCmd | KeyCode.KeyG, secondary: [KeyCode.F3] },760weight: KeybindingWeight.EditorContrib761}, {762kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, CONTEXT_FIND_INPUT_FOCUSED),763primary: KeyCode.Enter,764weight: KeybindingWeight.EditorContrib765}]766}));767768NextMatchFindAction.addImplementation(0, async (accessor: ServicesAccessor, editor: ICodeEditor, args: any): Promise<void> => {769return matchFindAction(editor, true);770});771772773export const PreviousMatchFindAction = registerMultiEditorAction(new MultiEditorAction({774id: FIND_IDS.PreviousMatchFindAction,775label: nls.localize2('findPreviousMatchAction', "Find Previous"),776precondition: undefined,777kbOpts: [{778kbExpr: EditorContextKeys.focus,779primary: KeyMod.Shift | KeyCode.F3,780mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyG, secondary: [KeyMod.Shift | KeyCode.F3] },781weight: KeybindingWeight.EditorContrib782}, {783kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, CONTEXT_FIND_INPUT_FOCUSED),784primary: KeyMod.Shift | KeyCode.Enter,785weight: KeybindingWeight.EditorContrib786}]787}));788789PreviousMatchFindAction.addImplementation(0, async (accessor: ServicesAccessor, editor: ICodeEditor, args: any): Promise<void> => {790return matchFindAction(editor, false);791});792793export class MoveToMatchFindAction extends EditorAction {794795private _highlightDecorations: string[] = [];796constructor() {797super({798id: FIND_IDS.GoToMatchFindAction,799label: nls.localize2('findMatchAction.goToMatch', "Go to Match..."),800precondition: CONTEXT_FIND_WIDGET_VISIBLE801});802}803804public run(accessor: ServicesAccessor, editor: ICodeEditor): void | Promise<void> {805const controller = CommonFindController.get(editor);806if (!controller) {807return;808}809810const matchesCount = controller.getState().matchesCount;811if (matchesCount < 1) {812const notificationService = accessor.get(INotificationService);813notificationService.notify({814severity: Severity.Warning,815message: nls.localize('findMatchAction.noResults', "No matches. Try searching for something else.")816});817return;818}819820const quickInputService = accessor.get(IQuickInputService);821const disposables = new DisposableStore();822const inputBox = disposables.add(quickInputService.createInputBox());823inputBox.placeholder = nls.localize('findMatchAction.inputPlaceHolder', "Type a number to go to a specific match (between 1 and {0})", matchesCount);824825const toFindMatchIndex = (value: string): number | undefined => {826const index = parseInt(value);827if (isNaN(index)) {828return undefined;829}830831const matchCount = controller.getState().matchesCount;832if (index > 0 && index <= matchCount) {833return index - 1; // zero based834} else if (index < 0 && index >= -matchCount) {835return matchCount + index;836}837838return undefined;839};840841const updatePickerAndEditor = (value: string) => {842const index = toFindMatchIndex(value);843if (typeof index === 'number') {844// valid845inputBox.validationMessage = undefined;846controller.goToMatch(index);847const currentMatch = controller.getState().currentMatch;848if (currentMatch) {849this.addDecorations(editor, currentMatch);850}851} else {852inputBox.validationMessage = nls.localize('findMatchAction.inputValidationMessage', "Please type a number between 1 and {0}", controller.getState().matchesCount);853this.clearDecorations(editor);854}855};856disposables.add(inputBox.onDidChangeValue(value => {857updatePickerAndEditor(value);858}));859860disposables.add(inputBox.onDidAccept(() => {861const index = toFindMatchIndex(inputBox.value);862if (typeof index === 'number') {863controller.goToMatch(index);864inputBox.hide();865} else {866inputBox.validationMessage = nls.localize('findMatchAction.inputValidationMessage', "Please type a number between 1 and {0}", controller.getState().matchesCount);867}868}));869870disposables.add(inputBox.onDidHide(() => {871this.clearDecorations(editor);872disposables.dispose();873}));874875inputBox.show();876}877878private clearDecorations(editor: ICodeEditor): void {879editor.changeDecorations(changeAccessor => {880this._highlightDecorations = changeAccessor.deltaDecorations(this._highlightDecorations, []);881});882}883884private addDecorations(editor: ICodeEditor, range: IRange): void {885editor.changeDecorations(changeAccessor => {886this._highlightDecorations = changeAccessor.deltaDecorations(this._highlightDecorations, [887{888range,889options: {890description: 'find-match-quick-access-range-highlight',891className: 'rangeHighlight',892isWholeLine: true893}894},895{896range,897options: {898description: 'find-match-quick-access-range-highlight-overview',899overviewRuler: {900color: themeColorFromId(overviewRulerRangeHighlight),901position: OverviewRulerLane.Full902}903}904}905]);906});907}908}909910export abstract class SelectionMatchFindAction extends EditorAction {911public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {912const controller = CommonFindController.get(editor);913if (!controller) {914return;915}916917const selectionSearchString = getSelectionSearchString(editor, 'single', false);918if (selectionSearchString) {919controller.setSearchString(selectionSearchString);920}921if (!this._run(controller)) {922await controller.start({923forceRevealReplace: false,924seedSearchStringFromSelection: 'none',925seedSearchStringFromNonEmptySelection: false,926seedSearchStringFromGlobalClipboard: false,927shouldFocus: FindStartFocusAction.NoFocusChange,928shouldAnimate: true,929updateSearchScope: false,930loop: editor.getOption(EditorOption.find).loop931});932this._run(controller);933}934}935936protected abstract _run(controller: CommonFindController): boolean;937}938939export class NextSelectionMatchFindAction extends SelectionMatchFindAction {940941constructor() {942super({943id: FIND_IDS.NextSelectionMatchFindAction,944label: nls.localize2('nextSelectionMatchFindAction', "Find Next Selection"),945precondition: undefined,946kbOpts: {947kbExpr: EditorContextKeys.focus,948primary: KeyMod.CtrlCmd | KeyCode.F3,949weight: KeybindingWeight.EditorContrib950}951});952}953954protected _run(controller: CommonFindController): boolean {955return controller.moveToNextMatch();956}957}958959export class PreviousSelectionMatchFindAction extends SelectionMatchFindAction {960961constructor() {962super({963id: FIND_IDS.PreviousSelectionMatchFindAction,964label: nls.localize2('previousSelectionMatchFindAction', "Find Previous Selection"),965precondition: undefined,966kbOpts: {967kbExpr: EditorContextKeys.focus,968primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.F3,969weight: KeybindingWeight.EditorContrib970}971});972}973974protected _run(controller: CommonFindController): boolean {975return controller.moveToPrevMatch();976}977}978979export const StartFindReplaceAction = registerMultiEditorAction(new MultiEditorAction({980id: FIND_IDS.StartFindReplaceAction,981label: nls.localize2('startReplace', "Replace"),982precondition: ContextKeyExpr.or(EditorContextKeys.focus, ContextKeyExpr.has('editorIsOpen')),983kbOpts: {984kbExpr: null,985primary: KeyMod.CtrlCmd | KeyCode.KeyH,986mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyF },987weight: KeybindingWeight.EditorContrib988},989menuOpts: {990menuId: MenuId.MenubarEditMenu,991group: '3_find',992title: nls.localize({ key: 'miReplace', comment: ['&& denotes a mnemonic'] }, "&&Replace"),993order: 2994}995}));996997StartFindReplaceAction.addImplementation(0, (accessor: ServicesAccessor, editor: ICodeEditor, args: any): boolean | Promise<void> => {998if (!editor.hasModel() || editor.getOption(EditorOption.readOnly)) {999return false;1000}1001const controller = CommonFindController.get(editor);1002if (!controller) {1003return false;1004}10051006const currentSelection = editor.getSelection();1007const findInputFocused = controller.isFindInputFocused();1008// we only seed search string from selection when the current selection is single line and not empty,1009// + the find input is not focused1010const seedSearchStringFromSelection = !currentSelection.isEmpty()1011&& currentSelection.startLineNumber === currentSelection.endLineNumber1012&& (editor.getOption(EditorOption.find).seedSearchStringFromSelection !== 'never')1013&& !findInputFocused;1014/*1015* if the existing search string in find widget is empty and we don't seed search string from selection, it means the Find Input is still empty, so we should focus the Find Input instead of Replace Input.10161017* findInputFocused true -> seedSearchStringFromSelection false, FocusReplaceInput1018* findInputFocused false, seedSearchStringFromSelection true FocusReplaceInput1019* findInputFocused false seedSearchStringFromSelection false FocusFindInput1020*/1021const shouldFocus = (findInputFocused || seedSearchStringFromSelection) ?1022FindStartFocusAction.FocusReplaceInput : FindStartFocusAction.FocusFindInput;10231024return controller.start({1025forceRevealReplace: true,1026seedSearchStringFromSelection: seedSearchStringFromSelection ? 'single' : 'none',1027seedSearchStringFromNonEmptySelection: editor.getOption(EditorOption.find).seedSearchStringFromSelection === 'selection',1028seedSearchStringFromGlobalClipboard: editor.getOption(EditorOption.find).seedSearchStringFromSelection !== 'never',1029shouldFocus: shouldFocus,1030shouldAnimate: true,1031updateSearchScope: false,1032loop: editor.getOption(EditorOption.find).loop1033});1034});10351036registerEditorContribution(CommonFindController.ID, FindController, EditorContributionInstantiation.Eager); // eager because it uses `saveViewState`/`restoreViewState`10371038registerEditorAction(StartFindWithArgsAction);1039registerEditorAction(StartFindWithSelectionAction);1040registerEditorAction(MoveToMatchFindAction);1041registerEditorAction(NextSelectionMatchFindAction);1042registerEditorAction(PreviousSelectionMatchFindAction);10431044const FindCommand = EditorCommand.bindToContribution<CommonFindController>(CommonFindController.get);10451046registerEditorCommand(new FindCommand({1047id: FIND_IDS.CloseFindWidgetCommand,1048precondition: CONTEXT_FIND_WIDGET_VISIBLE,1049handler: x => x.closeFindWidget(),1050kbOpts: {1051weight: KeybindingWeight.EditorContrib + 5,1052kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, ContextKeyExpr.not('isComposing')),1053primary: KeyCode.Escape,1054secondary: [KeyMod.Shift | KeyCode.Escape]1055}1056}));10571058registerEditorCommand(new FindCommand({1059id: FIND_IDS.ToggleCaseSensitiveCommand,1060precondition: undefined,1061handler: x => x.toggleCaseSensitive(),1062kbOpts: {1063weight: KeybindingWeight.EditorContrib + 5,1064kbExpr: EditorContextKeys.focus,1065primary: ToggleCaseSensitiveKeybinding.primary,1066mac: ToggleCaseSensitiveKeybinding.mac,1067win: ToggleCaseSensitiveKeybinding.win,1068linux: ToggleCaseSensitiveKeybinding.linux1069}1070}));10711072registerEditorCommand(new FindCommand({1073id: FIND_IDS.ToggleWholeWordCommand,1074precondition: undefined,1075handler: x => x.toggleWholeWords(),1076kbOpts: {1077weight: KeybindingWeight.EditorContrib + 5,1078kbExpr: EditorContextKeys.focus,1079primary: ToggleWholeWordKeybinding.primary,1080mac: ToggleWholeWordKeybinding.mac,1081win: ToggleWholeWordKeybinding.win,1082linux: ToggleWholeWordKeybinding.linux1083}1084}));10851086registerEditorCommand(new FindCommand({1087id: FIND_IDS.ToggleRegexCommand,1088precondition: undefined,1089handler: x => x.toggleRegex(),1090kbOpts: {1091weight: KeybindingWeight.EditorContrib + 5,1092kbExpr: EditorContextKeys.focus,1093primary: ToggleRegexKeybinding.primary,1094mac: ToggleRegexKeybinding.mac,1095win: ToggleRegexKeybinding.win,1096linux: ToggleRegexKeybinding.linux1097}1098}));10991100registerEditorCommand(new FindCommand({1101id: FIND_IDS.ToggleSearchScopeCommand,1102precondition: undefined,1103handler: x => x.toggleSearchScope(),1104kbOpts: {1105weight: KeybindingWeight.EditorContrib + 5,1106kbExpr: EditorContextKeys.focus,1107primary: ToggleSearchScopeKeybinding.primary,1108mac: ToggleSearchScopeKeybinding.mac,1109win: ToggleSearchScopeKeybinding.win,1110linux: ToggleSearchScopeKeybinding.linux1111}1112}));11131114registerEditorCommand(new FindCommand({1115id: FIND_IDS.TogglePreserveCaseCommand,1116precondition: undefined,1117handler: x => x.togglePreserveCase(),1118kbOpts: {1119weight: KeybindingWeight.EditorContrib + 5,1120kbExpr: EditorContextKeys.focus,1121primary: TogglePreserveCaseKeybinding.primary,1122mac: TogglePreserveCaseKeybinding.mac,1123win: TogglePreserveCaseKeybinding.win,1124linux: TogglePreserveCaseKeybinding.linux1125}1126}));11271128registerEditorCommand(new FindCommand({1129id: FIND_IDS.ReplaceOneAction,1130precondition: CONTEXT_FIND_WIDGET_VISIBLE,1131handler: x => x.replace(),1132kbOpts: {1133weight: KeybindingWeight.EditorContrib + 5,1134kbExpr: EditorContextKeys.focus,1135primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Digit11136}1137}));11381139registerEditorCommand(new FindCommand({1140id: FIND_IDS.ReplaceOneAction,1141precondition: CONTEXT_FIND_WIDGET_VISIBLE,1142handler: x => x.replace(),1143kbOpts: {1144weight: KeybindingWeight.EditorContrib + 5,1145kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, CONTEXT_REPLACE_INPUT_FOCUSED, EditorContextKeys.isComposing.negate()),1146primary: KeyCode.Enter1147}1148}));11491150registerEditorCommand(new FindCommand({1151id: FIND_IDS.ReplaceAllAction,1152precondition: CONTEXT_FIND_WIDGET_VISIBLE,1153handler: x => x.replaceAll(),1154kbOpts: {1155weight: KeybindingWeight.EditorContrib + 5,1156kbExpr: EditorContextKeys.focus,1157primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Enter1158}1159}));11601161registerEditorCommand(new FindCommand({1162id: FIND_IDS.ReplaceAllAction,1163precondition: CONTEXT_FIND_WIDGET_VISIBLE,1164handler: x => x.replaceAll(),1165kbOpts: {1166weight: KeybindingWeight.EditorContrib + 5,1167kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, CONTEXT_REPLACE_INPUT_FOCUSED),1168primary: undefined,1169mac: {1170primary: KeyMod.CtrlCmd | KeyCode.Enter,1171}1172}1173}));11741175registerEditorCommand(new FindCommand({1176id: FIND_IDS.SelectAllMatchesAction,1177precondition: CONTEXT_FIND_WIDGET_VISIBLE,1178handler: x => x.selectAllMatches(),1179kbOpts: {1180weight: KeybindingWeight.EditorContrib + 5,1181kbExpr: EditorContextKeys.focus,1182primary: KeyMod.Alt | KeyCode.Enter1183}1184}));118511861187