Path: blob/main/src/vs/editor/contrib/find/browser/findState.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 { Emitter, Event } from '../../../../base/common/event.js';6import { Disposable } from '../../../../base/common/lifecycle.js';7import { Range } from '../../../common/core/range.js';8import { MATCHES_LIMIT } from './findModel.js';910export interface FindReplaceStateChangedEvent {11moveCursor: boolean;12updateHistory: boolean;1314searchString: boolean;15replaceString: boolean;16isRevealed: boolean;17isReplaceRevealed: boolean;18isRegex: boolean;19wholeWord: boolean;20matchCase: boolean;21preserveCase: boolean;22searchScope: boolean;23matchesPosition: boolean;24matchesCount: boolean;25currentMatch: boolean;26loop: boolean;27isSearching: boolean;28filters: boolean;29}3031export const enum FindOptionOverride {32NotSet = 0,33True = 1,34False = 235}3637export interface INewFindReplaceState<T extends { update: (value: T) => void } = { update: () => {} }> {38searchString?: string;39replaceString?: string;40isRevealed?: boolean;41isReplaceRevealed?: boolean;42isRegex?: boolean;43isRegexOverride?: FindOptionOverride;44wholeWord?: boolean;45wholeWordOverride?: FindOptionOverride;46matchCase?: boolean;47matchCaseOverride?: FindOptionOverride;48preserveCase?: boolean;49preserveCaseOverride?: FindOptionOverride;50searchScope?: Range[] | null;51loop?: boolean;52isSearching?: boolean;53filters?: T;54}5556function effectiveOptionValue(override: FindOptionOverride, value: boolean): boolean {57if (override === FindOptionOverride.True) {58return true;59}60if (override === FindOptionOverride.False) {61return false;62}63return value;64}6566export class FindReplaceState<T extends { update: (value: T) => void } = { update: () => {} }> extends Disposable {67private _searchString: string;68private _replaceString: string;69private _isRevealed: boolean;70private _isReplaceRevealed: boolean;71private _isRegex: boolean;72private _isRegexOverride: FindOptionOverride;73private _wholeWord: boolean;74private _wholeWordOverride: FindOptionOverride;75private _matchCase: boolean;76private _matchCaseOverride: FindOptionOverride;77private _preserveCase: boolean;78private _preserveCaseOverride: FindOptionOverride;79private _searchScope: Range[] | null;80private _matchesPosition: number;81private _matchesCount: number;82private _currentMatch: Range | null;83private _loop: boolean;84private _isSearching: boolean;85private _filters: T | null;86private readonly _onFindReplaceStateChange = this._register(new Emitter<FindReplaceStateChangedEvent>());8788public get searchString(): string { return this._searchString; }89public get replaceString(): string { return this._replaceString; }90public get isRevealed(): boolean { return this._isRevealed; }91public get isReplaceRevealed(): boolean { return this._isReplaceRevealed; }92public get isRegex(): boolean { return effectiveOptionValue(this._isRegexOverride, this._isRegex); }93public get wholeWord(): boolean { return effectiveOptionValue(this._wholeWordOverride, this._wholeWord); }94public get matchCase(): boolean { return effectiveOptionValue(this._matchCaseOverride, this._matchCase); }95public get preserveCase(): boolean { return effectiveOptionValue(this._preserveCaseOverride, this._preserveCase); }9697public get actualIsRegex(): boolean { return this._isRegex; }98public get actualWholeWord(): boolean { return this._wholeWord; }99public get actualMatchCase(): boolean { return this._matchCase; }100public get actualPreserveCase(): boolean { return this._preserveCase; }101102public get searchScope(): Range[] | null { return this._searchScope; }103public get matchesPosition(): number { return this._matchesPosition; }104public get matchesCount(): number { return this._matchesCount; }105public get currentMatch(): Range | null { return this._currentMatch; }106public get isSearching(): boolean { return this._isSearching; }107public get filters(): T | null { return this._filters; }108public readonly onFindReplaceStateChange: Event<FindReplaceStateChangedEvent> = this._onFindReplaceStateChange.event;109110constructor() {111super();112this._searchString = '';113this._replaceString = '';114this._isRevealed = false;115this._isReplaceRevealed = false;116this._isRegex = false;117this._isRegexOverride = FindOptionOverride.NotSet;118this._wholeWord = false;119this._wholeWordOverride = FindOptionOverride.NotSet;120this._matchCase = false;121this._matchCaseOverride = FindOptionOverride.NotSet;122this._preserveCase = false;123this._preserveCaseOverride = FindOptionOverride.NotSet;124this._searchScope = null;125this._matchesPosition = 0;126this._matchesCount = 0;127this._currentMatch = null;128this._loop = true;129this._isSearching = false;130this._filters = null;131}132133public changeMatchInfo(matchesPosition: number, matchesCount: number, currentMatch: Range | undefined): void {134const changeEvent: FindReplaceStateChangedEvent = {135moveCursor: false,136updateHistory: false,137searchString: false,138replaceString: false,139isRevealed: false,140isReplaceRevealed: false,141isRegex: false,142wholeWord: false,143matchCase: false,144preserveCase: false,145searchScope: false,146matchesPosition: false,147matchesCount: false,148currentMatch: false,149loop: false,150isSearching: false,151filters: false152};153let somethingChanged = false;154155if (matchesCount === 0) {156matchesPosition = 0;157}158if (matchesPosition > matchesCount) {159matchesPosition = matchesCount;160}161162if (this._matchesPosition !== matchesPosition) {163this._matchesPosition = matchesPosition;164changeEvent.matchesPosition = true;165somethingChanged = true;166}167if (this._matchesCount !== matchesCount) {168this._matchesCount = matchesCount;169changeEvent.matchesCount = true;170somethingChanged = true;171}172173if (typeof currentMatch !== 'undefined') {174if (!Range.equalsRange(this._currentMatch, currentMatch)) {175this._currentMatch = currentMatch;176changeEvent.currentMatch = true;177somethingChanged = true;178}179}180181if (somethingChanged) {182this._onFindReplaceStateChange.fire(changeEvent);183}184}185186public change(newState: INewFindReplaceState<T>, moveCursor: boolean, updateHistory: boolean = true): void {187const changeEvent: FindReplaceStateChangedEvent = {188moveCursor: moveCursor,189updateHistory: updateHistory,190searchString: false,191replaceString: false,192isRevealed: false,193isReplaceRevealed: false,194isRegex: false,195wholeWord: false,196matchCase: false,197preserveCase: false,198searchScope: false,199matchesPosition: false,200matchesCount: false,201currentMatch: false,202loop: false,203isSearching: false,204filters: false205};206let somethingChanged = false;207208const oldEffectiveIsRegex = this.isRegex;209const oldEffectiveWholeWords = this.wholeWord;210const oldEffectiveMatchCase = this.matchCase;211const oldEffectivePreserveCase = this.preserveCase;212213if (typeof newState.searchString !== 'undefined') {214if (this._searchString !== newState.searchString) {215this._searchString = newState.searchString;216changeEvent.searchString = true;217somethingChanged = true;218}219}220if (typeof newState.replaceString !== 'undefined') {221if (this._replaceString !== newState.replaceString) {222this._replaceString = newState.replaceString;223changeEvent.replaceString = true;224somethingChanged = true;225}226}227if (typeof newState.isRevealed !== 'undefined') {228if (this._isRevealed !== newState.isRevealed) {229this._isRevealed = newState.isRevealed;230changeEvent.isRevealed = true;231somethingChanged = true;232}233}234if (typeof newState.isReplaceRevealed !== 'undefined') {235if (this._isReplaceRevealed !== newState.isReplaceRevealed) {236this._isReplaceRevealed = newState.isReplaceRevealed;237changeEvent.isReplaceRevealed = true;238somethingChanged = true;239}240}241if (typeof newState.isRegex !== 'undefined') {242this._isRegex = newState.isRegex;243}244if (typeof newState.wholeWord !== 'undefined') {245this._wholeWord = newState.wholeWord;246}247if (typeof newState.matchCase !== 'undefined') {248this._matchCase = newState.matchCase;249}250if (typeof newState.preserveCase !== 'undefined') {251this._preserveCase = newState.preserveCase;252}253if (typeof newState.searchScope !== 'undefined') {254if (!newState.searchScope?.every((newSearchScope) => {255return this._searchScope?.some(existingSearchScope => {256return !Range.equalsRange(existingSearchScope, newSearchScope);257});258})) {259this._searchScope = newState.searchScope;260changeEvent.searchScope = true;261somethingChanged = true;262}263}264if (typeof newState.loop !== 'undefined') {265if (this._loop !== newState.loop) {266this._loop = newState.loop;267changeEvent.loop = true;268somethingChanged = true;269}270}271272if (typeof newState.isSearching !== 'undefined') {273if (this._isSearching !== newState.isSearching) {274this._isSearching = newState.isSearching;275changeEvent.isSearching = true;276somethingChanged = true;277}278}279280if (typeof newState.filters !== 'undefined') {281if (this._filters) {282this._filters.update(newState.filters);283} else {284this._filters = newState.filters;285}286287changeEvent.filters = true;288somethingChanged = true;289}290291// Overrides get set when they explicitly come in and get reset anytime something else changes292this._isRegexOverride = (typeof newState.isRegexOverride !== 'undefined' ? newState.isRegexOverride : FindOptionOverride.NotSet);293this._wholeWordOverride = (typeof newState.wholeWordOverride !== 'undefined' ? newState.wholeWordOverride : FindOptionOverride.NotSet);294this._matchCaseOverride = (typeof newState.matchCaseOverride !== 'undefined' ? newState.matchCaseOverride : FindOptionOverride.NotSet);295this._preserveCaseOverride = (typeof newState.preserveCaseOverride !== 'undefined' ? newState.preserveCaseOverride : FindOptionOverride.NotSet);296297if (oldEffectiveIsRegex !== this.isRegex) {298somethingChanged = true;299changeEvent.isRegex = true;300}301if (oldEffectiveWholeWords !== this.wholeWord) {302somethingChanged = true;303changeEvent.wholeWord = true;304}305if (oldEffectiveMatchCase !== this.matchCase) {306somethingChanged = true;307changeEvent.matchCase = true;308}309310if (oldEffectivePreserveCase !== this.preserveCase) {311somethingChanged = true;312changeEvent.preserveCase = true;313}314315if (somethingChanged) {316this._onFindReplaceStateChange.fire(changeEvent);317}318}319320public canNavigateBack(): boolean {321return this.canNavigateInLoop() || (this.matchesPosition !== 1);322}323324public canNavigateForward(): boolean {325return this.canNavigateInLoop() || (this.matchesPosition < this.matchesCount);326}327328private canNavigateInLoop(): boolean {329return this._loop || (this.matchesCount >= MATCHES_LIMIT);330}331332}333334335