@jupyterlab/codemirror 4.0.0-alpha.10 → 4.0.0-alpha.13
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-ipython.d.ts +1 -2
- package/lib/codemirror-ipython.js +7 -25
- package/lib/codemirror-ipython.js.map +1 -1
- package/lib/codemirror-ipythongfm.d.ts +10 -3
- package/lib/codemirror-ipythongfm.js +60 -30
- package/lib/codemirror-ipythongfm.js.map +1 -1
- package/lib/editor.d.ts +50 -199
- package/lib/editor.js +385 -626
- package/lib/editor.js.map +1 -1
- package/lib/editorconfiguration.d.ts +149 -0
- package/lib/editorconfiguration.js +286 -0
- package/lib/editorconfiguration.js.map +1 -0
- package/lib/editortheme.d.ts +41 -0
- package/lib/editortheme.js +167 -0
- package/lib/editortheme.js.map +1 -0
- package/lib/factory.js +22 -19
- package/lib/factory.js.map +1 -1
- package/lib/index.d.ts +2 -2
- package/lib/index.js +2 -2
- package/lib/index.js.map +1 -1
- package/lib/mimetype.js +9 -3
- package/lib/mimetype.js.map +1 -1
- package/lib/mode.d.ts +61 -55
- package/lib/mode.js +1472 -106
- package/lib/mode.js.map +1 -1
- package/lib/searchprovider.d.ts +11 -11
- package/lib/searchprovider.js +230 -267
- package/lib/searchprovider.js.map +1 -1
- package/lib/syntaxstatus.js +9 -3
- package/lib/syntaxstatus.js.map +1 -1
- package/package.json +49 -33
- package/style/base.css +13 -183
- package/style/index.css +0 -3
- package/style/index.js +0 -3
- package/lib/tokens.d.ts +0 -18
- package/lib/tokens.js +0 -8
- package/lib/tokens.js.map +0 -1
- package/typings/codemirror/codemirror.d.ts +0 -227
package/lib/searchprovider.js
CHANGED
|
@@ -1,14 +1,44 @@
|
|
|
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
|
+
*/
|
|
3
31
|
import { TextSearchEngine } from '@jupyterlab/documentsearch';
|
|
4
32
|
import { JSONExt } from '@lumino/coreutils';
|
|
5
33
|
import { Signal } from '@lumino/signaling';
|
|
6
|
-
import
|
|
34
|
+
import { RegExpCursor } from '@codemirror/search';
|
|
35
|
+
import { Compartment, StateEffect, StateField } from '@codemirror/state';
|
|
36
|
+
import { Decoration, EditorView } from '@codemirror/view';
|
|
7
37
|
/**
|
|
8
38
|
* CodeMirror search provider for file editor
|
|
9
39
|
*/
|
|
10
40
|
export class CodeMirrorSearchProvider {
|
|
11
|
-
constructor() {
|
|
41
|
+
constructor(editor) {
|
|
12
42
|
/**
|
|
13
43
|
* Set to true if the widget under search is read-only, false
|
|
14
44
|
* if it is editable. Will be used to determine whether to show
|
|
@@ -18,6 +48,41 @@ export class CodeMirrorSearchProvider {
|
|
|
18
48
|
this._matchState = {};
|
|
19
49
|
this._changed = new Signal(this);
|
|
20
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([]));
|
|
21
86
|
}
|
|
22
87
|
/**
|
|
23
88
|
* Whether the search provider is disposed or not.
|
|
@@ -56,11 +121,17 @@ export class CodeMirrorSearchProvider {
|
|
|
56
121
|
// no point in removing overlay in the middle of the search
|
|
57
122
|
await this.endQuery(false);
|
|
58
123
|
this._query = query;
|
|
59
|
-
|
|
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);
|
|
60
132
|
if (refreshOverlay) {
|
|
61
133
|
this._refreshOverlay();
|
|
62
134
|
}
|
|
63
|
-
await this._setInitialMatches(query);
|
|
64
135
|
const matches = this._parseMatchesFromState();
|
|
65
136
|
if (matches.length === 0) {
|
|
66
137
|
return Promise.resolve();
|
|
@@ -69,6 +140,7 @@ export class CodeMirrorSearchProvider {
|
|
|
69
140
|
const match = cursorMatch &&
|
|
70
141
|
this._matchState[cursorMatch.from.line][cursorMatch.from.ch];
|
|
71
142
|
this._currentMatch = match;
|
|
143
|
+
await this._highlightCurrentMatch(cursorMatch);
|
|
72
144
|
return Promise.resolve();
|
|
73
145
|
}
|
|
74
146
|
/**
|
|
@@ -80,20 +152,19 @@ export class CodeMirrorSearchProvider {
|
|
|
80
152
|
async endQuery(removeOverlay = true) {
|
|
81
153
|
this._matchState = {};
|
|
82
154
|
this._currentMatch = null;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
const from = this.editor.getCursor('from');
|
|
87
|
-
const to = this.editor.getCursor('to');
|
|
155
|
+
const from = this.editor.getPositionAt(this.editor.state.selection.main.from);
|
|
156
|
+
const to = this.editor.getPositionAt(this.editor.state.selection.main.to);
|
|
88
157
|
// Setting a reverse selection to allow search-as-you-type to maintain the
|
|
89
158
|
// current selected match. See comment in _findNext for more details.
|
|
90
159
|
if (from !== to) {
|
|
91
160
|
this.editor.setSelection({
|
|
92
|
-
start:
|
|
93
|
-
end:
|
|
161
|
+
start: to,
|
|
162
|
+
end: from
|
|
94
163
|
});
|
|
95
164
|
}
|
|
96
|
-
|
|
165
|
+
this.editor.editor.dispatch({
|
|
166
|
+
effects: this._listener.reconfigure([])
|
|
167
|
+
});
|
|
97
168
|
}
|
|
98
169
|
/**
|
|
99
170
|
* Clear currently highlighted match.
|
|
@@ -113,13 +184,7 @@ export class CodeMirrorSearchProvider {
|
|
|
113
184
|
* @returns A promise that resolves once the action has completed.
|
|
114
185
|
*/
|
|
115
186
|
async highlightNext(loop) {
|
|
116
|
-
|
|
117
|
-
if (!cursorMatch) {
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
const match = this._matchState[cursorMatch.from.line][cursorMatch.from.ch];
|
|
121
|
-
this._currentMatch = match;
|
|
122
|
-
return match;
|
|
187
|
+
return this._highlightCurrentMatch(this._findNext(false));
|
|
123
188
|
}
|
|
124
189
|
/**
|
|
125
190
|
* Move the current match indicator to the previous match.
|
|
@@ -129,12 +194,25 @@ export class CodeMirrorSearchProvider {
|
|
|
129
194
|
* @returns A promise that resolves once the action has completed.
|
|
130
195
|
*/
|
|
131
196
|
async highlightPrevious(loop) {
|
|
132
|
-
|
|
197
|
+
return this._highlightCurrentMatch(this._findNext(true));
|
|
198
|
+
}
|
|
199
|
+
async _highlightCurrentMatch(cursorMatch) {
|
|
200
|
+
// Highlight the current index
|
|
133
201
|
if (!cursorMatch) {
|
|
202
|
+
// Set cursor to remove any selection
|
|
203
|
+
this.editor.editor.dispatch({ selection: { anchor: 0 } });
|
|
134
204
|
return;
|
|
135
205
|
}
|
|
136
206
|
const match = this._matchState[cursorMatch.from.line][cursorMatch.from.ch];
|
|
137
207
|
this._currentMatch = match;
|
|
208
|
+
this.editor.editor.focus();
|
|
209
|
+
this.editor.editor.dispatch({
|
|
210
|
+
selection: {
|
|
211
|
+
anchor: match.position,
|
|
212
|
+
head: match.position + match.text.length
|
|
213
|
+
},
|
|
214
|
+
scrollIntoView: true
|
|
215
|
+
});
|
|
138
216
|
return match;
|
|
139
217
|
}
|
|
140
218
|
/**
|
|
@@ -150,12 +228,15 @@ export class CodeMirrorSearchProvider {
|
|
|
150
228
|
// replace it. Otherwise, just select the next match after the cursor.
|
|
151
229
|
let replaceOccurred = false;
|
|
152
230
|
if (this._currentMatchIsSelected()) {
|
|
153
|
-
const cursor = this.editor.
|
|
154
|
-
if (
|
|
231
|
+
const cursor = new RegExpCursor(this.editor.doc, this._query.source, { ignoreCase: !this._query.ignoreCase }, this.editor.state.selection.main.from).next();
|
|
232
|
+
if (cursor.done) {
|
|
155
233
|
return replaceOccurred;
|
|
156
234
|
}
|
|
235
|
+
const value = cursor.value;
|
|
157
236
|
replaceOccurred = true;
|
|
158
|
-
|
|
237
|
+
this.editor.editor.dispatch({
|
|
238
|
+
changes: { from: value.from, to: value.to, insert: newText }
|
|
239
|
+
});
|
|
159
240
|
}
|
|
160
241
|
await this.highlightNext();
|
|
161
242
|
return replaceOccurred;
|
|
@@ -170,16 +251,23 @@ export class CodeMirrorSearchProvider {
|
|
|
170
251
|
async replaceAllMatches(newText) {
|
|
171
252
|
let replaceOccurred = false;
|
|
172
253
|
return new Promise((resolve, _) => {
|
|
173
|
-
this.editor.
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
254
|
+
let cursor = new RegExpCursor(this.editor.doc, this._query.source, {
|
|
255
|
+
ignoreCase: !this._query.ignoreCase
|
|
256
|
+
}).next();
|
|
257
|
+
let changeSpec = [];
|
|
258
|
+
while (!cursor.done) {
|
|
259
|
+
replaceOccurred = true;
|
|
260
|
+
changeSpec.push({
|
|
261
|
+
from: cursor.value.from,
|
|
262
|
+
to: cursor.value.to,
|
|
263
|
+
insert: newText
|
|
264
|
+
});
|
|
265
|
+
cursor = cursor.next();
|
|
266
|
+
}
|
|
267
|
+
this.editor.editor.dispatch({ changes: changeSpec });
|
|
268
|
+
this._matchState = {};
|
|
269
|
+
this._currentMatch = null;
|
|
270
|
+
resolve(replaceOccurred);
|
|
183
271
|
});
|
|
184
272
|
}
|
|
185
273
|
/**
|
|
@@ -220,11 +308,9 @@ export class CodeMirrorSearchProvider {
|
|
|
220
308
|
// TODO make it more efficient
|
|
221
309
|
return this.matches.indexOf(this._currentMatch);
|
|
222
310
|
}
|
|
223
|
-
_onDocChanged(
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
// match state are all shifted, so here we need to recalculate it
|
|
227
|
-
if (changeObj.text.length > 1 || ((_b = (_a = changeObj.removed) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) > 1) {
|
|
311
|
+
_onDocChanged(update) {
|
|
312
|
+
// TODO: CM6 migration
|
|
313
|
+
if (update.changes.length) {
|
|
228
314
|
this._setInitialMatches(this._query)
|
|
229
315
|
.then(() => {
|
|
230
316
|
this._changed.emit(undefined);
|
|
@@ -235,13 +321,14 @@ export class CodeMirrorSearchProvider {
|
|
|
235
321
|
}
|
|
236
322
|
}
|
|
237
323
|
_refreshOverlay() {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
});
|
|
324
|
+
let effects = [
|
|
325
|
+
this._highlightEffect.of({ matches: this.matches })
|
|
326
|
+
];
|
|
327
|
+
if (!this.editor.state.field(this._highlightField, false)) {
|
|
328
|
+
effects.push(StateEffect.appendConfig.of([this._highlightField]));
|
|
329
|
+
}
|
|
330
|
+
this.editor.editor.dispatch({ effects });
|
|
331
|
+
this._changed.emit(undefined);
|
|
245
332
|
}
|
|
246
333
|
/**
|
|
247
334
|
* Do a full search on the entire document.
|
|
@@ -258,125 +345,52 @@ export class CodeMirrorSearchProvider {
|
|
|
258
345
|
*/
|
|
259
346
|
async _setInitialMatches(query) {
|
|
260
347
|
this._matchState = {};
|
|
261
|
-
const content = this.editor.doc.
|
|
348
|
+
const content = this.editor.doc.toString();
|
|
262
349
|
const matches = await TextSearchEngine.search(query, content);
|
|
263
350
|
matches.forEach(match => {
|
|
264
|
-
const { line, ch } = this.
|
|
351
|
+
const { line, ch } = this._getPosition(match.position);
|
|
265
352
|
if (!this._matchState[line]) {
|
|
266
353
|
this._matchState[line] = {};
|
|
267
354
|
}
|
|
268
355
|
this._matchState[line][ch] = match;
|
|
269
356
|
});
|
|
270
357
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
this._matchState[line] = {};
|
|
295
|
-
}
|
|
296
|
-
if (match && match.index === currentPos) {
|
|
297
|
-
// found match, add it to state
|
|
298
|
-
const matchLength = match[0].length;
|
|
299
|
-
const matchObj = {
|
|
300
|
-
text: lineText.slice(currentPos, currentPos + matchLength),
|
|
301
|
-
position: this.editor.doc.indexFromPos({
|
|
302
|
-
line,
|
|
303
|
-
ch: currentPos
|
|
304
|
-
})
|
|
305
|
-
};
|
|
306
|
-
if (!this._matchState[line]) {
|
|
307
|
-
this._matchState[line] = {};
|
|
308
|
-
}
|
|
309
|
-
this._matchState[line][currentPos] = matchObj;
|
|
310
|
-
// move the stream along and return searching style for the token
|
|
311
|
-
stream.pos += matchLength || 1;
|
|
312
|
-
// if the last thing on the line was a match, make sure we still
|
|
313
|
-
// emit the changed signal so the display can pick up the updates
|
|
314
|
-
if (stream.eol()) {
|
|
315
|
-
this._changed.emit(undefined);
|
|
316
|
-
}
|
|
317
|
-
return 'searching';
|
|
318
|
-
}
|
|
319
|
-
else if (match) {
|
|
320
|
-
// there's a match in the stream, advance the stream to its position
|
|
321
|
-
stream.pos = match.index;
|
|
322
|
-
}
|
|
323
|
-
else {
|
|
324
|
-
// no matches, consume the rest of the stream
|
|
325
|
-
this._changed.emit(undefined);
|
|
326
|
-
stream.skipToEnd();
|
|
327
|
-
}
|
|
358
|
+
_findNext(reverse) {
|
|
359
|
+
const caseSensitive = this._query.ignoreCase;
|
|
360
|
+
const state = this.editor.state;
|
|
361
|
+
// In order to support search-as-you-type, we needed a way to allow the first
|
|
362
|
+
// match to be selected when a search is started, but prevent the selected
|
|
363
|
+
// search to move for each new keypress. To do this, when a search is ended,
|
|
364
|
+
// the cursor is reversed, putting the head at the 'from' position. When a new
|
|
365
|
+
// search is started, the cursor we want is at the 'from' position, so that the same
|
|
366
|
+
// match is selected when the next key is entered (if it is still a match).
|
|
367
|
+
//
|
|
368
|
+
// When toggling through a search normally, the cursor is always set in the forward
|
|
369
|
+
// direction, so head is always at the 'to' position. That way, if reverse = false,
|
|
370
|
+
// the search proceeds from the 'to' position during normal toggling. If reverse = true,
|
|
371
|
+
// the search always proceeds from the 'anchor' position, which is at the 'from'.
|
|
372
|
+
const lastPosition = reverse
|
|
373
|
+
? state.selection.main.anchor
|
|
374
|
+
: state.selection.main.head;
|
|
375
|
+
let cursor = new RegExpCursor(this.editor.doc, this._query.source, { ignoreCase: !caseSensitive }, lastPosition).next();
|
|
376
|
+
if (cursor.done) {
|
|
377
|
+
const startOrEnd = reverse ? this.editor.doc.length : 0;
|
|
378
|
+
cursor = new RegExpCursor(this.editor.doc, this._query.source, { ignoreCase: !caseSensitive }, startOrEnd).next();
|
|
379
|
+
if (cursor.done) {
|
|
380
|
+
return null;
|
|
328
381
|
}
|
|
382
|
+
}
|
|
383
|
+
return {
|
|
384
|
+
from: this._getPosition(cursor.value.from),
|
|
385
|
+
to: this._getPosition(cursor.value.to)
|
|
329
386
|
};
|
|
330
387
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
// the cursor is reversed, putting the head at the 'from' position. When a new
|
|
338
|
-
// search is started, the cursor we want is at the 'from' position, so that the same
|
|
339
|
-
// match is selected when the next key is entered (if it is still a match).
|
|
340
|
-
//
|
|
341
|
-
// When toggling through a search normally, the cursor is always set in the forward
|
|
342
|
-
// direction, so head is always at the 'to' position. That way, if reverse = false,
|
|
343
|
-
// the search proceeds from the 'to' position during normal toggling. If reverse = true,
|
|
344
|
-
// the search always proceeds from the 'anchor' position, which is at the 'from'.
|
|
345
|
-
const cursorToGet = reverse ? 'anchor' : 'head';
|
|
346
|
-
const lastPosition = this.editor.getCursor(cursorToGet);
|
|
347
|
-
let cursor = this.editor.getSearchCursor(this._query, lastPosition, !caseSensitive);
|
|
348
|
-
if (!cursor.find(reverse)) {
|
|
349
|
-
// if we do want to loop, try searching from the bottom/top
|
|
350
|
-
const startOrEnd = reverse
|
|
351
|
-
? CodeMirror.Pos(this.editor.lastLine())
|
|
352
|
-
: CodeMirror.Pos(this.editor.firstLine(), 0);
|
|
353
|
-
cursor = this.editor.getSearchCursor(this._query, startOrEnd, !caseSensitive);
|
|
354
|
-
if (!cursor.find(reverse)) {
|
|
355
|
-
return null;
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
const fromPos = cursor.from();
|
|
359
|
-
const toPos = cursor.to();
|
|
360
|
-
const selRange = {
|
|
361
|
-
start: {
|
|
362
|
-
line: fromPos.line,
|
|
363
|
-
column: fromPos.ch
|
|
364
|
-
},
|
|
365
|
-
end: {
|
|
366
|
-
line: toPos.line,
|
|
367
|
-
column: toPos.ch
|
|
368
|
-
}
|
|
369
|
-
};
|
|
370
|
-
this.editor.setSelection(selRange);
|
|
371
|
-
this.editor.scrollIntoView({
|
|
372
|
-
from: fromPos,
|
|
373
|
-
to: toPos
|
|
374
|
-
}, 100);
|
|
375
|
-
return {
|
|
376
|
-
from: fromPos,
|
|
377
|
-
to: toPos
|
|
378
|
-
};
|
|
379
|
-
});
|
|
388
|
+
// TODO: remove this when Position API are removed and we work with
|
|
389
|
+
// offsets only. Contrary to the functions in CodeMirrorEditor,
|
|
390
|
+
// these functions work with CM6 position (i.e. line number starting at 1).
|
|
391
|
+
_getPosition(offset) {
|
|
392
|
+
const line = this.editor.doc.lineAt(offset);
|
|
393
|
+
return { line: line.number, ch: offset - line.from };
|
|
380
394
|
}
|
|
381
395
|
_parseMatchesFromState() {
|
|
382
396
|
// Flatten state map
|
|
@@ -389,12 +403,6 @@ export class CodeMirrorSearchProvider {
|
|
|
389
403
|
}
|
|
390
404
|
return matches;
|
|
391
405
|
}
|
|
392
|
-
_toEditorPos(posIn) {
|
|
393
|
-
return {
|
|
394
|
-
line: posIn.line,
|
|
395
|
-
column: posIn.ch
|
|
396
|
-
};
|
|
397
|
-
}
|
|
398
406
|
_currentMatchIsSelected() {
|
|
399
407
|
if (!this._currentMatch) {
|
|
400
408
|
return false;
|
|
@@ -405,10 +413,8 @@ export class CodeMirrorSearchProvider {
|
|
|
405
413
|
return (selectionIsOneLine &&
|
|
406
414
|
this._currentMatch.text.length === currentSelectionLength &&
|
|
407
415
|
this._currentMatch.position ===
|
|
408
|
-
this.editor.doc.
|
|
409
|
-
|
|
410
|
-
ch: currentSelection.start.column
|
|
411
|
-
}));
|
|
416
|
+
this.editor.doc.line(currentSelection.start.line).from +
|
|
417
|
+
currentSelection.start.column);
|
|
412
418
|
}
|
|
413
419
|
}
|
|
414
420
|
/**
|
|
@@ -427,6 +433,38 @@ export class CodeMirrorSearchHighlighter {
|
|
|
427
433
|
this._cm = editor;
|
|
428
434
|
this._matches = new Array();
|
|
429
435
|
this._currentIndex = null;
|
|
436
|
+
this._highlightEffect = StateEffect.define({
|
|
437
|
+
map: (value, mapping) => ({
|
|
438
|
+
matches: value.matches.map(v => ({
|
|
439
|
+
text: v.text,
|
|
440
|
+
position: mapping.mapPos(v.position)
|
|
441
|
+
}))
|
|
442
|
+
})
|
|
443
|
+
});
|
|
444
|
+
this._highlightMark = Decoration.mark({ class: 'cm-searching' });
|
|
445
|
+
this._highlightField = StateField.define({
|
|
446
|
+
create: () => {
|
|
447
|
+
return Decoration.none;
|
|
448
|
+
},
|
|
449
|
+
update: (highlights, transaction) => {
|
|
450
|
+
highlights = highlights.map(transaction.changes);
|
|
451
|
+
for (let ef of transaction.effects) {
|
|
452
|
+
if (ef.is(this._highlightEffect)) {
|
|
453
|
+
const e = ef;
|
|
454
|
+
if (e.value.matches.length) {
|
|
455
|
+
highlights = highlights.update({
|
|
456
|
+
add: e.value.matches.map(m => this._highlightMark.range(m.position, m.position + m.text.length))
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
highlights = Decoration.none;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
return highlights;
|
|
465
|
+
},
|
|
466
|
+
provide: f => EditorView.decorations.from(f)
|
|
467
|
+
});
|
|
430
468
|
}
|
|
431
469
|
/**
|
|
432
470
|
* The current index of the selected match.
|
|
@@ -444,7 +482,7 @@ export class CodeMirrorSearchHighlighter {
|
|
|
444
482
|
if (!JSONExt.deepEqual(this._matches, v)) {
|
|
445
483
|
this._matches = v;
|
|
446
484
|
}
|
|
447
|
-
this.
|
|
485
|
+
this._refresh();
|
|
448
486
|
}
|
|
449
487
|
/**
|
|
450
488
|
* Clear all highlighted matches
|
|
@@ -453,29 +491,22 @@ export class CodeMirrorSearchHighlighter {
|
|
|
453
491
|
this._currentIndex = null;
|
|
454
492
|
this._highlightCurrentMatch();
|
|
455
493
|
}
|
|
456
|
-
/**
|
|
457
|
-
* Refresh the highlight matches overlay
|
|
458
|
-
*/
|
|
459
|
-
refresh() {
|
|
460
|
-
this._refreshOverlay();
|
|
461
|
-
}
|
|
462
494
|
/**
|
|
463
495
|
* Clear the highlighted matches.
|
|
464
496
|
*/
|
|
465
497
|
endQuery() {
|
|
466
498
|
this._currentIndex = null;
|
|
467
499
|
this._matches = [];
|
|
468
|
-
this._cm.
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
const
|
|
500
|
+
this._cm.editor.dispatch({
|
|
501
|
+
effects: this._highlightEffect.of({ matches: [] })
|
|
502
|
+
});
|
|
503
|
+
const selection = this._cm.state.selection.main;
|
|
504
|
+
const from = selection.from;
|
|
505
|
+
const to = selection.to;
|
|
472
506
|
// Setting a reverse selection to allow search-as-you-type to maintain the
|
|
473
507
|
// current selected match. See comment in _findNext for more details.
|
|
474
508
|
if (from !== to) {
|
|
475
|
-
this._cm.
|
|
476
|
-
start: this._toEditorPos(to),
|
|
477
|
-
end: this._toEditorPos(from)
|
|
478
|
-
});
|
|
509
|
+
this._cm.editor.dispatch({ selection: { anchor: to, head: from } });
|
|
479
510
|
}
|
|
480
511
|
return Promise.resolve();
|
|
481
512
|
}
|
|
@@ -507,83 +538,28 @@ export class CodeMirrorSearchHighlighter {
|
|
|
507
538
|
// Highlight the current index
|
|
508
539
|
if (this._currentIndex !== null) {
|
|
509
540
|
const match = this.matches[this._currentIndex];
|
|
510
|
-
this._cm.
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
// Matches is on the same line
|
|
518
|
-
line: start.line,
|
|
519
|
-
column: start.ch + match.text.length
|
|
520
|
-
};
|
|
521
|
-
// No need to scroll into view this is the default behavior
|
|
522
|
-
this._cm.setSelection({
|
|
523
|
-
start: from,
|
|
524
|
-
end: to
|
|
525
|
-
});
|
|
541
|
+
this._cm.editor.focus();
|
|
542
|
+
this._cm.editor.dispatch({
|
|
543
|
+
selection: {
|
|
544
|
+
anchor: match.position,
|
|
545
|
+
head: match.position + match.text.length
|
|
546
|
+
},
|
|
547
|
+
scrollIntoView: true
|
|
526
548
|
});
|
|
527
549
|
}
|
|
528
550
|
else {
|
|
529
551
|
// Set cursor to remove any selection
|
|
530
|
-
this._cm.
|
|
552
|
+
this._cm.editor.dispatch({ selection: { anchor: 0 } });
|
|
531
553
|
}
|
|
532
554
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
this._cm.
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
const position = this._cm.doc.indexFromPos({
|
|
542
|
-
line: stream.lineOracle.line,
|
|
543
|
-
ch: stream.pos
|
|
544
|
-
});
|
|
545
|
-
let found = this._matches.length > 0
|
|
546
|
-
? Utils.findNext(this._matches, position, 0, // lastMatchIndex,
|
|
547
|
-
this._matches.length - 1)
|
|
548
|
-
: null;
|
|
549
|
-
if (found !== null) {
|
|
550
|
-
// lastMatchIndex = found;
|
|
551
|
-
const match = this._matches[found];
|
|
552
|
-
if (match.position > position + stream.string.length) {
|
|
553
|
-
// next match not in this stream, consume the rest of the stream
|
|
554
|
-
stream.skipToEnd();
|
|
555
|
-
return null;
|
|
556
|
-
}
|
|
557
|
-
if (position === match.position) {
|
|
558
|
-
// move the stream along and return searching style for the token
|
|
559
|
-
stream.pos += match.text.length || 1;
|
|
560
|
-
return 'searching';
|
|
561
|
-
}
|
|
562
|
-
else {
|
|
563
|
-
// Move to the next match
|
|
564
|
-
stream.pos += match.position - position;
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
else {
|
|
568
|
-
// no matches, consume the rest of the stream
|
|
569
|
-
stream.skipToEnd();
|
|
570
|
-
}
|
|
571
|
-
return null;
|
|
572
|
-
};
|
|
573
|
-
return {
|
|
574
|
-
name: 'jp-searching',
|
|
575
|
-
/**
|
|
576
|
-
* Token function is called when a line needs to be processed -
|
|
577
|
-
* when the overlay is initially created, it's called on all lines;
|
|
578
|
-
* when a line is modified and needs to be re-evaluated, it's called
|
|
579
|
-
* on just that line.
|
|
580
|
-
*
|
|
581
|
-
* This implementation of the token function both constructs/maintains
|
|
582
|
-
* the overlay and keeps track of the match state as the document is
|
|
583
|
-
* updated while a search is active.
|
|
584
|
-
*/
|
|
585
|
-
token: token.bind(this)
|
|
586
|
-
};
|
|
555
|
+
_refresh() {
|
|
556
|
+
let effects = [
|
|
557
|
+
this._highlightEffect.of({ matches: this.matches })
|
|
558
|
+
];
|
|
559
|
+
if (!this._cm.state.field(this._highlightField, false)) {
|
|
560
|
+
effects.push(StateEffect.appendConfig.of([this._highlightField]));
|
|
561
|
+
}
|
|
562
|
+
this._cm.editor.dispatch({ effects });
|
|
587
563
|
}
|
|
588
564
|
_findNext(reverse) {
|
|
589
565
|
if (this._matches.length === 0) {
|
|
@@ -601,20 +577,13 @@ export class CodeMirrorSearchHighlighter {
|
|
|
601
577
|
// direction, so head is always at the 'to' position. That way, if reverse = false,
|
|
602
578
|
// the search proceeds from the 'to' position during normal toggling. If reverse = true,
|
|
603
579
|
// the search always proceeds from the 'anchor' position, which is at the 'from'.
|
|
604
|
-
const
|
|
605
|
-
let lastPosition =
|
|
606
|
-
if (lastPosition
|
|
607
|
-
lastPosition.ch === 0 &&
|
|
608
|
-
reverse &&
|
|
609
|
-
this.currentIndex === null) {
|
|
580
|
+
const cursor = this._cm.state.selection.main;
|
|
581
|
+
let lastPosition = reverse ? cursor.anchor : cursor.head;
|
|
582
|
+
if (lastPosition === 0 && reverse && this.currentIndex === null) {
|
|
610
583
|
// The default position is (0, 0) but we want to start from the end in that case
|
|
611
|
-
lastPosition =
|
|
612
|
-
// Go to virtual next line so position got clamp to end
|
|
613
|
-
line: this._cm.lineCount,
|
|
614
|
-
ch: 0
|
|
615
|
-
};
|
|
584
|
+
lastPosition = this._cm.doc.length;
|
|
616
585
|
}
|
|
617
|
-
const position =
|
|
586
|
+
const position = lastPosition;
|
|
618
587
|
let found = Utils.findNext(this._matches, position, 0, this._matches.length - 1);
|
|
619
588
|
if (found === null) {
|
|
620
589
|
// Don't loop
|
|
@@ -629,12 +598,6 @@ export class CodeMirrorSearchHighlighter {
|
|
|
629
598
|
}
|
|
630
599
|
return found;
|
|
631
600
|
}
|
|
632
|
-
_toEditorPos(posIn) {
|
|
633
|
-
return {
|
|
634
|
-
line: posIn.line,
|
|
635
|
-
column: posIn.ch
|
|
636
|
-
};
|
|
637
|
-
}
|
|
638
601
|
}
|
|
639
602
|
/**
|
|
640
603
|
* Helpers namespace
|