Path: blob/main/src/vs/editor/contrib/find/browser/findController.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 { 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';3637const SEARCH_STRING_MAX_LENGTH = 524288;3839export function getSelectionSearchString(editor: ICodeEditor, seedSearchStringFromSelection: 'single' | 'multiple' = 'single', seedSearchStringFromNonEmptySelection: boolean = false): string | null {40if (!editor.hasModel()) {41return null;42}4344const selection = editor.getSelection();45// if selection spans multiple lines, default search string to empty4647if ((seedSearchStringFromSelection === 'single' && selection.startLineNumber === selection.endLineNumber)48|| seedSearchStringFromSelection === 'multiple') {49if (selection.isEmpty()) {50const wordAtPosition = editor.getConfiguredWordAtPosition(selection.getStartPosition());51if (wordAtPosition && (false === seedSearchStringFromNonEmptySelection)) {52return wordAtPosition.word;53}54} else {55if (editor.getModel().getValueLengthInRange(selection) < SEARCH_STRING_MAX_LENGTH) {56return editor.getModel().getValueInRange(selection);57}58}59}6061return null;62}6364export const enum FindStartFocusAction {65NoFocusChange,66FocusFindInput,67FocusReplaceInput68}6970export interface IFindStartOptions {71forceRevealReplace: boolean;72seedSearchStringFromSelection: 'none' | 'single' | 'multiple';73seedSearchStringFromNonEmptySelection: boolean;74seedSearchStringFromGlobalClipboard: boolean;75shouldFocus: FindStartFocusAction;76shouldAnimate: boolean;77updateSearchScope: boolean;78loop: boolean;79}8081export interface IFindStartArguments {82searchString?: string;83replaceString?: string;84isRegex?: boolean;85matchWholeWord?: boolean;86isCaseSensitive?: boolean;87preserveCase?: boolean;88findInSelection?: boolean;89}9091export class CommonFindController extends Disposable implements IEditorContribution {9293public static readonly ID = 'editor.contrib.findController';9495protected _editor: ICodeEditor;96private readonly _findWidgetVisible: IContextKey<boolean>;97protected _state: FindReplaceState;98protected _updateHistoryDelayer: Delayer<void>;99private _model: FindModelBoundToEditorModel | null;100protected readonly _storageService: IStorageService;101private readonly _clipboardService: IClipboardService;102protected readonly _contextKeyService: IContextKeyService;103protected readonly _notificationService: INotificationService;104protected readonly _hoverService: IHoverService;105106get editor() {107return this._editor;108}109110public static get(editor: ICodeEditor): CommonFindController | null {111return editor.getContribution<CommonFindController>(CommonFindController.ID);112}113114constructor(115editor: ICodeEditor,116@IContextKeyService contextKeyService: IContextKeyService,117@IStorageService storageService: IStorageService,118@IClipboardService clipboardService: IClipboardService,119@INotificationService notificationService: INotificationService,120@IHoverService hoverService: IHoverService121) {122super();123this._editor = editor;124this._findWidgetVisible = CONTEXT_FIND_WIDGET_VISIBLE.bindTo(contextKeyService);125this._contextKeyService = contextKeyService;126this._storageService = storageService;127this._clipboardService = clipboardService;128this._notificationService = notificationService;129this._hoverService = hoverService;130131this._updateHistoryDelayer = new Delayer<void>(500);132this._state = this._register(new FindReplaceState());133this.loadQueryState();134this._register(this._state.onFindReplaceStateChange((e) => this._onStateChanged(e)));135136this._model = null;137138this._register(this._editor.onDidChangeModel(() => {139const shouldRestartFind = (this._editor.getModel() && this._state.isRevealed);140141this.disposeModel();142143this._state.change({144searchScope: null,145matchCase: this._storageService.getBoolean('editor.matchCase', StorageScope.WORKSPACE, false),146wholeWord: this._storageService.getBoolean('editor.wholeWord', StorageScope.WORKSPACE, false),147isRegex: this._storageService.getBoolean('editor.isRegex', StorageScope.WORKSPACE, false),148preserveCase: this._storageService.getBoolean('editor.preserveCase', StorageScope.WORKSPACE, false)149}, false);150151if (shouldRestartFind) {152this._start({153forceRevealReplace: false,154seedSearchStringFromSelection: 'none',155seedSearchStringFromNonEmptySelection: false,156seedSearchStringFromGlobalClipboard: false,157shouldFocus: FindStartFocusAction.NoFocusChange,158shouldAnimate: false,159updateSearchScope: false,160loop: this._editor.getOption(EditorOption.find).loop161});162}163}));164}165166public override dispose(): void {167this.disposeModel();168super.dispose();169}170171private disposeModel(): void {172if (this._model) {173this._model.dispose();174this._model = null;175}176}177178private _onStateChanged(e: FindReplaceStateChangedEvent): void {179this.saveQueryState(e);180181if (e.isRevealed) {182if (this._state.isRevealed) {183this._findWidgetVisible.set(true);184} else {185this._findWidgetVisible.reset();186this.disposeModel();187}188}189if (e.searchString) {190this.setGlobalBufferTerm(this._state.searchString);191}192}193194private saveQueryState(e: FindReplaceStateChangedEvent) {195if (e.isRegex) {196this._storageService.store('editor.isRegex', this._state.actualIsRegex, StorageScope.WORKSPACE, StorageTarget.MACHINE);197}198if (e.wholeWord) {199this._storageService.store('editor.wholeWord', this._state.actualWholeWord, StorageScope.WORKSPACE, StorageTarget.MACHINE);200}201if (e.matchCase) {202this._storageService.store('editor.matchCase', this._state.actualMatchCase, StorageScope.WORKSPACE, StorageTarget.MACHINE);203}204if (e.preserveCase) {205this._storageService.store('editor.preserveCase', this._state.actualPreserveCase, StorageScope.WORKSPACE, StorageTarget.MACHINE);206}207}208209private loadQueryState() {210this._state.change({211matchCase: this._storageService.getBoolean('editor.matchCase', StorageScope.WORKSPACE, this._state.matchCase),212wholeWord: this._storageService.getBoolean('editor.wholeWord', StorageScope.WORKSPACE, this._state.wholeWord),213isRegex: this._storageService.getBoolean('editor.isRegex', StorageScope.WORKSPACE, this._state.isRegex),214preserveCase: this._storageService.getBoolean('editor.preserveCase', StorageScope.WORKSPACE, this._state.preserveCase)215}, false);216}217218public isFindInputFocused(): boolean {219return !!CONTEXT_FIND_INPUT_FOCUSED.getValue(this._contextKeyService);220}221222public getState(): FindReplaceState {223return this._state;224}225226public closeFindWidget(): void {227this._state.change({228isRevealed: false,229searchScope: null230}, false);231this._editor.focus();232}233234public toggleCaseSensitive(): void {235this._state.change({ matchCase: !this._state.matchCase }, false);236if (!this._state.isRevealed) {237this.highlightFindOptions();238}239}240241public toggleWholeWords(): void {242this._state.change({ wholeWord: !this._state.wholeWord }, false);243if (!this._state.isRevealed) {244this.highlightFindOptions();245}246}247248public toggleRegex(): void {249this._state.change({ isRegex: !this._state.isRegex }, false);250if (!this._state.isRevealed) {251this.highlightFindOptions();252}253}254255public togglePreserveCase(): void {256this._state.change({ preserveCase: !this._state.preserveCase }, false);257if (!this._state.isRevealed) {258this.highlightFindOptions();259}260}261262public toggleSearchScope(): void {263if (this._state.searchScope) {264this._state.change({ searchScope: null }, true);265} else {266if (this._editor.hasModel()) {267let selections = this._editor.getSelections();268selections = selections.map(selection => {269if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) {270selection = selection.setEndPosition(271selection.endLineNumber - 1,272this._editor.getModel()!.getLineMaxColumn(selection.endLineNumber - 1)273);274}275if (!selection.isEmpty()) {276return selection;277}278return null;279}).filter((element): element is Selection => !!element);280281if (selections.length) {282this._state.change({ searchScope: selections }, true);283}284}285}286}287288public setSearchString(searchString: string): void {289if (this._state.isRegex) {290searchString = strings.escapeRegExpCharacters(searchString);291}292this._state.change({ searchString: searchString }, false);293}294295public highlightFindOptions(ignoreWhenVisible: boolean = false): void {296// overwritten in subclass297}298299protected async _start(opts: IFindStartOptions, newState?: INewFindReplaceState): Promise<void> {300this.disposeModel();301302if (!this._editor.hasModel()) {303// cannot do anything with an editor that doesn't have a model...304return;305}306307const stateChanges: INewFindReplaceState = {308...newState,309isRevealed: true310};311312if (opts.seedSearchStringFromSelection === 'single') {313const selectionSearchString = getSelectionSearchString(this._editor, opts.seedSearchStringFromSelection, opts.seedSearchStringFromNonEmptySelection);314if (selectionSearchString) {315if (this._state.isRegex) {316stateChanges.searchString = strings.escapeRegExpCharacters(selectionSearchString);317} else {318stateChanges.searchString = selectionSearchString;319}320}321} else if (opts.seedSearchStringFromSelection === 'multiple' && !opts.updateSearchScope) {322const selectionSearchString = getSelectionSearchString(this._editor, opts.seedSearchStringFromSelection);323if (selectionSearchString) {324stateChanges.searchString = selectionSearchString;325}326}327328if (!stateChanges.searchString && opts.seedSearchStringFromGlobalClipboard) {329const selectionSearchString = await this.getGlobalBufferTerm();330331if (!this._editor.hasModel()) {332// the editor has lost its model in the meantime333return;334}335336if (selectionSearchString) {337stateChanges.searchString = selectionSearchString;338}339}340341// Overwrite isReplaceRevealed342if (opts.forceRevealReplace || stateChanges.isReplaceRevealed) {343stateChanges.isReplaceRevealed = true;344} else if (!this._findWidgetVisible.get()) {345stateChanges.isReplaceRevealed = false;346}347348if (opts.updateSearchScope) {349const currentSelections = this._editor.getSelections();350if (currentSelections.some(selection => !selection.isEmpty())) {351stateChanges.searchScope = currentSelections;352}353}354355stateChanges.loop = opts.loop;356357this._state.change(stateChanges, false);358359if (!this._model) {360this._model = new FindModelBoundToEditorModel(this._editor, this._state);361}362}363364public start(opts: IFindStartOptions, newState?: INewFindReplaceState): Promise<void> {365return this._start(opts, newState);366}367368public moveToNextMatch(): boolean {369if (this._model) {370this._model.moveToNextMatch();371return true;372}373return false;374}375376public moveToPrevMatch(): boolean {377if (this._model) {378this._model.moveToPrevMatch();379return true;380}381return false;382}383384public goToMatch(index: number): boolean {385if (this._model) {386this._model.moveToMatch(index);387return true;388}389return false;390}391392public replace(): boolean {393if (this._model) {394this._model.replace();395return true;396}397return false;398}399400public replaceAll(): boolean {401if (this._model) {402if (this._editor.getModel()?.isTooLargeForHeapOperation()) {403this._notificationService.warn(nls.localize('too.large.for.replaceall', "The file is too large to perform a replace all operation."));404return false;405}406this._model.replaceAll();407return true;408}409return false;410}411412public selectAllMatches(): boolean {413if (this._model) {414this._model.selectAllMatches();415this._editor.focus();416return true;417}418return false;419}420421public async getGlobalBufferTerm(): Promise<string> {422if (this._editor.getOption(EditorOption.find).globalFindClipboard423&& this._editor.hasModel()424&& !this._editor.getModel().isTooLargeForSyncing()425) {426return this._clipboardService.readFindText();427}428return '';429}430431public setGlobalBufferTerm(text: string): void {432if (this._editor.getOption(EditorOption.find).globalFindClipboard433&& this._editor.hasModel()434&& !this._editor.getModel().isTooLargeForSyncing()435) {436// intentionally not awaited437this._clipboardService.writeFindText(text);438}439}440}441442export class FindController extends CommonFindController implements IFindController {443444private _widget: FindWidget | null;445private _findOptionsWidget: FindOptionsWidget | null;446private _findWidgetSearchHistory: FindWidgetSearchHistory;447private _replaceWidgetHistory: ReplaceWidgetHistory;448449constructor(450editor: ICodeEditor,451@IContextViewService private readonly _contextViewService: IContextViewService,452@IContextKeyService _contextKeyService: IContextKeyService,453@IKeybindingService private readonly _keybindingService: IKeybindingService,454@INotificationService notificationService: INotificationService,455@IStorageService _storageService: IStorageService,456@IClipboardService clipboardService: IClipboardService,457@IHoverService hoverService: IHoverService,458) {459super(editor, _contextKeyService, _storageService, clipboardService, notificationService, hoverService);460this._widget = null;461this._findOptionsWidget = null;462this._findWidgetSearchHistory = FindWidgetSearchHistory.getOrCreate(_storageService);463this._replaceWidgetHistory = ReplaceWidgetHistory.getOrCreate(_storageService);464}465466protected override async _start(opts: IFindStartOptions, newState?: INewFindReplaceState): Promise<void> {467if (!this._widget) {468this._createFindWidget();469}470471const selection = this._editor.getSelection();472let updateSearchScope = false;473474switch (this._editor.getOption(EditorOption.find).autoFindInSelection) {475case 'always':476updateSearchScope = true;477break;478case 'never':479updateSearchScope = false;480break;481case 'multiline': {482const isSelectionMultipleLine = !!selection && selection.startLineNumber !== selection.endLineNumber;483updateSearchScope = isSelectionMultipleLine;484break;485}486default:487break;488}489490opts.updateSearchScope = opts.updateSearchScope || updateSearchScope;491492await super._start(opts, newState);493494if (this._widget) {495if (opts.shouldFocus === FindStartFocusAction.FocusReplaceInput) {496this._widget.focusReplaceInput();497} else if (opts.shouldFocus === FindStartFocusAction.FocusFindInput) {498this._widget.focusFindInput();499}500}501}502503public override highlightFindOptions(ignoreWhenVisible: boolean = false): void {504if (!this._widget) {505this._createFindWidget();506}507if (this._state.isRevealed && !ignoreWhenVisible) {508this._widget!.highlightFindOptions();509} else {510this._findOptionsWidget!.highlightFindOptions();511}512}513514private _createFindWidget() {515this._widget = this._register(new FindWidget(this._editor, this, this._state, this._contextViewService, this._keybindingService, this._contextKeyService, this._hoverService, this._findWidgetSearchHistory, this._replaceWidgetHistory));516this._findOptionsWidget = this._register(new FindOptionsWidget(this._editor, this._state, this._keybindingService));517}518519saveViewState(): any {520return this._widget?.getViewState();521}522523restoreViewState(state: any): void {524this._widget?.setViewState(state);525}526}527528export const StartFindAction = registerMultiEditorAction(new MultiEditorAction({529id: FIND_IDS.StartFindAction,530label: nls.localize2('startFindAction', "Find"),531precondition: ContextKeyExpr.or(EditorContextKeys.focus, ContextKeyExpr.has('editorIsOpen')),532kbOpts: {533kbExpr: null,534primary: KeyMod.CtrlCmd | KeyCode.KeyF,535weight: KeybindingWeight.EditorContrib536},537menuOpts: {538menuId: MenuId.MenubarEditMenu,539group: '3_find',540title: nls.localize({ key: 'miFind', comment: ['&& denotes a mnemonic'] }, "&&Find"),541order: 1542}543}));544545StartFindAction.addImplementation(0, (accessor: ServicesAccessor, editor: ICodeEditor, args: any): boolean | Promise<void> => {546const controller = CommonFindController.get(editor);547if (!controller) {548return false;549}550return controller.start({551forceRevealReplace: false,552seedSearchStringFromSelection: editor.getOption(EditorOption.find).seedSearchStringFromSelection !== 'never' ? 'single' : 'none',553seedSearchStringFromNonEmptySelection: editor.getOption(EditorOption.find).seedSearchStringFromSelection === 'selection',554seedSearchStringFromGlobalClipboard: editor.getOption(EditorOption.find).globalFindClipboard,555shouldFocus: FindStartFocusAction.FocusFindInput,556shouldAnimate: true,557updateSearchScope: false,558loop: editor.getOption(EditorOption.find).loop559});560});561562const findArgDescription = {563description: 'Open a new In-Editor Find Widget.',564args: [{565name: 'Open a new In-Editor Find Widget args',566schema: {567properties: {568searchString: { type: 'string' },569replaceString: { type: 'string' },570isRegex: { type: 'boolean' },571matchWholeWord: { type: 'boolean' },572isCaseSensitive: { type: 'boolean' },573preserveCase: { type: 'boolean' },574findInSelection: { type: 'boolean' },575}576}577}]578} as const;579580export class StartFindWithArgsAction extends EditorAction {581582constructor() {583super({584id: FIND_IDS.StartFindWithArgs,585label: nls.localize2('startFindWithArgsAction', "Find with Arguments"),586precondition: undefined,587kbOpts: {588kbExpr: null,589primary: 0,590weight: KeybindingWeight.EditorContrib591},592metadata: findArgDescription593});594}595596public async run(accessor: ServicesAccessor, editor: ICodeEditor, args?: IFindStartArguments): Promise<void> {597const controller = CommonFindController.get(editor);598if (controller) {599const newState: INewFindReplaceState = args ? {600searchString: args.searchString,601replaceString: args.replaceString,602isReplaceRevealed: args.replaceString !== undefined,603isRegex: args.isRegex,604// isRegexOverride: args.regexOverride,605wholeWord: args.matchWholeWord,606// wholeWordOverride: args.wholeWordOverride,607matchCase: args.isCaseSensitive,608// matchCaseOverride: args.matchCaseOverride,609preserveCase: args.preserveCase,610// preserveCaseOverride: args.preserveCaseOverride,611} : {};612613await controller.start({614forceRevealReplace: false,615seedSearchStringFromSelection: (controller.getState().searchString.length === 0) && editor.getOption(EditorOption.find).seedSearchStringFromSelection !== 'never' ? 'single' : 'none',616seedSearchStringFromNonEmptySelection: editor.getOption(EditorOption.find).seedSearchStringFromSelection === 'selection',617seedSearchStringFromGlobalClipboard: true,618shouldFocus: FindStartFocusAction.FocusFindInput,619shouldAnimate: true,620updateSearchScope: args?.findInSelection || false,621loop: editor.getOption(EditorOption.find).loop622}, newState);623624controller.setGlobalBufferTerm(controller.getState().searchString);625}626}627}628629export class StartFindWithSelectionAction extends EditorAction {630631constructor() {632super({633id: FIND_IDS.StartFindWithSelection,634label: nls.localize2('startFindWithSelectionAction', "Find with Selection"),635precondition: undefined,636kbOpts: {637kbExpr: null,638primary: 0,639mac: {640primary: KeyMod.CtrlCmd | KeyCode.KeyE,641},642weight: KeybindingWeight.EditorContrib643}644});645}646647public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {648const controller = CommonFindController.get(editor);649if (controller) {650await controller.start({651forceRevealReplace: false,652seedSearchStringFromSelection: 'multiple',653seedSearchStringFromNonEmptySelection: false,654seedSearchStringFromGlobalClipboard: false,655shouldFocus: FindStartFocusAction.NoFocusChange,656shouldAnimate: true,657updateSearchScope: false,658loop: editor.getOption(EditorOption.find).loop659});660661controller.setGlobalBufferTerm(controller.getState().searchString);662}663}664}665export abstract class MatchFindAction extends EditorAction {666public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {667const controller = CommonFindController.get(editor);668if (controller && !this._run(controller)) {669await controller.start({670forceRevealReplace: false,671seedSearchStringFromSelection: (controller.getState().searchString.length === 0) && editor.getOption(EditorOption.find).seedSearchStringFromSelection !== 'never' ? 'single' : 'none',672seedSearchStringFromNonEmptySelection: editor.getOption(EditorOption.find).seedSearchStringFromSelection === 'selection',673seedSearchStringFromGlobalClipboard: true,674shouldFocus: FindStartFocusAction.NoFocusChange,675shouldAnimate: true,676updateSearchScope: false,677loop: editor.getOption(EditorOption.find).loop678});679this._run(controller);680}681}682683protected abstract _run(controller: CommonFindController): boolean;684}685686export class NextMatchFindAction extends MatchFindAction {687688constructor() {689super({690id: FIND_IDS.NextMatchFindAction,691label: nls.localize2('findNextMatchAction', "Find Next"),692precondition: undefined,693kbOpts: [{694kbExpr: EditorContextKeys.focus,695primary: KeyCode.F3,696mac: { primary: KeyMod.CtrlCmd | KeyCode.KeyG, secondary: [KeyCode.F3] },697weight: KeybindingWeight.EditorContrib698}, {699kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, CONTEXT_FIND_INPUT_FOCUSED),700primary: KeyCode.Enter,701weight: KeybindingWeight.EditorContrib702}]703});704}705706protected _run(controller: CommonFindController): boolean {707const result = controller.moveToNextMatch();708if (result) {709controller.editor.pushUndoStop();710return true;711}712713return false;714}715}716717718export class PreviousMatchFindAction extends MatchFindAction {719720constructor() {721super({722id: FIND_IDS.PreviousMatchFindAction,723label: nls.localize2('findPreviousMatchAction', "Find Previous"),724precondition: undefined,725kbOpts: [{726kbExpr: EditorContextKeys.focus,727primary: KeyMod.Shift | KeyCode.F3,728mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyG, secondary: [KeyMod.Shift | KeyCode.F3] },729weight: KeybindingWeight.EditorContrib730}, {731kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, CONTEXT_FIND_INPUT_FOCUSED),732primary: KeyMod.Shift | KeyCode.Enter,733weight: KeybindingWeight.EditorContrib734}735]736});737}738739protected _run(controller: CommonFindController): boolean {740return controller.moveToPrevMatch();741}742}743744export class MoveToMatchFindAction extends EditorAction {745746private _highlightDecorations: string[] = [];747constructor() {748super({749id: FIND_IDS.GoToMatchFindAction,750label: nls.localize2('findMatchAction.goToMatch', "Go to Match..."),751precondition: CONTEXT_FIND_WIDGET_VISIBLE752});753}754755public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void | Promise<void> {756const controller = CommonFindController.get(editor);757if (!controller) {758return;759}760761const matchesCount = controller.getState().matchesCount;762if (matchesCount < 1) {763const notificationService = accessor.get(INotificationService);764notificationService.notify({765severity: Severity.Warning,766message: nls.localize('findMatchAction.noResults', "No matches. Try searching for something else.")767});768return;769}770771const quickInputService = accessor.get(IQuickInputService);772const disposables = new DisposableStore();773const inputBox = disposables.add(quickInputService.createInputBox());774inputBox.placeholder = nls.localize('findMatchAction.inputPlaceHolder', "Type a number to go to a specific match (between 1 and {0})", matchesCount);775776const toFindMatchIndex = (value: string): number | undefined => {777const index = parseInt(value);778if (isNaN(index)) {779return undefined;780}781782const matchCount = controller.getState().matchesCount;783if (index > 0 && index <= matchCount) {784return index - 1; // zero based785} else if (index < 0 && index >= -matchCount) {786return matchCount + index;787}788789return undefined;790};791792const updatePickerAndEditor = (value: string) => {793const index = toFindMatchIndex(value);794if (typeof index === 'number') {795// valid796inputBox.validationMessage = undefined;797controller.goToMatch(index);798const currentMatch = controller.getState().currentMatch;799if (currentMatch) {800this.addDecorations(editor, currentMatch);801}802} else {803inputBox.validationMessage = nls.localize('findMatchAction.inputValidationMessage', "Please type a number between 1 and {0}", controller.getState().matchesCount);804this.clearDecorations(editor);805}806};807disposables.add(inputBox.onDidChangeValue(value => {808updatePickerAndEditor(value);809}));810811disposables.add(inputBox.onDidAccept(() => {812const index = toFindMatchIndex(inputBox.value);813if (typeof index === 'number') {814controller.goToMatch(index);815inputBox.hide();816} else {817inputBox.validationMessage = nls.localize('findMatchAction.inputValidationMessage', "Please type a number between 1 and {0}", controller.getState().matchesCount);818}819}));820821disposables.add(inputBox.onDidHide(() => {822this.clearDecorations(editor);823disposables.dispose();824}));825826inputBox.show();827}828829private clearDecorations(editor: ICodeEditor): void {830editor.changeDecorations(changeAccessor => {831this._highlightDecorations = changeAccessor.deltaDecorations(this._highlightDecorations, []);832});833}834835private addDecorations(editor: ICodeEditor, range: IRange): void {836editor.changeDecorations(changeAccessor => {837this._highlightDecorations = changeAccessor.deltaDecorations(this._highlightDecorations, [838{839range,840options: {841description: 'find-match-quick-access-range-highlight',842className: 'rangeHighlight',843isWholeLine: true844}845},846{847range,848options: {849description: 'find-match-quick-access-range-highlight-overview',850overviewRuler: {851color: themeColorFromId(overviewRulerRangeHighlight),852position: OverviewRulerLane.Full853}854}855}856]);857});858}859}860861export abstract class SelectionMatchFindAction extends EditorAction {862public async run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {863const controller = CommonFindController.get(editor);864if (!controller) {865return;866}867868const selectionSearchString = getSelectionSearchString(editor, 'single', false);869if (selectionSearchString) {870controller.setSearchString(selectionSearchString);871}872if (!this._run(controller)) {873await controller.start({874forceRevealReplace: false,875seedSearchStringFromSelection: 'none',876seedSearchStringFromNonEmptySelection: false,877seedSearchStringFromGlobalClipboard: false,878shouldFocus: FindStartFocusAction.NoFocusChange,879shouldAnimate: true,880updateSearchScope: false,881loop: editor.getOption(EditorOption.find).loop882});883this._run(controller);884}885}886887protected abstract _run(controller: CommonFindController): boolean;888}889890export class NextSelectionMatchFindAction extends SelectionMatchFindAction {891892constructor() {893super({894id: FIND_IDS.NextSelectionMatchFindAction,895label: nls.localize2('nextSelectionMatchFindAction', "Find Next Selection"),896precondition: undefined,897kbOpts: {898kbExpr: EditorContextKeys.focus,899primary: KeyMod.CtrlCmd | KeyCode.F3,900weight: KeybindingWeight.EditorContrib901}902});903}904905protected _run(controller: CommonFindController): boolean {906return controller.moveToNextMatch();907}908}909910export class PreviousSelectionMatchFindAction extends SelectionMatchFindAction {911912constructor() {913super({914id: FIND_IDS.PreviousSelectionMatchFindAction,915label: nls.localize2('previousSelectionMatchFindAction', "Find Previous Selection"),916precondition: undefined,917kbOpts: {918kbExpr: EditorContextKeys.focus,919primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.F3,920weight: KeybindingWeight.EditorContrib921}922});923}924925protected _run(controller: CommonFindController): boolean {926return controller.moveToPrevMatch();927}928}929930export const StartFindReplaceAction = registerMultiEditorAction(new MultiEditorAction({931id: FIND_IDS.StartFindReplaceAction,932label: nls.localize2('startReplace', "Replace"),933precondition: ContextKeyExpr.or(EditorContextKeys.focus, ContextKeyExpr.has('editorIsOpen')),934kbOpts: {935kbExpr: null,936primary: KeyMod.CtrlCmd | KeyCode.KeyH,937mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyF },938weight: KeybindingWeight.EditorContrib939},940menuOpts: {941menuId: MenuId.MenubarEditMenu,942group: '3_find',943title: nls.localize({ key: 'miReplace', comment: ['&& denotes a mnemonic'] }, "&&Replace"),944order: 2945}946}));947948StartFindReplaceAction.addImplementation(0, (accessor: ServicesAccessor, editor: ICodeEditor, args: any): boolean | Promise<void> => {949if (!editor.hasModel() || editor.getOption(EditorOption.readOnly)) {950return false;951}952const controller = CommonFindController.get(editor);953if (!controller) {954return false;955}956957const currentSelection = editor.getSelection();958const findInputFocused = controller.isFindInputFocused();959// we only seed search string from selection when the current selection is single line and not empty,960// + the find input is not focused961const seedSearchStringFromSelection = !currentSelection.isEmpty()962&& currentSelection.startLineNumber === currentSelection.endLineNumber963&& (editor.getOption(EditorOption.find).seedSearchStringFromSelection !== 'never')964&& !findInputFocused;965/*966* 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.967968* findInputFocused true -> seedSearchStringFromSelection false, FocusReplaceInput969* findInputFocused false, seedSearchStringFromSelection true FocusReplaceInput970* findInputFocused false seedSearchStringFromSelection false FocusFindInput971*/972const shouldFocus = (findInputFocused || seedSearchStringFromSelection) ?973FindStartFocusAction.FocusReplaceInput : FindStartFocusAction.FocusFindInput;974975return controller.start({976forceRevealReplace: true,977seedSearchStringFromSelection: seedSearchStringFromSelection ? 'single' : 'none',978seedSearchStringFromNonEmptySelection: editor.getOption(EditorOption.find).seedSearchStringFromSelection === 'selection',979seedSearchStringFromGlobalClipboard: editor.getOption(EditorOption.find).seedSearchStringFromSelection !== 'never',980shouldFocus: shouldFocus,981shouldAnimate: true,982updateSearchScope: false,983loop: editor.getOption(EditorOption.find).loop984});985});986987registerEditorContribution(CommonFindController.ID, FindController, EditorContributionInstantiation.Eager); // eager because it uses `saveViewState`/`restoreViewState`988989registerEditorAction(StartFindWithArgsAction);990registerEditorAction(StartFindWithSelectionAction);991registerEditorAction(NextMatchFindAction);992registerEditorAction(PreviousMatchFindAction);993registerEditorAction(MoveToMatchFindAction);994registerEditorAction(NextSelectionMatchFindAction);995registerEditorAction(PreviousSelectionMatchFindAction);996997const FindCommand = EditorCommand.bindToContribution<CommonFindController>(CommonFindController.get);998999registerEditorCommand(new FindCommand({1000id: FIND_IDS.CloseFindWidgetCommand,1001precondition: CONTEXT_FIND_WIDGET_VISIBLE,1002handler: x => x.closeFindWidget(),1003kbOpts: {1004weight: KeybindingWeight.EditorContrib + 5,1005kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, ContextKeyExpr.not('isComposing')),1006primary: KeyCode.Escape,1007secondary: [KeyMod.Shift | KeyCode.Escape]1008}1009}));10101011registerEditorCommand(new FindCommand({1012id: FIND_IDS.ToggleCaseSensitiveCommand,1013precondition: undefined,1014handler: x => x.toggleCaseSensitive(),1015kbOpts: {1016weight: KeybindingWeight.EditorContrib + 5,1017kbExpr: EditorContextKeys.focus,1018primary: ToggleCaseSensitiveKeybinding.primary,1019mac: ToggleCaseSensitiveKeybinding.mac,1020win: ToggleCaseSensitiveKeybinding.win,1021linux: ToggleCaseSensitiveKeybinding.linux1022}1023}));10241025registerEditorCommand(new FindCommand({1026id: FIND_IDS.ToggleWholeWordCommand,1027precondition: undefined,1028handler: x => x.toggleWholeWords(),1029kbOpts: {1030weight: KeybindingWeight.EditorContrib + 5,1031kbExpr: EditorContextKeys.focus,1032primary: ToggleWholeWordKeybinding.primary,1033mac: ToggleWholeWordKeybinding.mac,1034win: ToggleWholeWordKeybinding.win,1035linux: ToggleWholeWordKeybinding.linux1036}1037}));10381039registerEditorCommand(new FindCommand({1040id: FIND_IDS.ToggleRegexCommand,1041precondition: undefined,1042handler: x => x.toggleRegex(),1043kbOpts: {1044weight: KeybindingWeight.EditorContrib + 5,1045kbExpr: EditorContextKeys.focus,1046primary: ToggleRegexKeybinding.primary,1047mac: ToggleRegexKeybinding.mac,1048win: ToggleRegexKeybinding.win,1049linux: ToggleRegexKeybinding.linux1050}1051}));10521053registerEditorCommand(new FindCommand({1054id: FIND_IDS.ToggleSearchScopeCommand,1055precondition: undefined,1056handler: x => x.toggleSearchScope(),1057kbOpts: {1058weight: KeybindingWeight.EditorContrib + 5,1059kbExpr: EditorContextKeys.focus,1060primary: ToggleSearchScopeKeybinding.primary,1061mac: ToggleSearchScopeKeybinding.mac,1062win: ToggleSearchScopeKeybinding.win,1063linux: ToggleSearchScopeKeybinding.linux1064}1065}));10661067registerEditorCommand(new FindCommand({1068id: FIND_IDS.TogglePreserveCaseCommand,1069precondition: undefined,1070handler: x => x.togglePreserveCase(),1071kbOpts: {1072weight: KeybindingWeight.EditorContrib + 5,1073kbExpr: EditorContextKeys.focus,1074primary: TogglePreserveCaseKeybinding.primary,1075mac: TogglePreserveCaseKeybinding.mac,1076win: TogglePreserveCaseKeybinding.win,1077linux: TogglePreserveCaseKeybinding.linux1078}1079}));10801081registerEditorCommand(new FindCommand({1082id: FIND_IDS.ReplaceOneAction,1083precondition: CONTEXT_FIND_WIDGET_VISIBLE,1084handler: x => x.replace(),1085kbOpts: {1086weight: KeybindingWeight.EditorContrib + 5,1087kbExpr: EditorContextKeys.focus,1088primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Digit11089}1090}));10911092registerEditorCommand(new FindCommand({1093id: FIND_IDS.ReplaceOneAction,1094precondition: CONTEXT_FIND_WIDGET_VISIBLE,1095handler: x => x.replace(),1096kbOpts: {1097weight: KeybindingWeight.EditorContrib + 5,1098kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, CONTEXT_REPLACE_INPUT_FOCUSED),1099primary: KeyCode.Enter1100}1101}));11021103registerEditorCommand(new FindCommand({1104id: FIND_IDS.ReplaceAllAction,1105precondition: CONTEXT_FIND_WIDGET_VISIBLE,1106handler: x => x.replaceAll(),1107kbOpts: {1108weight: KeybindingWeight.EditorContrib + 5,1109kbExpr: EditorContextKeys.focus,1110primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Enter1111}1112}));11131114registerEditorCommand(new FindCommand({1115id: FIND_IDS.ReplaceAllAction,1116precondition: CONTEXT_FIND_WIDGET_VISIBLE,1117handler: x => x.replaceAll(),1118kbOpts: {1119weight: KeybindingWeight.EditorContrib + 5,1120kbExpr: ContextKeyExpr.and(EditorContextKeys.focus, CONTEXT_REPLACE_INPUT_FOCUSED),1121primary: undefined,1122mac: {1123primary: KeyMod.CtrlCmd | KeyCode.Enter,1124}1125}1126}));11271128registerEditorCommand(new FindCommand({1129id: FIND_IDS.SelectAllMatchesAction,1130precondition: CONTEXT_FIND_WIDGET_VISIBLE,1131handler: x => x.selectAllMatches(),1132kbOpts: {1133weight: KeybindingWeight.EditorContrib + 5,1134kbExpr: EditorContextKeys.focus,1135primary: KeyMod.Alt | KeyCode.Enter1136}1137}));113811391140