@jupyterlab/codemirror 4.0.0-alpha.19 → 4.0.0-alpha.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/codemirror-ipythongfm.d.ts +0 -1
- package/lib/codemirror-ipythongfm.js +0 -8
- package/lib/codemirror-ipythongfm.js.map +1 -1
- package/lib/commands.d.ts +13 -0
- package/lib/commands.js +32 -0
- package/lib/commands.js.map +1 -0
- package/lib/editor.d.ts +29 -68
- package/lib/editor.js +66 -212
- package/lib/editor.js.map +1 -1
- package/lib/extension.d.ts +236 -0
- package/lib/extension.js +687 -0
- package/lib/extension.js.map +1 -0
- package/lib/extensions/customStyle.d.ts +25 -0
- package/lib/extensions/customStyle.js +51 -0
- package/lib/extensions/customStyle.js.map +1 -0
- package/lib/extensions/index.d.ts +3 -0
- package/lib/extensions/index.js +8 -0
- package/lib/extensions/index.js.map +1 -0
- package/lib/extensions/rulers.d.ts +8 -0
- package/lib/extensions/rulers.js +85 -0
- package/lib/extensions/rulers.js.map +1 -0
- package/lib/extensions/ybinding.d.ts +125 -0
- package/lib/extensions/ybinding.js +213 -0
- package/lib/extensions/ybinding.js.map +1 -0
- package/lib/factory.d.ts +15 -5
- package/lib/factory.js +45 -37
- package/lib/factory.js.map +1 -1
- package/lib/index.d.ts +7 -20
- package/lib/index.js +7 -24
- package/lib/index.js.map +1 -1
- package/lib/language.d.ts +96 -0
- package/lib/language.js +1690 -0
- package/lib/language.js.map +1 -0
- package/lib/mimetype.d.ts +3 -0
- package/lib/mimetype.js +9 -4
- package/lib/mimetype.js.map +1 -1
- package/lib/searchprovider.d.ts +90 -80
- package/lib/searchprovider.js +221 -352
- package/lib/searchprovider.js.map +1 -1
- package/lib/theme.d.ts +57 -0
- package/lib/{editortheme.js → theme.js} +57 -30
- package/lib/theme.js.map +1 -0
- package/lib/token.d.ts +375 -0
- package/lib/token.js +18 -0
- package/lib/token.js.map +1 -0
- package/package.json +28 -36
- package/src/codemirror-ipythongfm.ts +109 -0
- package/src/commands.ts +34 -0
- package/src/editor.ts +783 -0
- package/src/extension.ts +878 -0
- package/src/extensions/customStyle.ts +79 -0
- package/src/extensions/index.ts +8 -0
- package/src/extensions/rulers.ts +112 -0
- package/src/extensions/ybinding.ts +267 -0
- package/src/factory.ts +100 -0
- package/src/index.ts +17 -0
- package/src/language.ts +1729 -0
- package/src/mimetype.ts +54 -0
- package/src/searchprovider.ts +700 -0
- package/src/theme.ts +219 -0
- package/src/token.ts +436 -0
- package/src/typings.d.ts +13 -0
- package/style/index.css +0 -3
- package/style/index.js +0 -3
- package/lib/codemirror-ipython.d.ts +0 -1
- package/lib/codemirror-ipython.js +0 -12
- package/lib/codemirror-ipython.js.map +0 -1
- package/lib/editorconfiguration.d.ts +0 -149
- package/lib/editorconfiguration.js +0 -289
- package/lib/editorconfiguration.js.map +0 -1
- package/lib/editortheme.d.ts +0 -41
- package/lib/editortheme.js.map +0 -1
- package/lib/mode.d.ts +0 -88
- package/lib/mode.js +0 -1518
- package/lib/mode.js.map +0 -1
- package/lib/syntaxstatus.d.ts +0 -67
- package/lib/syntaxstatus.js +0 -146
- package/lib/syntaxstatus.js.map +0 -1
package/lib/searchprovider.js
CHANGED
|
@@ -1,426 +1,293 @@
|
|
|
1
1
|
// Copyright (c) Jupyter Development Team.
|
|
2
2
|
// Distributed under the terms of the Modified BSD License.
|
|
3
|
-
/*
|
|
4
|
-
Parts of the implementation of the search in this file were derived from
|
|
5
|
-
CodeMirror's search at:
|
|
6
|
-
https://github.com/codemirror/CodeMirror/blob/c2676685866c571a1c9c82cb25018cc08b4d42b2/addon/search/search.js
|
|
7
|
-
which is licensed with the following license:
|
|
8
|
-
|
|
9
|
-
MIT License
|
|
10
|
-
|
|
11
|
-
Copyright (C) 2017 by Marijn Haverbeke <marijnh@gmail.com> and others
|
|
12
|
-
|
|
13
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
14
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
15
|
-
in the Software without restriction, including without limitation the rights
|
|
16
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
17
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
18
|
-
furnished to do so, subject to the following conditions:
|
|
19
|
-
|
|
20
|
-
The above copyright notice and this permission notice shall be included in
|
|
21
|
-
all copies or substantial portions of the Software.
|
|
22
|
-
|
|
23
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
24
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
25
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
26
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
27
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
28
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
29
|
-
THE SOFTWARE.
|
|
30
|
-
*/
|
|
31
|
-
import { GenericSearchProvider, TextSearchEngine } from '@jupyterlab/documentsearch';
|
|
32
3
|
import { JSONExt } from '@lumino/coreutils';
|
|
33
|
-
import {
|
|
34
|
-
import { RegExpCursor } from '@codemirror/search';
|
|
35
|
-
import { Compartment, StateEffect, StateField } from '@codemirror/state';
|
|
4
|
+
import { StateEffect, StateField } from '@codemirror/state';
|
|
36
5
|
import { Decoration, EditorView } from '@codemirror/view';
|
|
6
|
+
import { GenericSearchProvider, TextSearchEngine } from '@jupyterlab/documentsearch';
|
|
7
|
+
import { Signal } from '@lumino/signaling';
|
|
37
8
|
/**
|
|
38
|
-
*
|
|
9
|
+
* Search provider for editors.
|
|
39
10
|
*/
|
|
40
|
-
export class
|
|
41
|
-
constructor(editor) {
|
|
42
|
-
/**
|
|
43
|
-
* Set to true if the widget under search is read-only, false
|
|
44
|
-
* if it is editable. Will be used to determine whether to show
|
|
45
|
-
* the replace option.
|
|
46
|
-
*/
|
|
47
|
-
this.isReadOnly = false;
|
|
48
|
-
this._matchState = {};
|
|
49
|
-
this._changed = new Signal(this);
|
|
50
|
-
this._disposed = false;
|
|
51
|
-
this.editor = editor;
|
|
52
|
-
this._highlightEffect = StateEffect.define({
|
|
53
|
-
map: (value, mapping) => ({
|
|
54
|
-
matches: value.matches.map(v => ({
|
|
55
|
-
text: v.text,
|
|
56
|
-
position: mapping.mapPos(v.position)
|
|
57
|
-
}))
|
|
58
|
-
})
|
|
59
|
-
});
|
|
60
|
-
this._highlightMark = Decoration.mark({ class: 'cm-searching' });
|
|
61
|
-
this._highlightField = StateField.define({
|
|
62
|
-
create: () => {
|
|
63
|
-
return Decoration.none;
|
|
64
|
-
},
|
|
65
|
-
update: (highlights, transaction) => {
|
|
66
|
-
highlights = highlights.map(transaction.changes);
|
|
67
|
-
for (let ef of transaction.effects) {
|
|
68
|
-
if (ef.is(this._highlightEffect)) {
|
|
69
|
-
const e = ef;
|
|
70
|
-
if (e.value.matches.length) {
|
|
71
|
-
highlights = highlights.update({
|
|
72
|
-
add: e.value.matches.map(m => this._highlightMark.range(m.position, m.position + m.text.length))
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
highlights = Decoration.none;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return highlights;
|
|
81
|
-
},
|
|
82
|
-
provide: f => EditorView.decorations.from(f)
|
|
83
|
-
});
|
|
84
|
-
this._listener = new Compartment();
|
|
85
|
-
this.editor.injectExtension(this._listener.of([]));
|
|
86
|
-
}
|
|
11
|
+
export class EditorSearchProvider {
|
|
87
12
|
/**
|
|
88
|
-
*
|
|
13
|
+
* Constructor
|
|
89
14
|
*/
|
|
90
|
-
|
|
91
|
-
|
|
15
|
+
constructor() {
|
|
16
|
+
/**
|
|
17
|
+
* Current match index
|
|
18
|
+
*/
|
|
19
|
+
this.currentIndex = null;
|
|
20
|
+
/**
|
|
21
|
+
* Current search query
|
|
22
|
+
*/
|
|
23
|
+
this.query = null;
|
|
24
|
+
this._isActive = true;
|
|
25
|
+
this._isDisposed = false;
|
|
26
|
+
this._cmHandler = null;
|
|
27
|
+
this.currentIndex = null;
|
|
28
|
+
this._stateChanged = new Signal(this);
|
|
92
29
|
}
|
|
93
30
|
/**
|
|
94
|
-
*
|
|
31
|
+
* CodeMirror search highlighter
|
|
95
32
|
*/
|
|
96
|
-
|
|
97
|
-
if (this.
|
|
98
|
-
|
|
33
|
+
get cmHandler() {
|
|
34
|
+
if (!this._cmHandler) {
|
|
35
|
+
this._cmHandler = new CodeMirrorSearchHighlighter(this.editor);
|
|
99
36
|
}
|
|
100
|
-
this.
|
|
101
|
-
Signal.clearData(this);
|
|
37
|
+
return this._cmHandler;
|
|
102
38
|
}
|
|
103
39
|
/**
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
* @param query the search regular expression
|
|
40
|
+
* Changed signal to be emitted when search matches change.
|
|
107
41
|
*/
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
return Promise.resolve();
|
|
111
|
-
}
|
|
112
|
-
return this._startQuery(query);
|
|
42
|
+
get stateChanged() {
|
|
43
|
+
return this._stateChanged;
|
|
113
44
|
}
|
|
114
45
|
/**
|
|
115
|
-
*
|
|
46
|
+
* Current match index
|
|
116
47
|
*/
|
|
117
|
-
|
|
118
|
-
this.
|
|
119
|
-
}
|
|
120
|
-
async _startQuery(query, refreshOverlay = true) {
|
|
121
|
-
// no point in removing overlay in the middle of the search
|
|
122
|
-
await this.endQuery(false);
|
|
123
|
-
this._query = query;
|
|
124
|
-
this.editor.editor.dispatch({
|
|
125
|
-
effects: this._listener.reconfigure(EditorView.updateListener.of((v) => {
|
|
126
|
-
if (v.docChanged) {
|
|
127
|
-
this._onDocChanged(v);
|
|
128
|
-
}
|
|
129
|
-
}))
|
|
130
|
-
});
|
|
131
|
-
await this._setInitialMatches(query);
|
|
132
|
-
if (refreshOverlay) {
|
|
133
|
-
this._refreshOverlay();
|
|
134
|
-
}
|
|
135
|
-
const matches = this._parseMatchesFromState();
|
|
136
|
-
if (matches.length === 0) {
|
|
137
|
-
return Promise.resolve();
|
|
138
|
-
}
|
|
139
|
-
const cursorMatch = this._findNext(false);
|
|
140
|
-
const match = cursorMatch &&
|
|
141
|
-
this._matchState[cursorMatch.from.line][cursorMatch.from.ch];
|
|
142
|
-
this._currentMatch = match;
|
|
143
|
-
await this._highlightCurrentMatch(cursorMatch);
|
|
144
|
-
return Promise.resolve();
|
|
48
|
+
get currentMatchIndex() {
|
|
49
|
+
return this.isActive ? this.currentIndex : null;
|
|
145
50
|
}
|
|
146
51
|
/**
|
|
147
|
-
*
|
|
148
|
-
* in order to start a new query or refresh an existing one.
|
|
52
|
+
* Whether the cell search is active.
|
|
149
53
|
*
|
|
150
|
-
*
|
|
54
|
+
* This is used when applying search only on selected cells.
|
|
151
55
|
*/
|
|
152
|
-
|
|
153
|
-
this.
|
|
154
|
-
this._currentMatch = null;
|
|
155
|
-
const from = this.editor.getPositionAt(this.editor.state.selection.main.from);
|
|
156
|
-
const to = this.editor.getPositionAt(this.editor.state.selection.main.to);
|
|
157
|
-
// Setting a reverse selection to allow search-as-you-type to maintain the
|
|
158
|
-
// current selected match. See comment in _findNext for more details.
|
|
159
|
-
if (from !== to) {
|
|
160
|
-
this.editor.setSelection({
|
|
161
|
-
start: to,
|
|
162
|
-
end: from
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
this.editor.editor.dispatch({
|
|
166
|
-
effects: this._listener.reconfigure([])
|
|
167
|
-
});
|
|
56
|
+
get isActive() {
|
|
57
|
+
return this._isActive;
|
|
168
58
|
}
|
|
169
59
|
/**
|
|
170
|
-
*
|
|
60
|
+
* Whether the search provider is disposed or not.
|
|
171
61
|
*/
|
|
172
|
-
|
|
173
|
-
this.
|
|
174
|
-
const cursor = this.editor.getCursorPosition();
|
|
175
|
-
// Reset cursor position to remove any selection
|
|
176
|
-
this.editor.setCursorPosition(cursor);
|
|
177
|
-
return Promise.resolve();
|
|
62
|
+
get isDisposed() {
|
|
63
|
+
return this._isDisposed;
|
|
178
64
|
}
|
|
179
65
|
/**
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
* @param loop Whether to loop within the matches list.
|
|
183
|
-
*
|
|
184
|
-
* @returns A promise that resolves once the action has completed.
|
|
66
|
+
* Number of matches in the cell.
|
|
185
67
|
*/
|
|
186
|
-
|
|
187
|
-
return this.
|
|
68
|
+
get matchesCount() {
|
|
69
|
+
return this.isActive ? this.cmHandler.matches.length : 0;
|
|
188
70
|
}
|
|
189
71
|
/**
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
* @param loop Whether to loop within the matches list.
|
|
193
|
-
*
|
|
194
|
-
* @returns A promise that resolves once the action has completed.
|
|
72
|
+
* Clear currently highlighted match
|
|
195
73
|
*/
|
|
196
|
-
|
|
197
|
-
|
|
74
|
+
clearHighlight() {
|
|
75
|
+
this.currentIndex = null;
|
|
76
|
+
this.cmHandler.clearHighlight();
|
|
77
|
+
return Promise.resolve();
|
|
198
78
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Dispose the search provider
|
|
81
|
+
*/
|
|
82
|
+
dispose() {
|
|
83
|
+
if (this._isDisposed) {
|
|
204
84
|
return;
|
|
205
85
|
}
|
|
206
|
-
|
|
207
|
-
this
|
|
208
|
-
this.
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
},
|
|
214
|
-
scrollIntoView: true
|
|
215
|
-
});
|
|
216
|
-
return match;
|
|
86
|
+
this._isDisposed = true;
|
|
87
|
+
Signal.clearData(this);
|
|
88
|
+
if (this.isActive) {
|
|
89
|
+
this.endQuery().catch(reason => {
|
|
90
|
+
console.error(`Failed to end search query on cells.`, reason);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
217
93
|
}
|
|
218
94
|
/**
|
|
219
|
-
*
|
|
95
|
+
* Set `isActive` status.
|
|
220
96
|
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
97
|
+
* #### Notes
|
|
98
|
+
* It will start or end the search
|
|
223
99
|
*
|
|
224
|
-
* @
|
|
100
|
+
* @param v New value
|
|
225
101
|
*/
|
|
226
|
-
async
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
if (this.
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
return replaceOccurred;
|
|
102
|
+
async setIsActive(v) {
|
|
103
|
+
if (this._isActive !== v) {
|
|
104
|
+
this._isActive = v;
|
|
105
|
+
}
|
|
106
|
+
if (this._isActive) {
|
|
107
|
+
if (this.query !== null) {
|
|
108
|
+
await this.startQuery(this.query, this.filters);
|
|
234
109
|
}
|
|
235
|
-
const value = cursor.value;
|
|
236
|
-
replaceOccurred = true;
|
|
237
|
-
const insertText = (options === null || options === void 0 ? void 0 : options.preserveCase)
|
|
238
|
-
? GenericSearchProvider.preserveCase(this.editor.doc.sliceString(cursor.value.from, cursor.value.to), newText)
|
|
239
|
-
: newText;
|
|
240
|
-
this.editor.editor.dispatch({
|
|
241
|
-
changes: { from: value.from, to: value.to, insert: insertText }
|
|
242
|
-
});
|
|
243
110
|
}
|
|
244
|
-
|
|
245
|
-
|
|
111
|
+
else {
|
|
112
|
+
await this.endQuery();
|
|
113
|
+
}
|
|
246
114
|
}
|
|
247
115
|
/**
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
* @param newText The replacement text
|
|
116
|
+
* Initialize the search using the provided options. Should update the UI
|
|
117
|
+
* to highlight all matches and "select" the first match.
|
|
251
118
|
*
|
|
252
|
-
* @
|
|
119
|
+
* @param query A RegExp to be use to perform the search
|
|
120
|
+
* @param filters Filter parameters to pass to provider
|
|
253
121
|
*/
|
|
254
|
-
async
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
while (!cursor.done) {
|
|
262
|
-
replaceOccurred = true;
|
|
263
|
-
const insertText = (options === null || options === void 0 ? void 0 : options.preserveCase)
|
|
264
|
-
? GenericSearchProvider.preserveCase(this.editor.doc.sliceString(cursor.value.from, cursor.value.to), newText)
|
|
265
|
-
: newText;
|
|
266
|
-
changeSpec.push({
|
|
267
|
-
from: cursor.value.from,
|
|
268
|
-
to: cursor.value.to,
|
|
269
|
-
insert: insertText
|
|
270
|
-
});
|
|
271
|
-
cursor = cursor.next();
|
|
272
|
-
}
|
|
273
|
-
this.editor.editor.dispatch({ changes: changeSpec });
|
|
274
|
-
this._matchState = {};
|
|
275
|
-
this._currentMatch = null;
|
|
276
|
-
resolve(replaceOccurred);
|
|
277
|
-
});
|
|
122
|
+
async startQuery(query, filters) {
|
|
123
|
+
this.query = query;
|
|
124
|
+
this.filters = filters;
|
|
125
|
+
// Search input
|
|
126
|
+
const content = this.model.sharedModel.getSource();
|
|
127
|
+
await this.updateCodeMirror(content);
|
|
128
|
+
this.model.sharedModel.changed.connect(this.onSharedModelChanged, this);
|
|
278
129
|
}
|
|
279
130
|
/**
|
|
280
|
-
*
|
|
131
|
+
* Stop the search and clean any UI elements.
|
|
281
132
|
*/
|
|
282
|
-
|
|
283
|
-
|
|
133
|
+
async endQuery() {
|
|
134
|
+
await this.cmHandler.endQuery();
|
|
135
|
+
this.currentIndex = null;
|
|
284
136
|
}
|
|
285
137
|
/**
|
|
286
|
-
*
|
|
138
|
+
* Highlight the next match.
|
|
139
|
+
*
|
|
140
|
+
* @returns The next match if there is one.
|
|
287
141
|
*/
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
142
|
+
async highlightNext(loop = true) {
|
|
143
|
+
if (this.matchesCount === 0 || !this.isActive) {
|
|
144
|
+
this.currentIndex = null;
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
// This starts from the cursor position
|
|
148
|
+
let match = await this.cmHandler.highlightNext();
|
|
149
|
+
if (match) {
|
|
150
|
+
this.currentIndex = this.cmHandler.currentIndex;
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
this.currentIndex = loop ? 0 : null;
|
|
154
|
+
}
|
|
155
|
+
return match;
|
|
292
156
|
}
|
|
293
|
-
return
|
|
157
|
+
return Promise.resolve(this.getCurrentMatch());
|
|
294
158
|
}
|
|
295
159
|
/**
|
|
296
|
-
*
|
|
160
|
+
* Highlight the previous match.
|
|
161
|
+
*
|
|
162
|
+
* @returns The previous match if there is one.
|
|
297
163
|
*/
|
|
298
|
-
|
|
299
|
-
|
|
164
|
+
async highlightPrevious(loop = true) {
|
|
165
|
+
if (this.matchesCount === 0 || !this.isActive) {
|
|
166
|
+
this.currentIndex = null;
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
// This starts from the cursor position
|
|
170
|
+
let match = await this.cmHandler.highlightPrevious();
|
|
171
|
+
if (match) {
|
|
172
|
+
this.currentIndex = this.cmHandler.currentIndex;
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
this.currentIndex = loop ? this.matchesCount - 1 : null;
|
|
176
|
+
}
|
|
177
|
+
return match;
|
|
178
|
+
}
|
|
179
|
+
return Promise.resolve(this.getCurrentMatch());
|
|
300
180
|
}
|
|
301
181
|
/**
|
|
302
|
-
*
|
|
182
|
+
* Replace the currently selected match with the provided text.
|
|
183
|
+
*
|
|
184
|
+
* If no match is selected, it won't do anything.
|
|
185
|
+
*
|
|
186
|
+
* @param newText The replacement text.
|
|
187
|
+
* @returns Whether a replace occurred.
|
|
303
188
|
*/
|
|
304
|
-
|
|
305
|
-
|
|
189
|
+
replaceCurrentMatch(newText, loop, options) {
|
|
190
|
+
if (!this.isActive) {
|
|
191
|
+
return Promise.resolve(false);
|
|
192
|
+
}
|
|
193
|
+
let occurred = false;
|
|
194
|
+
if (this.currentIndex !== null &&
|
|
195
|
+
this.currentIndex < this.cmHandler.matches.length) {
|
|
196
|
+
const editor = this.editor;
|
|
197
|
+
const selection = editor.state.sliceDoc(editor.state.selection.main.from, editor.state.selection.main.to);
|
|
198
|
+
const match = this.getCurrentMatch();
|
|
199
|
+
// If cursor is not on a selection, highlight the next match
|
|
200
|
+
if (selection !== (match === null || match === void 0 ? void 0 : match.text)) {
|
|
201
|
+
this.currentIndex = null;
|
|
202
|
+
// The next will be highlighted as a consequence of this returning false
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
this.cmHandler.matches.splice(this.currentIndex, 1);
|
|
206
|
+
this.currentIndex = null;
|
|
207
|
+
const substitutedText = (options === null || options === void 0 ? void 0 : options.regularExpression)
|
|
208
|
+
? match.text.replace(this.query, newText)
|
|
209
|
+
: newText;
|
|
210
|
+
const insertText = (options === null || options === void 0 ? void 0 : options.preserveCase)
|
|
211
|
+
? GenericSearchProvider.preserveCase(match.text, substitutedText)
|
|
212
|
+
: substitutedText;
|
|
213
|
+
this.model.sharedModel.updateSource(match.position, match.position + match.text.length, insertText);
|
|
214
|
+
occurred = true;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return Promise.resolve(occurred);
|
|
306
218
|
}
|
|
307
219
|
/**
|
|
308
|
-
*
|
|
220
|
+
* Replace all matches in the cell source with the provided text
|
|
221
|
+
*
|
|
222
|
+
* @param newText The replacement text.
|
|
223
|
+
* @returns Whether a replace occurred.
|
|
309
224
|
*/
|
|
310
|
-
|
|
311
|
-
if (!this.
|
|
312
|
-
return
|
|
313
|
-
}
|
|
314
|
-
// TODO make it more efficient
|
|
315
|
-
return this.matches.indexOf(this._currentMatch);
|
|
316
|
-
}
|
|
317
|
-
_onDocChanged(update) {
|
|
318
|
-
// TODO: CM6 migration
|
|
319
|
-
if (update.changes.length) {
|
|
320
|
-
this._setInitialMatches(this._query)
|
|
321
|
-
.then(() => {
|
|
322
|
-
this._changed.emit(undefined);
|
|
323
|
-
})
|
|
324
|
-
.catch(reason => {
|
|
325
|
-
console.error(`Fail to reapply search on CodeMirror document change:\n${reason}`);
|
|
326
|
-
});
|
|
225
|
+
replaceAllMatches(newText, options) {
|
|
226
|
+
if (!this.isActive) {
|
|
227
|
+
return Promise.resolve(false);
|
|
327
228
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
let
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
229
|
+
let occurred = this.cmHandler.matches.length > 0;
|
|
230
|
+
let src = this.model.sharedModel.getSource();
|
|
231
|
+
let lastEnd = 0;
|
|
232
|
+
const finalSrc = this.cmHandler.matches.reduce((agg, match) => {
|
|
233
|
+
const start = match.position;
|
|
234
|
+
const end = start + match.text.length;
|
|
235
|
+
const substitutedText = (options === null || options === void 0 ? void 0 : options.regularExpression)
|
|
236
|
+
? match.text.replace(this.query, newText)
|
|
237
|
+
: newText;
|
|
238
|
+
const insertText = (options === null || options === void 0 ? void 0 : options.preserveCase)
|
|
239
|
+
? GenericSearchProvider.preserveCase(match.text, substitutedText)
|
|
240
|
+
: substitutedText;
|
|
241
|
+
const newStep = `${agg}${src.slice(lastEnd, start)}${insertText}`;
|
|
242
|
+
lastEnd = end;
|
|
243
|
+
return newStep;
|
|
244
|
+
}, '');
|
|
245
|
+
if (occurred) {
|
|
246
|
+
this.cmHandler.matches = [];
|
|
247
|
+
this.currentIndex = null;
|
|
248
|
+
this.model.sharedModel.setSource(`${finalSrc}${src.slice(lastEnd)}`);
|
|
335
249
|
}
|
|
336
|
-
|
|
337
|
-
this._changed.emit(undefined);
|
|
250
|
+
return Promise.resolve(occurred);
|
|
338
251
|
}
|
|
339
252
|
/**
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
* This manually constructs the initial match state across the whole
|
|
343
|
-
* document. This must be done manually because the codemirror overlay
|
|
344
|
-
* is lazy-loaded, so it will only tokenize lines that are in or near
|
|
345
|
-
* the viewport. This is sufficient for efficiently maintaining the
|
|
346
|
-
* state when changes are made to the document, as changes occur in or
|
|
347
|
-
* near the viewport, but to scan the whole document, a manual search
|
|
348
|
-
* across the entire content is required.
|
|
253
|
+
* Get the current match if it exists.
|
|
349
254
|
*
|
|
350
|
-
* @
|
|
255
|
+
* @returns The current match
|
|
351
256
|
*/
|
|
352
|
-
|
|
353
|
-
this.
|
|
354
|
-
|
|
355
|
-
const matches = await TextSearchEngine.search(query, content);
|
|
356
|
-
matches.forEach(match => {
|
|
357
|
-
const { line, ch } = this._getPosition(match.position);
|
|
358
|
-
if (!this._matchState[line]) {
|
|
359
|
-
this._matchState[line] = {};
|
|
360
|
-
}
|
|
361
|
-
this._matchState[line][ch] = match;
|
|
362
|
-
});
|
|
363
|
-
}
|
|
364
|
-
_findNext(reverse) {
|
|
365
|
-
const caseSensitive = this._query.ignoreCase;
|
|
366
|
-
const state = this.editor.state;
|
|
367
|
-
// In order to support search-as-you-type, we needed a way to allow the first
|
|
368
|
-
// match to be selected when a search is started, but prevent the selected
|
|
369
|
-
// search to move for each new keypress. To do this, when a search is ended,
|
|
370
|
-
// the cursor is reversed, putting the head at the 'from' position. When a new
|
|
371
|
-
// search is started, the cursor we want is at the 'from' position, so that the same
|
|
372
|
-
// match is selected when the next key is entered (if it is still a match).
|
|
373
|
-
//
|
|
374
|
-
// When toggling through a search normally, the cursor is always set in the forward
|
|
375
|
-
// direction, so head is always at the 'to' position. That way, if reverse = false,
|
|
376
|
-
// the search proceeds from the 'to' position during normal toggling. If reverse = true,
|
|
377
|
-
// the search always proceeds from the 'anchor' position, which is at the 'from'.
|
|
378
|
-
const lastPosition = reverse
|
|
379
|
-
? state.selection.main.anchor
|
|
380
|
-
: state.selection.main.head;
|
|
381
|
-
let cursor = new RegExpCursor(this.editor.doc, this._query.source, { ignoreCase: !caseSensitive }, lastPosition).next();
|
|
382
|
-
if (cursor.done) {
|
|
383
|
-
const startOrEnd = reverse ? this.editor.doc.length : 0;
|
|
384
|
-
cursor = new RegExpCursor(this.editor.doc, this._query.source, { ignoreCase: !caseSensitive }, startOrEnd).next();
|
|
385
|
-
if (cursor.done) {
|
|
386
|
-
return null;
|
|
387
|
-
}
|
|
257
|
+
getCurrentMatch() {
|
|
258
|
+
if (this.currentIndex === null) {
|
|
259
|
+
return undefined;
|
|
388
260
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
}
|
|
394
|
-
// TODO: remove this when Position API are removed and we work with
|
|
395
|
-
// offsets only. Contrary to the functions in CodeMirrorEditor,
|
|
396
|
-
// these functions work with CM6 position (i.e. line number starting at 1).
|
|
397
|
-
_getPosition(offset) {
|
|
398
|
-
const line = this.editor.doc.lineAt(offset);
|
|
399
|
-
return { line: line.number, ch: offset - line.from };
|
|
400
|
-
}
|
|
401
|
-
_parseMatchesFromState() {
|
|
402
|
-
// Flatten state map
|
|
403
|
-
const matches = new Array();
|
|
404
|
-
for (const lineKey in this._matchState) {
|
|
405
|
-
const lineMatches = this._matchState[lineKey];
|
|
406
|
-
for (const posKey in lineMatches) {
|
|
407
|
-
matches.push(lineMatches[posKey]);
|
|
261
|
+
else {
|
|
262
|
+
let match = undefined;
|
|
263
|
+
if (this.currentIndex < this.cmHandler.matches.length) {
|
|
264
|
+
match = this.cmHandler.matches[this.currentIndex];
|
|
408
265
|
}
|
|
266
|
+
return match;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Callback on source change
|
|
271
|
+
*
|
|
272
|
+
* @param emitter Source of the change
|
|
273
|
+
* @param changes Source change
|
|
274
|
+
*/
|
|
275
|
+
async onSharedModelChanged(emitter, changes) {
|
|
276
|
+
if (changes.sourceChange) {
|
|
277
|
+
await this.updateCodeMirror(emitter.getSource());
|
|
278
|
+
this._stateChanged.emit();
|
|
409
279
|
}
|
|
410
|
-
return matches;
|
|
411
280
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
281
|
+
/**
|
|
282
|
+
* Update matches
|
|
283
|
+
*/
|
|
284
|
+
async updateCodeMirror(content) {
|
|
285
|
+
if (this.query !== null && this.isActive) {
|
|
286
|
+
this.cmHandler.matches = await TextSearchEngine.search(this.query, content);
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
this.cmHandler.matches = [];
|
|
415
290
|
}
|
|
416
|
-
const currentSelection = this.editor.getSelection();
|
|
417
|
-
const currentSelectionLength = currentSelection.end.column - currentSelection.start.column;
|
|
418
|
-
const selectionIsOneLine = currentSelection.start.line === currentSelection.end.line;
|
|
419
|
-
return (selectionIsOneLine &&
|
|
420
|
-
this._currentMatch.text.length === currentSelectionLength &&
|
|
421
|
-
this._currentMatch.position ===
|
|
422
|
-
this.editor.doc.line(currentSelection.start.line).from +
|
|
423
|
-
currentSelection.start.column);
|
|
424
291
|
}
|
|
425
292
|
}
|
|
426
293
|
/**
|
|
@@ -459,7 +326,9 @@ export class CodeMirrorSearchHighlighter {
|
|
|
459
326
|
const e = ef;
|
|
460
327
|
if (e.value.matches.length) {
|
|
461
328
|
highlights = highlights.update({
|
|
462
|
-
add: e.value.matches.map(m => this._highlightMark.range(m.position, m.position + m.text.length))
|
|
329
|
+
add: e.value.matches.map(m => this._highlightMark.range(m.position, m.position + m.text.length)),
|
|
330
|
+
// filter out old marks
|
|
331
|
+
filter: () => false
|
|
463
332
|
});
|
|
464
333
|
}
|
|
465
334
|
else {
|