@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.
- package/LICENSE +21 -0
- package/README.md +3 -0
- package/es/code-cell-search-protocol.d.ts +26 -0
- package/es/code-cell-search-protocol.d.ts.map +1 -0
- package/es/code-cell-search-protocol.js +3 -0
- package/es/code-cell-search-provider-contribution.d.ts +18 -0
- package/es/code-cell-search-provider-contribution.d.ts.map +1 -0
- package/es/code-cell-search-provider-contribution.js +64 -0
- package/es/code-cell-search-provider.d.ts +52 -0
- package/es/code-cell-search-provider.d.ts.map +1 -0
- package/es/code-cell-search-provider.js +423 -0
- package/es/code-editor-cell-search-provider.d.ts +138 -0
- package/es/code-editor-cell-search-provider.d.ts.map +1 -0
- package/es/code-editor-cell-search-provider.js +563 -0
- package/es/index.d.ts +5 -0
- package/es/index.d.ts.map +1 -0
- package/es/index.js +4 -0
- package/es/module.d.ts +3 -0
- package/es/module.d.ts.map +1 -0
- package/es/module.js +30 -0
- package/es/search-highlighter.d.ts +58 -0
- package/es/search-highlighter.d.ts.map +1 -0
- package/es/search-highlighter.js +241 -0
- package/package.json +57 -0
- package/src/code-cell-search-protocol.ts +40 -0
- package/src/code-cell-search-provider-contribution.ts +39 -0
- package/src/code-cell-search-provider.ts +227 -0
- package/src/code-editor-cell-search-provider.ts +377 -0
- package/src/index.spec.ts +10 -0
- package/src/index.ts +4 -0
- package/src/module.ts +46 -0
- package/src/search-highlighter.ts +207 -0
|
@@ -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
|
+
}
|