1 |
|
2 |
|
3 | import { Dialog, showDialog } from '@jupyterlab/apputils';
|
4 | import { CodeCell, createCellSearchProvider } from '@jupyterlab/cells';
|
5 | import { SearchProvider } from '@jupyterlab/documentsearch';
|
6 | import { nullTranslator } from '@jupyterlab/translation';
|
7 | import { ArrayExt } from '@lumino/algorithm';
|
8 | import { NotebookPanel } from './panel';
|
9 |
|
10 |
|
11 |
|
12 | export class NotebookSearchProvider extends SearchProvider {
|
13 | |
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 | constructor(widget, translator = nullTranslator) {
|
20 | super(widget);
|
21 | this.translator = translator;
|
22 | this._textSelection = null;
|
23 | this._currentProviderIndex = null;
|
24 | this._delayedActiveCellChangeHandler = null;
|
25 | this._onSelection = false;
|
26 | this._selectedCells = 1;
|
27 | this._selectedLines = 0;
|
28 | this._query = null;
|
29 | this._searchProviders = [];
|
30 | this._editorSelectionsObservable = null;
|
31 | this._selectionSearchMode = 'cells';
|
32 | this._selectionLock = false;
|
33 | this._handleHighlightsAfterActiveCellChange =
|
34 | this._handleHighlightsAfterActiveCellChange.bind(this);
|
35 | this.widget.model.cells.changed.connect(this._onCellsChanged, this);
|
36 | this.widget.content.activeCellChanged.connect(this._onActiveCellChanged, this);
|
37 | this.widget.content.selectionChanged.connect(this._onCellSelectionChanged, this);
|
38 | this.widget.content.stateChanged.connect(this._onNotebookStateChanged, this);
|
39 | this._observeActiveCell();
|
40 | this._filtersChanged.connect(this._setEnginesSelectionSearchMode, this);
|
41 | }
|
42 | _onNotebookStateChanged(_, args) {
|
43 | if (args.name === 'mode') {
|
44 |
|
45 | window.setTimeout(() => {
|
46 | var _a;
|
47 | if (args.newValue === 'command' &&
|
48 | ((_a = document.activeElement) === null || _a === void 0 ? void 0 : _a.closest('.jp-DocumentSearch-overlay'))) {
|
49 |
|
50 | return;
|
51 | }
|
52 | this._updateSelectionMode();
|
53 | this._filtersChanged.emit();
|
54 | }, 0);
|
55 | }
|
56 | }
|
57 | |
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 | static isApplicable(domain) {
|
64 |
|
65 |
|
66 | return domain instanceof NotebookPanel;
|
67 | }
|
68 | |
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 |
|
79 |
|
80 | static createNew(widget, translator) {
|
81 | return new NotebookSearchProvider(widget, translator);
|
82 | }
|
83 | |
84 |
|
85 |
|
86 | get currentMatchIndex() {
|
87 | let agg = 0;
|
88 | let found = false;
|
89 | for (let idx = 0; idx < this._searchProviders.length; idx++) {
|
90 | const provider = this._searchProviders[idx];
|
91 | if (this._currentProviderIndex == idx) {
|
92 | const localMatch = provider.currentMatchIndex;
|
93 | if (localMatch === null) {
|
94 | return null;
|
95 | }
|
96 | agg += localMatch;
|
97 | found = true;
|
98 | break;
|
99 | }
|
100 | else {
|
101 | agg += provider.matchesCount;
|
102 | }
|
103 | }
|
104 | return found ? agg : null;
|
105 | }
|
106 | |
107 |
|
108 |
|
109 | get matchesCount() {
|
110 | return this._searchProviders.reduce((sum, provider) => (sum += provider.matchesCount), 0);
|
111 | }
|
112 | |
113 |
|
114 |
|
115 |
|
116 |
|
117 | get isReadOnly() {
|
118 | var _a, _b, _c;
|
119 | return (_c = (_b = (_a = this.widget) === null || _a === void 0 ? void 0 : _a.content.model) === null || _b === void 0 ? void 0 : _b.readOnly) !== null && _c !== void 0 ? _c : false;
|
120 | }
|
121 | |
122 |
|
123 |
|
124 | get replaceOptionsSupport() {
|
125 | return {
|
126 | preserveCase: true
|
127 | };
|
128 | }
|
129 | |
130 |
|
131 |
|
132 |
|
133 |
|
134 |
|
135 |
|
136 |
|
137 |
|
138 |
|
139 |
|
140 | dispose() {
|
141 | var _a;
|
142 | if (this.isDisposed) {
|
143 | return;
|
144 | }
|
145 | this.widget.content.activeCellChanged.disconnect(this._onActiveCellChanged, this);
|
146 | (_a = this.widget.model) === null || _a === void 0 ? void 0 : _a.cells.changed.disconnect(this._onCellsChanged, this);
|
147 | this.widget.content.stateChanged.disconnect(this._onNotebookStateChanged, this);
|
148 | this.widget.content.selectionChanged.disconnect(this._onCellSelectionChanged, this);
|
149 | this._stopObservingLastCell();
|
150 | super.dispose();
|
151 | const index = this.widget.content.activeCellIndex;
|
152 | this.endQuery()
|
153 | .then(() => {
|
154 | if (!this.widget.isDisposed) {
|
155 | this.widget.content.activeCellIndex = index;
|
156 | }
|
157 | })
|
158 | .catch(reason => {
|
159 | console.error(`Fail to end search query in notebook:\n${reason}`);
|
160 | });
|
161 | }
|
162 | |
163 |
|
164 |
|
165 |
|
166 |
|
167 | getFilters() {
|
168 | const trans = this.translator.load('jupyterlab');
|
169 | return {
|
170 | output: {
|
171 | title: trans.__('Search Cell Outputs'),
|
172 | description: trans.__('Search in the cell outputs.'),
|
173 | default: false,
|
174 | supportReplace: false
|
175 | },
|
176 | selection: {
|
177 | title: this._selectionSearchMode === 'cells'
|
178 | ? trans._n('Search in %1 Selected Cell', 'Search in %1 Selected Cells', this._selectedCells)
|
179 | : trans._n('Search in %1 Selected Line', 'Search in %1 Selected Lines', this._selectedLines),
|
180 | description: trans.__('Search only in the selected cells or text (depending on edit/command mode).'),
|
181 | default: false,
|
182 | supportReplace: true
|
183 | }
|
184 | };
|
185 | }
|
186 | |
187 |
|
188 |
|
189 |
|
190 |
|
191 |
|
192 | _updateSelectionMode() {
|
193 | if (this._selectionLock) {
|
194 | return;
|
195 | }
|
196 | this._selectionSearchMode =
|
197 | this._selectedCells === 1 &&
|
198 | this.widget.content.mode === 'edit' &&
|
199 | this._selectedLines !== 0
|
200 | ? 'text'
|
201 | : 'cells';
|
202 | }
|
203 | |
204 |
|
205 |
|
206 |
|
207 |
|
208 |
|
209 | getInitialQuery() {
|
210 | const activeCell = this.widget.content.activeCell;
|
211 | const editor = activeCell === null || activeCell === void 0 ? void 0 : activeCell.editor;
|
212 | if (!editor) {
|
213 | return '';
|
214 | }
|
215 | const selection = editor.state.sliceDoc(editor.state.selection.main.from, editor.state.selection.main.to);
|
216 | return selection;
|
217 | }
|
218 | |
219 |
|
220 |
|
221 | async clearHighlight() {
|
222 | this._selectionLock = true;
|
223 | if (this._currentProviderIndex !== null &&
|
224 | this._currentProviderIndex < this._searchProviders.length) {
|
225 | await this._searchProviders[this._currentProviderIndex].clearHighlight();
|
226 | this._currentProviderIndex = null;
|
227 | }
|
228 | this._selectionLock = false;
|
229 | }
|
230 | |
231 |
|
232 |
|
233 |
|
234 |
|
235 |
|
236 |
|
237 | async highlightNext(loop = true, options) {
|
238 | const match = await this._stepNext(false, loop, options);
|
239 | return match !== null && match !== void 0 ? match : undefined;
|
240 | }
|
241 | |
242 |
|
243 |
|
244 |
|
245 |
|
246 |
|
247 |
|
248 | async highlightPrevious(loop = true, options) {
|
249 | const match = await this._stepNext(true, loop, options);
|
250 | return match !== null && match !== void 0 ? match : undefined;
|
251 | }
|
252 | |
253 |
|
254 |
|
255 |
|
256 |
|
257 |
|
258 |
|
259 | async startQuery(query, filters) {
|
260 | if (!this.widget) {
|
261 | return;
|
262 | }
|
263 | await this.endQuery();
|
264 | let cells = this.widget.content.widgets;
|
265 | this._query = query;
|
266 | this._filters = {
|
267 | output: false,
|
268 | selection: false,
|
269 | ...(filters !== null && filters !== void 0 ? filters : {})
|
270 | };
|
271 | this._onSelection = this._filters.selection;
|
272 | const currentProviderIndex = this.widget.content.activeCellIndex;
|
273 |
|
274 | this._searchProviders = await Promise.all(cells.map(async (cell, index) => {
|
275 | const cellSearchProvider = createCellSearchProvider(cell);
|
276 | await cellSearchProvider.setIsActive(!this._filters.selection ||
|
277 | this.widget.content.isSelectedOrActive(cell));
|
278 | if (this._onSelection &&
|
279 | this._selectionSearchMode === 'text' &&
|
280 | index === currentProviderIndex) {
|
281 | if (this._textSelection) {
|
282 | await cellSearchProvider.setSearchSelection(this._textSelection);
|
283 | }
|
284 | }
|
285 | await cellSearchProvider.startQuery(query, this._filters);
|
286 | return cellSearchProvider;
|
287 | }));
|
288 | this._currentProviderIndex = currentProviderIndex;
|
289 |
|
290 |
|
291 |
|
292 |
|
293 |
|
294 |
|
295 |
|
296 | await this.highlightNext(true, {
|
297 | from: 'selection-start',
|
298 | scroll: false,
|
299 | select: false
|
300 | });
|
301 | return Promise.resolve();
|
302 | }
|
303 | |
304 |
|
305 |
|
306 | async endQuery() {
|
307 | await Promise.all(this._searchProviders.map(provider => {
|
308 | return provider.endQuery().then(() => {
|
309 | provider.dispose();
|
310 | });
|
311 | }));
|
312 | this._searchProviders.length = 0;
|
313 | this._currentProviderIndex = null;
|
314 | }
|
315 | |
316 |
|
317 |
|
318 |
|
319 |
|
320 |
|
321 |
|
322 |
|
323 | async replaceCurrentMatch(newText, loop = true, options) {
|
324 | let replaceOccurred = false;
|
325 | const unrenderMarkdownCell = async (highlightNext = false) => {
|
326 | var _a;
|
327 |
|
328 | const activeCell = (_a = this.widget) === null || _a === void 0 ? void 0 : _a.content.activeCell;
|
329 | if ((activeCell === null || activeCell === void 0 ? void 0 : activeCell.model.type) === 'markdown' &&
|
330 | activeCell.rendered) {
|
331 | activeCell.rendered = false;
|
332 | if (highlightNext) {
|
333 | await this.highlightNext(loop);
|
334 | }
|
335 | }
|
336 | };
|
337 | if (this._currentProviderIndex !== null) {
|
338 | await unrenderMarkdownCell();
|
339 | const searchEngine = this._searchProviders[this._currentProviderIndex];
|
340 | replaceOccurred = await searchEngine.replaceCurrentMatch(newText, false, options);
|
341 | if (searchEngine.currentMatchIndex === null) {
|
342 |
|
343 | await this.highlightNext(loop);
|
344 | }
|
345 | }
|
346 |
|
347 |
|
348 | await unrenderMarkdownCell(true);
|
349 | return replaceOccurred;
|
350 | }
|
351 | |
352 |
|
353 |
|
354 |
|
355 |
|
356 |
|
357 |
|
358 | async replaceAllMatches(newText, options) {
|
359 | const replacementOccurred = await Promise.all(this._searchProviders.map(provider => {
|
360 | return provider.replaceAllMatches(newText, options);
|
361 | }));
|
362 | return replacementOccurred.includes(true);
|
363 | }
|
364 | async validateFilter(name, value) {
|
365 | if (name !== 'output') {
|
366 |
|
367 | return value;
|
368 | }
|
369 |
|
370 | if (value &&
|
371 | this.widget.content.widgets.some(w => w instanceof CodeCell && w.isPlaceholder())) {
|
372 | const trans = this.translator.load('jupyterlab');
|
373 | const reply = await showDialog({
|
374 | title: trans.__('Confirmation'),
|
375 | body: trans.__('Searching outputs is expensive and requires to first rendered all outputs. Are you sure you want to search in the cell outputs?'),
|
376 | buttons: [
|
377 | Dialog.cancelButton({ label: trans.__('Cancel') }),
|
378 | Dialog.okButton({ label: trans.__('Ok') })
|
379 | ]
|
380 | });
|
381 | if (reply.button.accept) {
|
382 | this.widget.content.widgets.forEach((w, i) => {
|
383 | if (w instanceof CodeCell && w.isPlaceholder()) {
|
384 | this.widget.content.renderCellOutputs(i);
|
385 | }
|
386 | });
|
387 | }
|
388 | else {
|
389 | return false;
|
390 | }
|
391 | }
|
392 | return value;
|
393 | }
|
394 | _addCellProvider(index) {
|
395 | var _a, _b;
|
396 | const cell = this.widget.content.widgets[index];
|
397 | const cellSearchProvider = createCellSearchProvider(cell);
|
398 | ArrayExt.insert(this._searchProviders, index, cellSearchProvider);
|
399 | void cellSearchProvider
|
400 | .setIsActive(!((_b = (_a = this._filters) === null || _a === void 0 ? void 0 : _a.selection) !== null && _b !== void 0 ? _b : false) ||
|
401 | this.widget.content.isSelectedOrActive(cell))
|
402 | .then(() => {
|
403 | void cellSearchProvider.startQuery(this._query, this._filters);
|
404 | });
|
405 | }
|
406 | _removeCellProvider(index) {
|
407 | const provider = ArrayExt.removeAt(this._searchProviders, index);
|
408 | provider === null || provider === void 0 ? void 0 : provider.dispose();
|
409 | }
|
410 | async _onCellsChanged(cells, changes) {
|
411 | switch (changes.type) {
|
412 | case 'add':
|
413 | changes.newValues.forEach((model, index) => {
|
414 | this._addCellProvider(changes.newIndex + index);
|
415 | });
|
416 | break;
|
417 | case 'move':
|
418 | ArrayExt.move(this._searchProviders, changes.oldIndex, changes.newIndex);
|
419 | break;
|
420 | case 'remove':
|
421 | for (let index = 0; index < changes.oldValues.length; index++) {
|
422 | this._removeCellProvider(changes.oldIndex);
|
423 | }
|
424 | break;
|
425 | case 'set':
|
426 | changes.newValues.forEach((model, index) => {
|
427 | this._addCellProvider(changes.newIndex + index);
|
428 | this._removeCellProvider(changes.newIndex + index + 1);
|
429 | });
|
430 | break;
|
431 | }
|
432 | this._stateChanged.emit();
|
433 | }
|
434 | async _stepNext(reverse = false, loop = false, options) {
|
435 | const activateNewMatch = async (match) => {
|
436 | var _a;
|
437 | const shouldScroll = (_a = options === null || options === void 0 ? void 0 : options.scroll) !== null && _a !== void 0 ? _a : true;
|
438 | if (!shouldScroll) {
|
439 |
|
440 | return;
|
441 | }
|
442 | this._selectionLock = true;
|
443 | if (this.widget.content.activeCellIndex !== this._currentProviderIndex) {
|
444 | this.widget.content.activeCellIndex = this._currentProviderIndex;
|
445 | }
|
446 | if (this.widget.content.activeCellIndex === -1) {
|
447 | console.warn('No active cell (no cells or no model), aborting search');
|
448 | this._selectionLock = false;
|
449 | return;
|
450 | }
|
451 | const activeCell = this.widget.content.activeCell;
|
452 | if (!activeCell.inViewport) {
|
453 | try {
|
454 | await this.widget.content.scrollToItem(this._currentProviderIndex);
|
455 | }
|
456 | catch (error) {
|
457 |
|
458 | }
|
459 | }
|
460 |
|
461 | if (activeCell.inputHidden) {
|
462 | activeCell.inputHidden = false;
|
463 | }
|
464 | if (!activeCell.inViewport) {
|
465 | this._selectionLock = false;
|
466 |
|
467 | return;
|
468 | }
|
469 | await activeCell.ready;
|
470 | const editor = activeCell.editor;
|
471 | editor.revealPosition(editor.getPositionAt(match.position));
|
472 | this._selectionLock = false;
|
473 | };
|
474 | if (this._currentProviderIndex === null) {
|
475 | this._currentProviderIndex = this.widget.content.activeCellIndex;
|
476 | }
|
477 |
|
478 |
|
479 |
|
480 | if (reverse && this.widget.content.mode === 'command') {
|
481 | const searchEngine = this._searchProviders[this._currentProviderIndex];
|
482 | const currentMatch = searchEngine.getCurrentMatch();
|
483 | if (!currentMatch) {
|
484 | this._currentProviderIndex -= 1;
|
485 | }
|
486 | if (loop) {
|
487 | this._currentProviderIndex =
|
488 | (this._currentProviderIndex + this._searchProviders.length) %
|
489 | this._searchProviders.length;
|
490 | }
|
491 | }
|
492 | const startIndex = this._currentProviderIndex;
|
493 | do {
|
494 | const searchEngine = this._searchProviders[this._currentProviderIndex];
|
495 | const match = reverse
|
496 | ? await searchEngine.highlightPrevious(false, options)
|
497 | : await searchEngine.highlightNext(false, options);
|
498 | if (match) {
|
499 | await activateNewMatch(match);
|
500 | return match;
|
501 | }
|
502 | else {
|
503 | this._currentProviderIndex =
|
504 | this._currentProviderIndex + (reverse ? -1 : 1);
|
505 | if (loop) {
|
506 | this._currentProviderIndex =
|
507 | (this._currentProviderIndex + this._searchProviders.length) %
|
508 | this._searchProviders.length;
|
509 | }
|
510 | }
|
511 | } while (loop
|
512 | ?
|
513 | this._currentProviderIndex !== startIndex
|
514 | : 0 <= this._currentProviderIndex &&
|
515 | this._currentProviderIndex < this._searchProviders.length);
|
516 | if (loop) {
|
517 |
|
518 | const searchEngine = this._searchProviders[startIndex];
|
519 | const match = reverse
|
520 | ? await searchEngine.highlightPrevious(false, options)
|
521 | : await searchEngine.highlightNext(false, options);
|
522 | if (match) {
|
523 | await activateNewMatch(match);
|
524 | return match;
|
525 | }
|
526 | }
|
527 | this._currentProviderIndex = null;
|
528 | return null;
|
529 | }
|
530 | async _onActiveCellChanged() {
|
531 | if (this._delayedActiveCellChangeHandler !== null) {
|
532 |
|
533 |
|
534 | clearTimeout(this._delayedActiveCellChangeHandler);
|
535 | this._delayedActiveCellChangeHandler = null;
|
536 | }
|
537 | if (this.widget.content.activeCellIndex !== this._currentProviderIndex) {
|
538 |
|
539 |
|
540 |
|
541 |
|
542 |
|
543 | this._delayedActiveCellChangeHandler = setTimeout(() => {
|
544 | this.delayedActiveCellChangeHandlerReady =
|
545 | this._handleHighlightsAfterActiveCellChange();
|
546 | }, 0);
|
547 | }
|
548 | this._observeActiveCell();
|
549 | }
|
550 | async _handleHighlightsAfterActiveCellChange() {
|
551 | if (this._onSelection) {
|
552 | const previousProviderCell = this._currentProviderIndex !== null &&
|
553 | this._currentProviderIndex < this.widget.content.widgets.length
|
554 | ? this.widget.content.widgets[this._currentProviderIndex]
|
555 | : null;
|
556 | const previousProviderInCurrentSelection = previousProviderCell &&
|
557 | this.widget.content.isSelectedOrActive(previousProviderCell);
|
558 | if (!previousProviderInCurrentSelection) {
|
559 | await this._updateCellSelection();
|
560 |
|
561 | await this.clearHighlight();
|
562 |
|
563 |
|
564 |
|
565 | this._currentProviderIndex = this.widget.content.activeCellIndex;
|
566 | }
|
567 | }
|
568 | await this._ensureCurrentMatch();
|
569 | }
|
570 | |
571 |
|
572 |
|
573 |
|
574 | async _ensureCurrentMatch() {
|
575 | if (this._currentProviderIndex !== null) {
|
576 | const searchEngine = this._searchProviders[this._currentProviderIndex];
|
577 | if (!searchEngine) {
|
578 |
|
579 | return;
|
580 | }
|
581 | const currentMatch = searchEngine.getCurrentMatch();
|
582 | if (!currentMatch && this.matchesCount) {
|
583 |
|
584 |
|
585 | await this.highlightNext(true, {
|
586 | from: 'start',
|
587 | scroll: false,
|
588 | select: false
|
589 | });
|
590 | }
|
591 | }
|
592 | }
|
593 | _observeActiveCell() {
|
594 | var _a;
|
595 | const editor = (_a = this.widget.content.activeCell) === null || _a === void 0 ? void 0 : _a.editor;
|
596 | if (!editor) {
|
597 | return;
|
598 | }
|
599 | this._stopObservingLastCell();
|
600 | editor.model.selections.changed.connect(this._setSelectedLines, this);
|
601 | this._editorSelectionsObservable = editor.model.selections;
|
602 | }
|
603 | _stopObservingLastCell() {
|
604 | if (this._editorSelectionsObservable) {
|
605 | this._editorSelectionsObservable.changed.disconnect(this._setSelectedLines, this);
|
606 | }
|
607 | }
|
608 | _setSelectedLines() {
|
609 | var _a;
|
610 | const editor = (_a = this.widget.content.activeCell) === null || _a === void 0 ? void 0 : _a.editor;
|
611 | if (!editor) {
|
612 | return;
|
613 | }
|
614 | const selection = editor.getSelection();
|
615 | const { start, end } = selection;
|
616 | const newLines = end.line === start.line && end.column === start.column
|
617 | ? 0
|
618 | : end.line - start.line + 1;
|
619 | this._textSelection = selection;
|
620 | if (newLines !== this._selectedLines) {
|
621 | this._selectedLines = newLines;
|
622 | this._updateSelectionMode();
|
623 | }
|
624 | this._filtersChanged.emit();
|
625 | }
|
626 | |
627 |
|
628 |
|
629 | async _setEnginesSelectionSearchMode() {
|
630 | let textMode;
|
631 | if (!this._onSelection) {
|
632 |
|
633 | textMode = false;
|
634 | }
|
635 | else {
|
636 |
|
637 |
|
638 |
|
639 | textMode = this._selectionSearchMode === 'text';
|
640 | }
|
641 | if (this._selectionLock) {
|
642 | return;
|
643 | }
|
644 |
|
645 | await Promise.all(this._searchProviders.map((provider, index) => {
|
646 | const isCurrent = this.widget.content.activeCellIndex === index;
|
647 | provider.setProtectSelection(isCurrent && this._onSelection);
|
648 | return provider.setSearchSelection(isCurrent && textMode ? this._textSelection : null);
|
649 | }));
|
650 | }
|
651 | async _onCellSelectionChanged() {
|
652 | if (this._delayedActiveCellChangeHandler !== null) {
|
653 |
|
654 |
|
655 |
|
656 | clearTimeout(this._delayedActiveCellChangeHandler);
|
657 | this._delayedActiveCellChangeHandler = null;
|
658 | }
|
659 | await this._updateCellSelection();
|
660 | if (this._currentProviderIndex === null) {
|
661 |
|
662 | const firstSelectedCellIndex = this.widget.content.widgets.findIndex(cell => this.widget.content.isSelectedOrActive(cell));
|
663 | this._currentProviderIndex = firstSelectedCellIndex;
|
664 | }
|
665 | await this._ensureCurrentMatch();
|
666 | }
|
667 | async _updateCellSelection() {
|
668 | const cells = this.widget.content.widgets;
|
669 | let selectedCells = 0;
|
670 | await Promise.all(cells.map(async (cell, index) => {
|
671 | const provider = this._searchProviders[index];
|
672 | const isSelected = this.widget.content.isSelectedOrActive(cell);
|
673 | if (isSelected) {
|
674 | selectedCells += 1;
|
675 | }
|
676 | if (provider && this._onSelection) {
|
677 | await provider.setIsActive(isSelected);
|
678 | }
|
679 | }));
|
680 | if (selectedCells !== this._selectedCells) {
|
681 | this._selectedCells = selectedCells;
|
682 | this._updateSelectionMode();
|
683 | }
|
684 | this._filtersChanged.emit();
|
685 | }
|
686 | }
|
687 |
|
\ | No newline at end of file |