@difizen/libro-search-code-cell 0.1.2

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.
@@ -0,0 +1,58 @@
1
+ import type { IEditor, SearchMatch } from '@difizen/libro-code-editor';
2
+ import { LibroSearchUtils } from '@difizen/libro-search';
3
+ import type { CodeEditorSearchHighlighter } from './code-cell-search-protocol.js';
4
+ /**
5
+ * Helper class to highlight texts in a code mirror editor.
6
+ *
7
+ * Highlighted texts (aka `matches`) must be provided through
8
+ * the `matches` attributes.
9
+ */
10
+ export declare class GenericSearchHighlighter implements CodeEditorSearchHighlighter {
11
+ utils: LibroSearchUtils;
12
+ protected editor: IEditor | undefined;
13
+ _currentIndex: number | undefined;
14
+ protected _matches: SearchMatch[];
15
+ /**
16
+ * The list of matches
17
+ */
18
+ get matches(): SearchMatch[];
19
+ set matches(v: SearchMatch[]);
20
+ get currentIndex(): number | undefined;
21
+ set currentIndex(v: number | undefined);
22
+ /**
23
+ * Constructor
24
+ *
25
+ * @param editor The CodeMirror editor
26
+ */
27
+ constructor();
28
+ /**
29
+ * Clear all highlighted matches
30
+ */
31
+ clearHighlight(): void;
32
+ /**
33
+ * Clear the highlighted matches.
34
+ */
35
+ endQuery(): Promise<void>;
36
+ /**
37
+ * Highlight the next match
38
+ *
39
+ * @returns The next match if available
40
+ */
41
+ highlightNext(): Promise<SearchMatch | undefined>;
42
+ /**
43
+ * Highlight the previous match
44
+ *
45
+ * @returns The previous match if available
46
+ */
47
+ highlightPrevious(): Promise<SearchMatch | undefined>;
48
+ /**
49
+ * Set the editor
50
+ *
51
+ * @param editor Editor
52
+ */
53
+ setEditor(editor: IEditor): void;
54
+ protected _highlightCurrentMatch(): void;
55
+ protected refresh(): void;
56
+ protected _findNext(reverse: boolean): number | undefined;
57
+ }
58
+ //# sourceMappingURL=search-highlighter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-highlighter.d.ts","sourceRoot":"","sources":["../src/search-highlighter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAEvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAIzD,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,gCAAgC,CAAC;AAElF;;;;;GAKG;AACH,qBACa,wBAAyB,YAAW,2BAA2B;IAChD,KAAK,EAAE,gBAAgB,CAAC;IAElD,SAAS,CAAC,MAAM,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,SAAS,CAAC,QAAQ,EAAE,WAAW,EAAE,CAAC;IAE1C;;OAEG;IACH,IAAI,OAAO,IAAI,WAAW,EAAE,CAE3B;IACD,IAAI,OAAO,CAAC,CAAC,EAAE,WAAW,EAAE,EAK3B;IAED,IAAI,YAAY,IAAI,MAAM,GAAG,SAAS,CAErC;IACD,IAAI,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,SAAS,EAGrC;IAED;;;;OAIG;;IAMH;;OAEG;IACH,cAAc,IAAI,IAAI;IAKtB;;OAEG;IACH,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAsBzB;;;;OAIG;IACH,aAAa,IAAI,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC;IAQjD;;;;OAIG;IACH,iBAAiB,IAAI,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC;IAQrD;;;;OAIG;IACH,SAAS,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI;IAQhC,SAAS,CAAC,sBAAsB,IAAI,IAAI;IAwBxC,SAAS,CAAC,OAAO,IAAI,IAAI;IAQzB,SAAS,CAAC,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS;CAgD1D"}
@@ -0,0 +1,241 @@
1
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
2
+ var _dec, _dec2, _dec3, _dec4, _class, _class2, _descriptor, _descriptor2, _descriptor3;
3
+ function _initializerDefineProperty(target, property, descriptor, context) { if (!descriptor) return; Object.defineProperty(target, property, { enumerable: descriptor.enumerable, configurable: descriptor.configurable, writable: descriptor.writable, value: descriptor.initializer ? descriptor.initializer.call(context) : void 0 }); }
4
+ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
5
+ function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
6
+ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
7
+ function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
8
+ function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
9
+ function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { var desc = {}; Object.keys(descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; desc.configurable = !!desc.configurable; if ('value' in desc || desc.initializer) { desc.writable = true; } desc = decorators.slice().reverse().reduce(function (desc, decorator) { return decorator(target, property, desc) || desc; }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; desc.initializer = undefined; } if (desc.initializer === void 0) { Object.defineProperty(target, property, desc); desc = null; } return desc; }
10
+ function _initializerWarningHelper(descriptor, context) { throw new Error('Decorating class property failed. Please ensure that ' + 'transform-class-properties is enabled and runs after the decorators transform.'); }
11
+ /* eslint-disable no-param-reassign */
12
+
13
+ import { deepEqual } from '@difizen/libro-common';
14
+ import { LibroSearchUtils } from '@difizen/libro-search';
15
+ import { prop } from '@difizen/mana-app';
16
+ import { inject, transient } from '@difizen/mana-app';
17
+ /**
18
+ * Helper class to highlight texts in a code mirror editor.
19
+ *
20
+ * Highlighted texts (aka `matches`) must be provided through
21
+ * the `matches` attributes.
22
+ */
23
+ export var GenericSearchHighlighter = (_dec = transient(), _dec2 = inject(LibroSearchUtils), _dec3 = prop(), _dec4 = prop(), _dec(_class = (_class2 = /*#__PURE__*/function () {
24
+ /**
25
+ * Constructor
26
+ *
27
+ * @param editor The CodeMirror editor
28
+ */
29
+ function GenericSearchHighlighter() {
30
+ _classCallCheck(this, GenericSearchHighlighter);
31
+ _initializerDefineProperty(this, "utils", _descriptor, this);
32
+ _initializerDefineProperty(this, "_currentIndex", _descriptor2, this);
33
+ _initializerDefineProperty(this, "_matches", _descriptor3, this);
34
+ this._matches = new Array();
35
+ this.currentIndex = undefined;
36
+ }
37
+
38
+ /**
39
+ * Clear all highlighted matches
40
+ */
41
+ _createClass(GenericSearchHighlighter, [{
42
+ key: "matches",
43
+ get:
44
+ /**
45
+ * The list of matches
46
+ */
47
+ function get() {
48
+ return this._matches;
49
+ },
50
+ set: function set(v) {
51
+ if (!deepEqual(this._matches, v)) {
52
+ this._matches = v;
53
+ }
54
+ this.refresh();
55
+ }
56
+ }, {
57
+ key: "currentIndex",
58
+ get: function get() {
59
+ return this._currentIndex;
60
+ },
61
+ set: function set(v) {
62
+ this._currentIndex = v;
63
+ this.refresh();
64
+ }
65
+ }, {
66
+ key: "clearHighlight",
67
+ value: function clearHighlight() {
68
+ this.currentIndex = undefined;
69
+ this._highlightCurrentMatch();
70
+ }
71
+
72
+ /**
73
+ * Clear the highlighted matches.
74
+ */
75
+ }, {
76
+ key: "endQuery",
77
+ value: function endQuery() {
78
+ this._currentIndex = undefined;
79
+ this._matches = [];
80
+ if (this.editor) {
81
+ this.editor.highlightMatches([], undefined);
82
+ var selection = this.editor.getSelection();
83
+ var start = this.editor.getOffsetAt(selection.start);
84
+ var end = this.editor.getOffsetAt(selection.end);
85
+
86
+ // Setting a reverse selection to allow search-as-you-type to maintain the
87
+ // current selected match. See comment in _findNext for more details.
88
+ if (start !== end) {
89
+ this.editor.setSelection(selection);
90
+ }
91
+ }
92
+ return Promise.resolve();
93
+ }
94
+
95
+ /**
96
+ * Highlight the next match
97
+ *
98
+ * @returns The next match if available
99
+ */
100
+ }, {
101
+ key: "highlightNext",
102
+ value: function highlightNext() {
103
+ this.currentIndex = this._findNext(false);
104
+ this._highlightCurrentMatch();
105
+ return Promise.resolve(this.currentIndex !== undefined ? this._matches[this.currentIndex] : undefined);
106
+ }
107
+
108
+ /**
109
+ * Highlight the previous match
110
+ *
111
+ * @returns The previous match if available
112
+ */
113
+ }, {
114
+ key: "highlightPrevious",
115
+ value: function highlightPrevious() {
116
+ this.currentIndex = this._findNext(true);
117
+ this._highlightCurrentMatch();
118
+ return Promise.resolve(this.currentIndex !== undefined ? this._matches[this.currentIndex] : undefined);
119
+ }
120
+
121
+ /**
122
+ * Set the editor
123
+ *
124
+ * @param editor Editor
125
+ */
126
+ }, {
127
+ key: "setEditor",
128
+ value: function setEditor(editor) {
129
+ this.editor = editor;
130
+ this.refresh();
131
+ if (this.currentIndex !== undefined) {
132
+ this._highlightCurrentMatch();
133
+ }
134
+ }
135
+ }, {
136
+ key: "_highlightCurrentMatch",
137
+ value: function _highlightCurrentMatch() {
138
+ if (!this.editor) {
139
+ // no-op
140
+ return;
141
+ }
142
+
143
+ // Highlight the current index
144
+ if (this.currentIndex !== undefined) {
145
+ var match = this.matches[this.currentIndex];
146
+ // this.cm.editor.focus();
147
+ var start = this.editor.getPositionAt(match.position);
148
+ var end = this.editor.getPositionAt(match.position + match.text.length);
149
+ if (start && end) {
150
+ this.editor.setSelection({
151
+ start: start,
152
+ end: end
153
+ });
154
+ this.editor.revealSelection({
155
+ start: start,
156
+ end: end
157
+ });
158
+ }
159
+ } else {
160
+ var _start = this.editor.getPositionAt(0);
161
+ var _end = this.editor.getPositionAt(0);
162
+ // Set cursor to remove any selection
163
+ this.editor.setSelection({
164
+ start: _start,
165
+ end: _end
166
+ });
167
+ }
168
+ }
169
+ }, {
170
+ key: "refresh",
171
+ value: function refresh() {
172
+ if (!this.editor) {
173
+ // no-op
174
+ return;
175
+ }
176
+ this.editor.highlightMatches(this.matches, this.currentIndex);
177
+ }
178
+ }, {
179
+ key: "_findNext",
180
+ value: function _findNext(reverse) {
181
+ var _this$editor, _this$editor2;
182
+ if (this.matches.length === 0) {
183
+ // No-op
184
+ return undefined;
185
+ }
186
+ if (!this.editor) {
187
+ return;
188
+ }
189
+ // In order to support search-as-you-type, we needed a way to allow the first
190
+ // match to be selected when a search is started, but prevent the selected
191
+ // search to move for each new keypress. To do this, when a search is ended,
192
+ // the cursor is reversed, putting the head at the 'from' position. When a new
193
+ // search is started, the cursor we want is at the 'from' position, so that the same
194
+ // match is selected when the next key is entered (if it is still a match).
195
+ //
196
+ // When toggling through a search normally, the cursor is always set in the forward
197
+ // direction, so head is always at the 'to' position. That way, if reverse = false,
198
+ // the search proceeds from the 'to' position during normal toggling. If reverse = true,
199
+ // the search always proceeds from the 'anchor' position, which is at the 'from'.
200
+
201
+ var selection = (_this$editor = this.editor) === null || _this$editor === void 0 ? void 0 : _this$editor.getSelection();
202
+ var start = (_this$editor2 = this.editor) === null || _this$editor2 === void 0 ? void 0 : _this$editor2.getOffsetAt(selection.start);
203
+ var end = this.editor.getOffsetAt(selection.end);
204
+ var lastPosition = reverse ? start : end;
205
+ if (lastPosition === 0 && reverse && this.currentIndex === undefined) {
206
+ // The default position is (0, 0) but we want to start from the end in that case
207
+ lastPosition = this.editor.model.value.length;
208
+ }
209
+ var position = lastPosition;
210
+ var found = this.utils.findNext(this.matches, position, 0, this.matches.length - 1);
211
+ if (found === undefined) {
212
+ // Don't loop
213
+ return reverse ? this.matches.length - 1 : undefined;
214
+ }
215
+ if (reverse) {
216
+ found -= 1;
217
+ if (found < 0) {
218
+ // Don't loop
219
+ return undefined;
220
+ }
221
+ }
222
+ return found;
223
+ }
224
+ }]);
225
+ return GenericSearchHighlighter;
226
+ }(), (_descriptor = _applyDecoratedDescriptor(_class2.prototype, "utils", [_dec2], {
227
+ configurable: true,
228
+ enumerable: true,
229
+ writable: true,
230
+ initializer: null
231
+ }), _descriptor2 = _applyDecoratedDescriptor(_class2.prototype, "_currentIndex", [_dec3], {
232
+ configurable: true,
233
+ enumerable: true,
234
+ writable: true,
235
+ initializer: null
236
+ }), _descriptor3 = _applyDecoratedDescriptor(_class2.prototype, "_matches", [_dec4], {
237
+ configurable: true,
238
+ enumerable: true,
239
+ writable: true,
240
+ initializer: null
241
+ })), _class2)) || _class);
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@difizen/libro-search-code-cell",
3
+ "version": "0.1.2",
4
+ "description": "",
5
+ "keywords": [
6
+ "libro",
7
+ "notebook"
8
+ ],
9
+ "repository": "git@github.com:difizen/libro.git",
10
+ "license": "MIT",
11
+ "type": "module",
12
+ "exports": {
13
+ ".": {
14
+ "typings": "./es/index.d.ts",
15
+ "default": "./es/index.js"
16
+ },
17
+ "./mock": {
18
+ "typings": "./es/mock/index.d.ts",
19
+ "default": "./es/mock/index.js"
20
+ },
21
+ "./es/mock": {
22
+ "typings": "./es/mock/index.d.ts",
23
+ "default": "./es/mock/index.js"
24
+ },
25
+ "./package.json": "./package.json"
26
+ },
27
+ "main": "es/index.js",
28
+ "module": "es/index.js",
29
+ "typings": "es/index.d.ts",
30
+ "files": [
31
+ "es",
32
+ "src"
33
+ ],
34
+ "dependencies": {
35
+ "@difizen/libro-common": "^0.1.2",
36
+ "@difizen/libro-code-editor": "^0.1.2",
37
+ "@difizen/libro-code-cell": "^0.1.2",
38
+ "@difizen/libro-search": "^0.1.2",
39
+ "@difizen/libro-core": "^0.1.2",
40
+ "@difizen/mana-app": "latest",
41
+ "@codemirror/state": "^6.0.0",
42
+ "@codemirror/view": "^6.2.2"
43
+ },
44
+ "scripts": {
45
+ "setup": "father build",
46
+ "build": "father build",
47
+ "test": ": Note: lint task is delegated to test:* scripts",
48
+ "test:vitest": "vitest run",
49
+ "test:jest": "jest",
50
+ "coverage": ": Note: lint task is delegated to coverage:* scripts",
51
+ "coverage:vitest": "vitest run --coverage",
52
+ "coverage:jest": "jest --coverage",
53
+ "lint": ": Note: lint task is delegated to lint:* scripts",
54
+ "lint:eslint": "eslint src",
55
+ "lint:tsc": "tsc --noEmit"
56
+ }
57
+ }
@@ -0,0 +1,40 @@
1
+ import type { LibroCodeCellView } from '@difizen/libro-code-cell';
2
+ import type { IEditor, SearchMatch } from '@difizen/libro-code-editor';
3
+ import type { CellSearchProvider } from '@difizen/libro-search';
4
+
5
+ export type CodeEditorSearchHighlighterFactory = (
6
+ editor: IEditor | undefined,
7
+ ) => CodeEditorSearchHighlighter;
8
+ export const CodeEditorSearchHighlighterFactory = Symbol(
9
+ 'CodeEditorSearchHighlighterFactory',
10
+ );
11
+
12
+ export const CodeCellSearchOption = Symbol('CodeCellSearchOption');
13
+ export interface CodeCellSearchOption {
14
+ cell: LibroCodeCellView;
15
+ }
16
+
17
+ export const CodeCellSearchProviderFactory = Symbol('CodeCellSearchProviderFactory');
18
+ export type CodeCellSearchProviderFactory = (
19
+ option: CodeCellSearchOption,
20
+ ) => CellSearchProvider;
21
+
22
+ export interface CodeEditorSearchHighlighter {
23
+ /**
24
+ * The list of matches
25
+ */
26
+ get matches(): SearchMatch[];
27
+ set matches(v: SearchMatch[]);
28
+
29
+ get currentIndex(): number | undefined;
30
+ set currentIndex(v: number | undefined);
31
+
32
+ setEditor: (editor: IEditor) => void;
33
+
34
+ clearHighlight: () => void;
35
+
36
+ endQuery: () => Promise<void>;
37
+
38
+ highlightNext: () => Promise<SearchMatch | undefined>;
39
+ highlightPrevious: () => Promise<SearchMatch | undefined>;
40
+ }
@@ -0,0 +1,39 @@
1
+ import { LibroCodeCellView } from '@difizen/libro-code-cell';
2
+ import type { CellView } from '@difizen/libro-core';
3
+ import { CellSearchProviderContribution } from '@difizen/libro-search';
4
+ import { ViewManager } from '@difizen/mana-app';
5
+ import { inject, singleton } from '@difizen/mana-app';
6
+
7
+ import { CodeCellSearchProviderFactory } from './code-cell-search-protocol.js';
8
+
9
+ @singleton({ contrib: CellSearchProviderContribution })
10
+ export class CodeCellSearchProviderContribution
11
+ implements CellSearchProviderContribution
12
+ {
13
+ @inject(ViewManager) viewManager: ViewManager;
14
+ @inject(CodeCellSearchProviderFactory)
15
+ providerfactory: CodeCellSearchProviderFactory;
16
+ canHandle = (cell: CellView) => {
17
+ if (cell instanceof LibroCodeCellView) {
18
+ return 100;
19
+ }
20
+ return 0;
21
+ };
22
+ factory(cell: CellView) {
23
+ return this.providerfactory({ cell: cell as LibroCodeCellView });
24
+ }
25
+ /**
26
+ * Get an initial query value if applicable so that it can be entered
27
+ * into the search box as an initial query
28
+ *
29
+ * @returns Initial value used to populate the search box.
30
+ */
31
+ getInitialQuery = (cell: CellView): string => {
32
+ if (cell instanceof LibroCodeCellView) {
33
+ const selection = cell.editor?.getSelectionValue();
34
+ // if there are newlines, just return empty string
35
+ return selection?.search(/\r?\n|\r/g) === -1 ? selection : '';
36
+ }
37
+ return '';
38
+ };
39
+ }
@@ -0,0 +1,227 @@
1
+ /* eslint-disable no-param-reassign */
2
+ /* eslint-disable @typescript-eslint/no-unused-vars */
3
+ import type { SearchMatch } from '@difizen/libro-code-editor';
4
+ import type { GenericSearchProvider, SearchFilters } from '@difizen/libro-search';
5
+ import { GenericSearchProviderFactory } from '@difizen/libro-search';
6
+ import { inject, prop, transient, watch } from '@difizen/mana-app';
7
+
8
+ import {
9
+ CodeCellSearchOption,
10
+ CodeEditorSearchHighlighterFactory,
11
+ } from './code-cell-search-protocol.js';
12
+ import { CodeEditorCellSearchProvider } from './code-editor-cell-search-provider.js';
13
+
14
+ @transient()
15
+ export class CodeCellSearchProvider extends CodeEditorCellSearchProvider {
16
+ protected genericSearchProviderFactory: GenericSearchProviderFactory;
17
+ @prop() protected outputsProvider: GenericSearchProvider[];
18
+ @prop() protected currentProviderIndex: number;
19
+ /**
20
+ * Constructor
21
+ *
22
+ * @param cell Cell widget
23
+ */
24
+ constructor(
25
+ @inject(CodeEditorSearchHighlighterFactory)
26
+ highlighterFactory: CodeEditorSearchHighlighterFactory,
27
+ @inject(GenericSearchProviderFactory)
28
+ genericSearchProviderFactory: GenericSearchProviderFactory,
29
+ @inject(CodeCellSearchOption) option: CodeCellSearchOption,
30
+ ) {
31
+ super(highlighterFactory, option.cell);
32
+ this.genericSearchProviderFactory = genericSearchProviderFactory;
33
+ this.currentProviderIndex = -1;
34
+ this.outputsProvider = [];
35
+ this.setupOutputProvider();
36
+ this.cell.outputArea.onUpdate(() => {
37
+ this.setupOutputProvider();
38
+ });
39
+
40
+ this.toDispose.push(
41
+ watch(this.cell.model, 'hasOutputHidden', async () => {
42
+ await this.refresh();
43
+ }),
44
+ );
45
+ this.toDispose.push(
46
+ watch(this.cell, 'hasInputHidden', async () => {
47
+ await this.refresh();
48
+ }),
49
+ );
50
+ }
51
+
52
+ /**
53
+ * Number of matches in the cell.
54
+ */
55
+ override get matchesCount(): number {
56
+ if (!this.isActive) {
57
+ return 0;
58
+ }
59
+
60
+ return (
61
+ super.matchesCount +
62
+ this.outputsProvider.reduce(
63
+ (sum, provider) => sum + (provider.matchesCount ?? 0),
64
+ 0,
65
+ )
66
+ );
67
+ }
68
+
69
+ /**
70
+ * Clear currently highlighted match.
71
+ */
72
+ override async clearHighlight(): Promise<void> {
73
+ await super.clearHighlight();
74
+ await Promise.all(
75
+ this.outputsProvider.map((provider) => provider.clearHighlight()),
76
+ );
77
+ }
78
+
79
+ /**
80
+ * Dispose the search provider
81
+ */
82
+ override dispose(): void {
83
+ if (this.isDisposed) {
84
+ return;
85
+ }
86
+ super.dispose();
87
+ this.outputsProvider.map((provider) => {
88
+ provider.dispose();
89
+ });
90
+ this.outputsProvider.length = 0;
91
+ }
92
+
93
+ /**
94
+ * Highlight the next match.
95
+ *
96
+ * @returns The next match if there is one.
97
+ */
98
+ override async highlightNext(): Promise<SearchMatch | undefined> {
99
+ if (this.matchesCount === 0 || !this.isActive) {
100
+ this.currentIndex = undefined;
101
+ } else {
102
+ if (this.currentProviderIndex === -1) {
103
+ const match = await super.highlightNext();
104
+ if (match) {
105
+ this.currentIndex = this.editorHighlighter.currentIndex;
106
+ return match;
107
+ } else {
108
+ this.currentProviderIndex = 0;
109
+ }
110
+ }
111
+
112
+ while (this.currentProviderIndex < this.outputsProvider.length) {
113
+ const provider = this.outputsProvider[this.currentProviderIndex];
114
+ const match = await provider.highlightNext(false);
115
+ if (match) {
116
+ this.currentIndex =
117
+ super.matchesCount +
118
+ this.outputsProvider
119
+ .slice(0, this.currentProviderIndex)
120
+ .reduce((sum, p) => (sum += p.matchesCount ?? 0), 0) +
121
+ provider.currentMatchIndex!;
122
+ return match;
123
+ } else {
124
+ this.currentProviderIndex += 1;
125
+ }
126
+ }
127
+
128
+ this.currentProviderIndex = -1;
129
+ this.currentIndex = undefined;
130
+ return undefined;
131
+ }
132
+ return;
133
+ }
134
+
135
+ /**
136
+ * Highlight the previous match.
137
+ *
138
+ * @returns The previous match if there is one.
139
+ */
140
+ override async highlightPrevious(): Promise<SearchMatch | undefined> {
141
+ if (this.matchesCount === 0 || !this.isActive) {
142
+ this.currentIndex = undefined;
143
+ } else {
144
+ if (this.currentIndex === undefined) {
145
+ this.currentProviderIndex = this.outputsProvider.length - 1;
146
+ }
147
+
148
+ while (this.currentProviderIndex >= 0) {
149
+ const provider = this.outputsProvider[this.currentProviderIndex];
150
+
151
+ const match = await provider.highlightPrevious(false);
152
+ if (match) {
153
+ this.currentIndex =
154
+ super.matchesCount +
155
+ this.outputsProvider
156
+ .slice(0, this.currentProviderIndex)
157
+ .reduce((sum, p) => (sum += p.matchesCount ?? 0), 0) +
158
+ provider.currentMatchIndex!;
159
+ return match;
160
+ } else {
161
+ this.currentProviderIndex -= 1;
162
+ }
163
+ }
164
+
165
+ const match = await super.highlightPrevious();
166
+ if (match) {
167
+ this.currentIndex = this.editorHighlighter.currentIndex;
168
+ return match;
169
+ } else {
170
+ this.currentIndex = undefined;
171
+ return undefined;
172
+ }
173
+ }
174
+ return;
175
+ }
176
+
177
+ /**
178
+ * Initialize the search using the provided options. Should update the UI to highlight
179
+ * all matches and "select" the first match.
180
+ *
181
+ * @param query A RegExp to be use to perform the search
182
+ * @param filters Filter parameters to pass to provider
183
+ */
184
+ override async startQuery(
185
+ query: RegExp | null,
186
+ filters?: SearchFilters,
187
+ ): Promise<void> {
188
+ await super.startQuery(query, filters);
189
+ // Search outputs
190
+ if (filters?.searchCellOutput) {
191
+ await Promise.all(
192
+ this.outputsProvider.map((provider) => {
193
+ provider.startQuery(query, this.filters);
194
+ }),
195
+ );
196
+ }
197
+ }
198
+
199
+ override async endQuery(): Promise<void> {
200
+ await super.endQuery();
201
+ if (this.filters?.searchCellOutput !== false && this.isActive) {
202
+ await Promise.all(this.outputsProvider.map((provider) => provider.endQuery()));
203
+ }
204
+ }
205
+
206
+ async refresh() {
207
+ await this.endQuery();
208
+ await this.clearHighlight();
209
+ await this.startQuery(this.query, this.filters);
210
+ }
211
+
212
+ protected setupOutputProvider = async () => {
213
+ this.outputsProvider.forEach((provider) => {
214
+ provider.dispose();
215
+ });
216
+ this.outputsProvider.length = 0;
217
+
218
+ this.currentProviderIndex = -1;
219
+ this.outputsProvider = this.cell.outputArea.outputs.map((output) => {
220
+ return this.genericSearchProviderFactory({ view: output });
221
+ });
222
+ if (this.isActive && this.query && this.filters?.searchCellOutput !== false) {
223
+ this.refresh();
224
+ }
225
+ this.stateChangedEmitter.fire();
226
+ };
227
+ }