Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/contrib/find/browser/findState.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Emitter, Event } from '../../../../base/common/event.js';
7
import { Disposable } from '../../../../base/common/lifecycle.js';
8
import { Range } from '../../../common/core/range.js';
9
import { MATCHES_LIMIT } from './findModel.js';
10
11
export interface FindReplaceStateChangedEvent {
12
moveCursor: boolean;
13
updateHistory: boolean;
14
15
searchString: boolean;
16
replaceString: boolean;
17
isRevealed: boolean;
18
isReplaceRevealed: boolean;
19
isRegex: boolean;
20
wholeWord: boolean;
21
matchCase: boolean;
22
preserveCase: boolean;
23
searchScope: boolean;
24
matchesPosition: boolean;
25
matchesCount: boolean;
26
currentMatch: boolean;
27
loop: boolean;
28
isSearching: boolean;
29
filters: boolean;
30
}
31
32
export const enum FindOptionOverride {
33
NotSet = 0,
34
True = 1,
35
False = 2
36
}
37
38
export interface INewFindReplaceState<T extends { update: (value: T) => void } = { update: () => {} }> {
39
searchString?: string;
40
replaceString?: string;
41
isRevealed?: boolean;
42
isReplaceRevealed?: boolean;
43
isRegex?: boolean;
44
isRegexOverride?: FindOptionOverride;
45
wholeWord?: boolean;
46
wholeWordOverride?: FindOptionOverride;
47
matchCase?: boolean;
48
matchCaseOverride?: FindOptionOverride;
49
preserveCase?: boolean;
50
preserveCaseOverride?: FindOptionOverride;
51
searchScope?: Range[] | null;
52
loop?: boolean;
53
isSearching?: boolean;
54
filters?: T;
55
}
56
57
function effectiveOptionValue(override: FindOptionOverride, value: boolean): boolean {
58
if (override === FindOptionOverride.True) {
59
return true;
60
}
61
if (override === FindOptionOverride.False) {
62
return false;
63
}
64
return value;
65
}
66
67
export class FindReplaceState<T extends { update: (value: T) => void } = { update: () => {} }> extends Disposable {
68
private _searchString: string;
69
private _replaceString: string;
70
private _isRevealed: boolean;
71
private _isReplaceRevealed: boolean;
72
private _isRegex: boolean;
73
private _isRegexOverride: FindOptionOverride;
74
private _wholeWord: boolean;
75
private _wholeWordOverride: FindOptionOverride;
76
private _matchCase: boolean;
77
private _matchCaseOverride: FindOptionOverride;
78
private _preserveCase: boolean;
79
private _preserveCaseOverride: FindOptionOverride;
80
private _searchScope: Range[] | null;
81
private _matchesPosition: number;
82
private _matchesCount: number;
83
private _currentMatch: Range | null;
84
private _loop: boolean;
85
private _isSearching: boolean;
86
private _filters: T | null;
87
private readonly _onFindReplaceStateChange = this._register(new Emitter<FindReplaceStateChangedEvent>());
88
89
public get searchString(): string { return this._searchString; }
90
public get replaceString(): string { return this._replaceString; }
91
public get isRevealed(): boolean { return this._isRevealed; }
92
public get isReplaceRevealed(): boolean { return this._isReplaceRevealed; }
93
public get isRegex(): boolean { return effectiveOptionValue(this._isRegexOverride, this._isRegex); }
94
public get wholeWord(): boolean { return effectiveOptionValue(this._wholeWordOverride, this._wholeWord); }
95
public get matchCase(): boolean { return effectiveOptionValue(this._matchCaseOverride, this._matchCase); }
96
public get preserveCase(): boolean { return effectiveOptionValue(this._preserveCaseOverride, this._preserveCase); }
97
98
public get actualIsRegex(): boolean { return this._isRegex; }
99
public get actualWholeWord(): boolean { return this._wholeWord; }
100
public get actualMatchCase(): boolean { return this._matchCase; }
101
public get actualPreserveCase(): boolean { return this._preserveCase; }
102
103
public get searchScope(): Range[] | null { return this._searchScope; }
104
public get matchesPosition(): number { return this._matchesPosition; }
105
public get matchesCount(): number { return this._matchesCount; }
106
public get currentMatch(): Range | null { return this._currentMatch; }
107
public get isSearching(): boolean { return this._isSearching; }
108
public get filters(): T | null { return this._filters; }
109
public readonly onFindReplaceStateChange: Event<FindReplaceStateChangedEvent> = this._onFindReplaceStateChange.event;
110
111
constructor() {
112
super();
113
this._searchString = '';
114
this._replaceString = '';
115
this._isRevealed = false;
116
this._isReplaceRevealed = false;
117
this._isRegex = false;
118
this._isRegexOverride = FindOptionOverride.NotSet;
119
this._wholeWord = false;
120
this._wholeWordOverride = FindOptionOverride.NotSet;
121
this._matchCase = false;
122
this._matchCaseOverride = FindOptionOverride.NotSet;
123
this._preserveCase = false;
124
this._preserveCaseOverride = FindOptionOverride.NotSet;
125
this._searchScope = null;
126
this._matchesPosition = 0;
127
this._matchesCount = 0;
128
this._currentMatch = null;
129
this._loop = true;
130
this._isSearching = false;
131
this._filters = null;
132
}
133
134
public changeMatchInfo(matchesPosition: number, matchesCount: number, currentMatch: Range | undefined): void {
135
const changeEvent: FindReplaceStateChangedEvent = {
136
moveCursor: false,
137
updateHistory: false,
138
searchString: false,
139
replaceString: false,
140
isRevealed: false,
141
isReplaceRevealed: false,
142
isRegex: false,
143
wholeWord: false,
144
matchCase: false,
145
preserveCase: false,
146
searchScope: false,
147
matchesPosition: false,
148
matchesCount: false,
149
currentMatch: false,
150
loop: false,
151
isSearching: false,
152
filters: false
153
};
154
let somethingChanged = false;
155
156
if (matchesCount === 0) {
157
matchesPosition = 0;
158
}
159
if (matchesPosition > matchesCount) {
160
matchesPosition = matchesCount;
161
}
162
163
if (this._matchesPosition !== matchesPosition) {
164
this._matchesPosition = matchesPosition;
165
changeEvent.matchesPosition = true;
166
somethingChanged = true;
167
}
168
if (this._matchesCount !== matchesCount) {
169
this._matchesCount = matchesCount;
170
changeEvent.matchesCount = true;
171
somethingChanged = true;
172
}
173
174
if (typeof currentMatch !== 'undefined') {
175
if (!Range.equalsRange(this._currentMatch, currentMatch)) {
176
this._currentMatch = currentMatch;
177
changeEvent.currentMatch = true;
178
somethingChanged = true;
179
}
180
}
181
182
if (somethingChanged) {
183
this._onFindReplaceStateChange.fire(changeEvent);
184
}
185
}
186
187
public change(newState: INewFindReplaceState<T>, moveCursor: boolean, updateHistory: boolean = true): void {
188
const changeEvent: FindReplaceStateChangedEvent = {
189
moveCursor: moveCursor,
190
updateHistory: updateHistory,
191
searchString: false,
192
replaceString: false,
193
isRevealed: false,
194
isReplaceRevealed: false,
195
isRegex: false,
196
wholeWord: false,
197
matchCase: false,
198
preserveCase: false,
199
searchScope: false,
200
matchesPosition: false,
201
matchesCount: false,
202
currentMatch: false,
203
loop: false,
204
isSearching: false,
205
filters: false
206
};
207
let somethingChanged = false;
208
209
const oldEffectiveIsRegex = this.isRegex;
210
const oldEffectiveWholeWords = this.wholeWord;
211
const oldEffectiveMatchCase = this.matchCase;
212
const oldEffectivePreserveCase = this.preserveCase;
213
214
if (typeof newState.searchString !== 'undefined') {
215
if (this._searchString !== newState.searchString) {
216
this._searchString = newState.searchString;
217
changeEvent.searchString = true;
218
somethingChanged = true;
219
}
220
}
221
if (typeof newState.replaceString !== 'undefined') {
222
if (this._replaceString !== newState.replaceString) {
223
this._replaceString = newState.replaceString;
224
changeEvent.replaceString = true;
225
somethingChanged = true;
226
}
227
}
228
if (typeof newState.isRevealed !== 'undefined') {
229
if (this._isRevealed !== newState.isRevealed) {
230
this._isRevealed = newState.isRevealed;
231
changeEvent.isRevealed = true;
232
somethingChanged = true;
233
}
234
}
235
if (typeof newState.isReplaceRevealed !== 'undefined') {
236
if (this._isReplaceRevealed !== newState.isReplaceRevealed) {
237
this._isReplaceRevealed = newState.isReplaceRevealed;
238
changeEvent.isReplaceRevealed = true;
239
somethingChanged = true;
240
}
241
}
242
if (typeof newState.isRegex !== 'undefined') {
243
this._isRegex = newState.isRegex;
244
}
245
if (typeof newState.wholeWord !== 'undefined') {
246
this._wholeWord = newState.wholeWord;
247
}
248
if (typeof newState.matchCase !== 'undefined') {
249
this._matchCase = newState.matchCase;
250
}
251
if (typeof newState.preserveCase !== 'undefined') {
252
this._preserveCase = newState.preserveCase;
253
}
254
if (typeof newState.searchScope !== 'undefined') {
255
if (!newState.searchScope?.every((newSearchScope) => {
256
return this._searchScope?.some(existingSearchScope => {
257
return !Range.equalsRange(existingSearchScope, newSearchScope);
258
});
259
})) {
260
this._searchScope = newState.searchScope;
261
changeEvent.searchScope = true;
262
somethingChanged = true;
263
}
264
}
265
if (typeof newState.loop !== 'undefined') {
266
if (this._loop !== newState.loop) {
267
this._loop = newState.loop;
268
changeEvent.loop = true;
269
somethingChanged = true;
270
}
271
}
272
273
if (typeof newState.isSearching !== 'undefined') {
274
if (this._isSearching !== newState.isSearching) {
275
this._isSearching = newState.isSearching;
276
changeEvent.isSearching = true;
277
somethingChanged = true;
278
}
279
}
280
281
if (typeof newState.filters !== 'undefined') {
282
if (this._filters) {
283
this._filters.update(newState.filters);
284
} else {
285
this._filters = newState.filters;
286
}
287
288
changeEvent.filters = true;
289
somethingChanged = true;
290
}
291
292
// Overrides get set when they explicitly come in and get reset anytime something else changes
293
this._isRegexOverride = (typeof newState.isRegexOverride !== 'undefined' ? newState.isRegexOverride : FindOptionOverride.NotSet);
294
this._wholeWordOverride = (typeof newState.wholeWordOverride !== 'undefined' ? newState.wholeWordOverride : FindOptionOverride.NotSet);
295
this._matchCaseOverride = (typeof newState.matchCaseOverride !== 'undefined' ? newState.matchCaseOverride : FindOptionOverride.NotSet);
296
this._preserveCaseOverride = (typeof newState.preserveCaseOverride !== 'undefined' ? newState.preserveCaseOverride : FindOptionOverride.NotSet);
297
298
if (oldEffectiveIsRegex !== this.isRegex) {
299
somethingChanged = true;
300
changeEvent.isRegex = true;
301
}
302
if (oldEffectiveWholeWords !== this.wholeWord) {
303
somethingChanged = true;
304
changeEvent.wholeWord = true;
305
}
306
if (oldEffectiveMatchCase !== this.matchCase) {
307
somethingChanged = true;
308
changeEvent.matchCase = true;
309
}
310
311
if (oldEffectivePreserveCase !== this.preserveCase) {
312
somethingChanged = true;
313
changeEvent.preserveCase = true;
314
}
315
316
if (somethingChanged) {
317
this._onFindReplaceStateChange.fire(changeEvent);
318
}
319
}
320
321
public canNavigateBack(): boolean {
322
return this.canNavigateInLoop() || (this.matchesPosition !== 1);
323
}
324
325
public canNavigateForward(): boolean {
326
return this.canNavigateInLoop() || (this.matchesPosition < this.matchesCount);
327
}
328
329
private canNavigateInLoop(): boolean {
330
return this._loop || (this.matchesCount >= MATCHES_LIMIT);
331
}
332
333
}
334
335