@taufik-nurrohman/text-editor.source 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright © 2021 Taufik Nurrohman <https://github.com/taufik-nurrohman>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the “Software”), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,6 @@
1
+ Text Editor Source
2
+ ==================
3
+
4
+ > Source code editor extension for [text editor](https://github.com/taufik-nurrohman/text-editor).
5
+
6
+ Provides a set of key strokes to generate responses like in a typical source code editor.
package/index.js ADDED
@@ -0,0 +1,429 @@
1
+ /*!
2
+ *
3
+ * The MIT License (MIT)
4
+ *
5
+ * Copyright © 2021 Taufik Nurrohman <https://github.com/taufik-nurrohman>
6
+ *
7
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ * of this software and associated documentation files (the “Software”), to deal
9
+ * in the Software without restriction, including without limitation the rights
10
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ * copies of the Software, and to permit persons to whom the Software is
12
+ * furnished to do so, subject to the following conditions:
13
+ *
14
+ * The above copyright notice and this permission notice shall be included in all
15
+ * copies or substantial portions of the Software.
16
+ *
17
+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ * SOFTWARE.
24
+ *
25
+ */
26
+ (function(global, factory) {
27
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.TE = global.TE || {}, global.TE.Source = {})));
28
+ })(this, function(exports) {
29
+ 'use strict';
30
+ var hasValue = function hasValue(x, data) {
31
+ return -1 !== data.indexOf(x);
32
+ };
33
+ var isArray = function isArray(x) {
34
+ return Array.isArray(x);
35
+ };
36
+ var isDefined = function isDefined(x) {
37
+ return 'undefined' !== typeof x;
38
+ };
39
+ var isInstance = function isInstance(x, of ) {
40
+ return x && isSet$1( of ) && x instanceof of ;
41
+ };
42
+ var isNull = function isNull(x) {
43
+ return null === x;
44
+ };
45
+ var isSet$1 = function isSet(x) {
46
+ return isDefined(x) && !isNull(x);
47
+ };
48
+ var isString = function isString(x) {
49
+ return 'string' === typeof x;
50
+ };
51
+ var toCount = function toCount(x) {
52
+ return x.length;
53
+ };
54
+ var toObjectValues = function toObjectValues(x) {
55
+ return Object.values(x);
56
+ };
57
+ var W = window;
58
+ var debounce = function debounce(then, time) {
59
+ var timer;
60
+ return function() {
61
+ var _arguments = arguments,
62
+ _this = this;
63
+ timer && clearTimeout(timer);
64
+ timer = setTimeout(function() {
65
+ return then.apply(_this, _arguments);
66
+ }, time);
67
+ };
68
+ };
69
+ var isPattern = function isPattern(pattern) {
70
+ return isInstance(pattern, RegExp);
71
+ };
72
+ var toPattern = function toPattern(pattern, opt) {
73
+ if (isPattern(pattern)) {
74
+ return pattern;
75
+ } // No need to escape `/` in the pattern string
76
+ pattern = pattern.replace(/\//g, '\\/');
77
+ return new RegExp(pattern, isSet$1(opt) ? opt : 'g');
78
+ };
79
+ const pairs = {
80
+ '`': '`',
81
+ '(': ')',
82
+ '{': '}',
83
+ '[': ']',
84
+ '"': '"',
85
+ "'": "'",
86
+ '<': '>'
87
+ };
88
+
89
+ function promisify(type, lot) {
90
+ return new Promise((resolve, reject) => {
91
+ let r = W[type].apply(W, lot);
92
+ return r ? resolve(r) : reject(r);
93
+ });
94
+ }
95
+ const defaults = {
96
+ source: {
97
+ pairs,
98
+ type: null
99
+ }
100
+ };
101
+ ['alert', 'confirm', 'prompt'].forEach(type => {
102
+ defaults.source[type] = (...lot) => promisify(type, lot);
103
+ });
104
+ const that = {};
105
+ that.toggle = function(open, close, wrap, tidy = false) {
106
+ if (!close && "" !== close) {
107
+ close = open;
108
+ }
109
+ let t = this,
110
+ {
111
+ after,
112
+ before,
113
+ value
114
+ } = t.$(),
115
+ closeCount = toCount(close),
116
+ openCount = toCount(open);
117
+ if (wrap && close === value.slice(-closeCount) && open === value.slice(0, openCount) || close === after.slice(0, closeCount) && open === before.slice(-openCount)) {
118
+ return t.peel(open, close, wrap);
119
+ }
120
+ if (false !== tidy) {
121
+ if (isString(tidy)) {
122
+ tidy = [tidy, tidy];
123
+ } else if (!isArray(tidy)) {
124
+ tidy = ["", ""];
125
+ }
126
+ if (!isSet(tidy[1])) {
127
+ tidy[1] = tidy[0];
128
+ }
129
+ t.trim(tidy[0], tidy[1]);
130
+ }
131
+ return t.wrap(open, close, wrap);
132
+ };
133
+ const CTRL_PREFIX = 'Control-';
134
+ const SHIFT_PREFIX = 'Shift-';
135
+
136
+ function canKeyDown(map, that) {
137
+ let charAfter,
138
+ charBefore,
139
+ charIndent = that.state.source.tab || that.state.tab || '\t',
140
+ charPairs = that.state.source.pairs || {},
141
+ charPairsValues = toObjectValues(charPairs),
142
+ {
143
+ key,
144
+ queue
145
+ } = map,
146
+ keyValue = map + ""; // Do nothing
147
+ if (queue.Alt || queue.Control) {
148
+ return true;
149
+ }
150
+ if (' ' === keyValue) {
151
+ let {
152
+ after,
153
+ before,
154
+ value
155
+ } = that.$();
156
+ charAfter = charPairs[charBefore = before.slice(-1)];
157
+ if (!value && charAfter && charBefore && charAfter === after[0]) {
158
+ that.wrap(' ', ' ');
159
+ return false;
160
+ }
161
+ return true;
162
+ }
163
+ if ('Enter' === keyValue) {
164
+ let {
165
+ after,
166
+ before,
167
+ value
168
+ } = that.$(),
169
+ lineBefore = before.split('\n').pop(),
170
+ lineMatch = lineBefore.match(/^(\s+)/),
171
+ lineMatchIndent = lineMatch && lineMatch[1] || "";
172
+ if (!value) {
173
+ if (after && before && (charAfter = charPairs[charBefore = before.slice(-1)]) && charAfter === after[0]) {
174
+ that.wrap('\n' + lineMatchIndent + (charBefore !== charAfter ? charIndent : ""), '\n' + lineMatchIndent).record();
175
+ return false;
176
+ }
177
+ if (lineMatchIndent) {
178
+ that.insert('\n' + lineMatchIndent, -1).record();
179
+ return false;
180
+ }
181
+ }
182
+ return true;
183
+ }
184
+ if ('Backspace' === keyValue) {
185
+ let {
186
+ after,
187
+ before,
188
+ value
189
+ } = that.$();
190
+ after.split('\n')[0];
191
+ let lineBefore = before.split('\n').pop(),
192
+ lineMatch = lineBefore.match(/^(\s+)/),
193
+ lineMatchIndent = lineMatch && lineMatch[1] || "";
194
+ charAfter = charPairs[charBefore = before.slice(-1)]; // Do nothing on escape
195
+ if ('\\' === charBefore) {
196
+ return true;
197
+ }
198
+ if (value) {
199
+ if (after && before && charAfter && charAfter === after[0] && !before.endsWith('\\' + charBefore)) {
200
+ that.record().peel(charBefore, charAfter).record();
201
+ return false;
202
+ }
203
+ return true;
204
+ }
205
+ charAfter = charPairs[charBefore = before.trim().slice(-1)];
206
+ if (charAfter && charBefore) {
207
+ if (after.startsWith(' ' + charAfter) && before.endsWith(charBefore + ' ') || after.startsWith('\n' + lineMatchIndent + charAfter) && before.endsWith(charBefore + '\n' + lineMatchIndent)) {
208
+ // Collapse bracket(s)
209
+ that.trim("", "").record();
210
+ return false;
211
+ }
212
+ } // Outdent
213
+ if (lineBefore.endsWith(charIndent)) {
214
+ that.pull(charIndent).record();
215
+ return false;
216
+ }
217
+ if (after && before && !before.endsWith('\\' + charBefore)) {
218
+ if (charAfter === after[0] && charBefore === before.slice(-1)) {
219
+ // Peel pair
220
+ that.peel(charBefore, charAfter).record();
221
+ return false;
222
+ }
223
+ }
224
+ return true;
225
+ }
226
+ let {
227
+ after,
228
+ before,
229
+ start,
230
+ value
231
+ } = that.$(); // Do nothing on escape
232
+ if ('\\' === (charBefore = before.slice(-1))) {
233
+ return true;
234
+ }
235
+ charAfter = hasValue(after[0], charPairsValues) ? after[0] : charPairs[charBefore]; // `|}`
236
+ if (!value && after && before && charAfter && key === charAfter) {
237
+ // Move to the next character
238
+ // `}|`
239
+ that.select(start + 1).record();
240
+ return false;
241
+ }
242
+ for (charBefore in charPairs) {
243
+ charAfter = charPairs[charBefore]; // `{|`
244
+ if (key === charBefore && charAfter) {
245
+ // Wrap pair or selection
246
+ // `{|}` `{|aaa|}`
247
+ that.wrap(charBefore, charAfter).record();
248
+ return false;
249
+ } // `|}`
250
+ if (key === charAfter) {
251
+ if (value) {
252
+ // Wrap selection
253
+ // `{|aaa|}`
254
+ that.record().wrap(charBefore, charAfter).record();
255
+ return false;
256
+ }
257
+ break;
258
+ }
259
+ }
260
+ return true;
261
+ }
262
+
263
+ function canKeyDownDent(map, that) {
264
+ let charIndent = that.state.source.tab || that.state.tab || '\t',
265
+ keyValue = map + ""; // Indent with `⌘+]`
266
+ if (CTRL_PREFIX + ']' === keyValue) {
267
+ that.push(charIndent).record();
268
+ return false;
269
+ } // Outdent with `⌘+[`
270
+ if (CTRL_PREFIX + '[' === keyValue) {
271
+ that.pull(charIndent).record();
272
+ return false;
273
+ }
274
+ return true;
275
+ }
276
+
277
+ function canKeyDownEnter(map, that) {
278
+ let {
279
+ key,
280
+ queue
281
+ } = map;
282
+ if (queue.Control && queue.Enter) {
283
+ let {
284
+ after,
285
+ before,
286
+ end,
287
+ start,
288
+ value
289
+ } = that.$(),
290
+ lineAfter = after.split('\n').shift(),
291
+ lineBefore = before.split('\n').pop(),
292
+ lineMatch = lineBefore.match(/^(\s+)/),
293
+ lineMatchIndent = lineMatch && lineMatch[1] || "";
294
+ if (before || after) {
295
+ if (queue.Shift) {
296
+ // Insert line over with `⌘+⇧+↵`
297
+ return that.select(start - toCount(lineBefore)).wrap(lineMatchIndent, '\n').insert(value).record(), false;
298
+ } // Insert line below with `⌘+↵`
299
+ return that.select(end + toCount(lineAfter)).wrap('\n' + lineMatchIndent, "").insert(value).record(), false;
300
+ }
301
+ }
302
+ return true;
303
+ }
304
+
305
+ function canKeyDownHistory(map, that) {
306
+ let keyValue = map + ""; // Redo with `⌘+y`
307
+ if (CTRL_PREFIX + 'y' === keyValue) {
308
+ return that.redo(), false;
309
+ } // Undo with `⌘+z`
310
+ if (CTRL_PREFIX + 'z' === keyValue) {
311
+ return that.undo(), false;
312
+ }
313
+ return true;
314
+ }
315
+
316
+ function canKeyDownMove(map, that) {
317
+ let {
318
+ key,
319
+ queue
320
+ } = map,
321
+ keyValue = map + "";
322
+ if (!queue.Control) {
323
+ return true;
324
+ }
325
+ let {
326
+ after,
327
+ before,
328
+ end,
329
+ start,
330
+ value
331
+ } = that.$(),
332
+ charPair,
333
+ charPairValue,
334
+ charPairs = that.state.source.pairs || {},
335
+ boundaries = [],
336
+ m;
337
+ if (value) {
338
+ for (charPair in charPairs) {
339
+ if (!(charPairValue = charPairs[charPair])) {
340
+ continue;
341
+ }
342
+ boundaries.push('(?:\\' + charPair + '(?:\\\\.|[^\\' + charPair + (charPairValue !== charPair ? '\\' + charPairValue : "") + '])*\\' + charPairValue + ')');
343
+ }
344
+ boundaries.push('\\w+'); // Word(s)
345
+ boundaries.push('\\s+'); // White-space(s)
346
+ boundaries.push('[\\s\\S]'); // Last try!
347
+ if (CTRL_PREFIX + 'ArrowLeft' === keyValue) {
348
+ if (m = before.match(toPattern('(' + boundaries.join('|') + ')$', ""))) {
349
+ that.insert("").select(start - toCount(m[0])).insert(value);
350
+ return that.record(), false;
351
+ }
352
+ return that.select(), false;
353
+ }
354
+ if (CTRL_PREFIX + 'ArrowRight' === keyValue) {
355
+ if (m = after.match(toPattern('^(' + boundaries.join('|') + ')', ""))) {
356
+ that.insert("").select(end + toCount(m[0]) - toCount(value)).insert(value);
357
+ return that.record(), false;
358
+ }
359
+ return that.select(), false;
360
+ }
361
+ }
362
+ let lineAfter = after.split('\n').shift(),
363
+ lineBefore = before.split('\n').pop(),
364
+ lineMatch = lineBefore.match(/^(\s+)/);
365
+ lineMatch && lineMatch[1] || ""; // Force to select the current line if there is no selection
366
+ end += toCount(lineAfter);
367
+ start -= toCount(lineBefore);
368
+ value = lineBefore + value + lineAfter;
369
+ if (CTRL_PREFIX + 'ArrowUp' === keyValue) {
370
+ if (!hasValue('\n', before)) {
371
+ return that.select(), false;
372
+ }
373
+ that.insert("");
374
+ that.replace(/^([^\n]*?)(\n|$)/, '$2', 1);
375
+ that.replace(/(^|\n)([^\n]*?)$/, "", -1);
376
+ let $ = that.$();
377
+ before = $.before;
378
+ start = $.start;
379
+ lineBefore = before.split('\n').pop();
380
+ that.select(start = start - toCount(lineBefore)).wrap(value, '\n');
381
+ that.select(start, start + toCount(value));
382
+ return that.record(), false;
383
+ }
384
+ if (CTRL_PREFIX + 'ArrowDown' === keyValue) {
385
+ if (!hasValue('\n', after)) {
386
+ return that.select(), false;
387
+ }
388
+ that.insert("");
389
+ that.replace(/^([^\n]*?)(\n|$)/, "", 1);
390
+ that.replace(/(^|\n)([^\n]*?)$/, '$1', -1);
391
+ let $ = that.$();
392
+ after = $.after;
393
+ end = $.end;
394
+ lineAfter = after.split('\n').shift();
395
+ that.select(end = end + toCount(lineAfter)).wrap('\n', value);
396
+ end += 1;
397
+ that.select(end, end + toCount(value));
398
+ return that.record(), false;
399
+ }
400
+ return true;
401
+ }
402
+
403
+ function canKeyDownTab(map, that) {
404
+ let charIndent = that.state.source.tab || that.state.tab || '\t',
405
+ keyValue = map + ""; // Indent with `⇥`
406
+ if ('Tab' === keyValue) {
407
+ return that.push(charIndent).record(), false;
408
+ } // Outdent with `⇧+⇥`
409
+ if (SHIFT_PREFIX + 'Tab' === keyValue) {
410
+ return that.pull(charIndent).record(), false;
411
+ }
412
+ return true;
413
+ }
414
+ let bounce = debounce(that => that.record(), 100);
415
+
416
+ function canKeyUp(map, that) {
417
+ return bounce(that), true;
418
+ }
419
+ const state = defaults;
420
+ exports.canKeyDown = canKeyDown;
421
+ exports.canKeyDownDent = canKeyDownDent;
422
+ exports.canKeyDownEnter = canKeyDownEnter;
423
+ exports.canKeyDownHistory = canKeyDownHistory;
424
+ exports.canKeyDownMove = canKeyDownMove;
425
+ exports.canKeyDownTab = canKeyDownTab;
426
+ exports.canKeyUp = canKeyUp;
427
+ exports.state = state;
428
+ exports.that = that;
429
+ });
package/index.min.js ADDED
@@ -0,0 +1,26 @@
1
+ /*!
2
+ *
3
+ * The MIT License (MIT)
4
+ *
5
+ * Copyright © 2021 Taufik Nurrohman <https://github.com/taufik-nurrohman>
6
+ *
7
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ * of this software and associated documentation files (the “Software”), to deal
9
+ * in the Software without restriction, including without limitation the rights
10
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ * copies of the Software, and to permit persons to whom the Software is
12
+ * furnished to do so, subject to the following conditions:
13
+ *
14
+ * The above copyright notice and this permission notice shall be included in all
15
+ * copies or substantial portions of the Software.
16
+ *
17
+ * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ * SOFTWARE.
24
+ *
25
+ */
26
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t(((e="undefined"!=typeof globalThis?globalThis:e||self).TE=e.TE||{},e.TE.Source={}))}(this,(function(e){"use strict";var t=function(e,t){return-1!==t.indexOf(e)},r=function(e){return function(e){return void 0!==e}(e)&&!function(e){return null===e}(e)},n=function(e){return e.length},o=window,i=function(e){return n=RegExp,(t=e)&&r(n)&&t instanceof n;var t,n},s=function(e,t){return i(e)?e:(e=e.replace(/\//g,"\\/"),RegExp(e,r(t)?t:"g"))};const c={source:{pairs:{"`":"`","(":")","{":"}","[":"]",'"':'"',"'":"'","<":">"},type:null}};["alert","confirm","prompt"].forEach((e=>{c.source[e]=(...t)=>function(e,t){return new Promise(((r,n)=>{let i=o[e].apply(o,t);return i?r(i):n(i)}))}(e,t)}));const l={};l.toggle=function(e,t,r,o=!1){t||""===t||(t=e);let i=this,{after:s,before:c,value:l}=i.$(),u=n(t),a=n(e);return r&&t===l.slice(-u)&&e===l.slice(0,a)||t===s.slice(0,u)&&e===c.slice(-a)?i.peel(e,t,r):(!1!==o&&("string"==typeof o?o=[o,o]:function(e){return Array.isArray(e)}(o)||(o=["",""]),isSet(o[1])||(o[1]=o[0]),i.trim(o[0],o[1])),i.wrap(e,t,r))};let u=(a=e=>e.record(),f=100,function(){var e=arguments,t=this;p&&clearTimeout(p),p=setTimeout((function(){return a.apply(t,e)}),f)});var a,f,p;const d=c;e.canKeyDown=function(e,r){let n,o,i=r.state.source.tab||r.state.tab||"\t",s=r.state.source.pairs||{},c=Object.values(s),{key:l,queue:u}=e,a=e+"";if(u.Alt||u.Control)return!0;if(" "===a){let{after:e,before:t,value:i}=r.$();return n=s[o=t.slice(-1)],!(!i&&n&&o&&n===e[0])||(r.wrap(" "," "),!1)}if("Enter"===a){let{after:e,before:t,value:c}=r.$(),l=t.split("\n").pop().match(/^(\s+)/),u=l&&l[1]||"";if(!c){if(e&&t&&(n=s[o=t.slice(-1)])&&n===e[0])return r.wrap("\n"+u+(o!==n?i:""),"\n"+u).record(),!1;if(u)return r.insert("\n"+u,-1).record(),!1}return!0}if("Backspace"===a){let{after:e,before:t,value:c}=r.$();e.split("\n")[0];let l=t.split("\n").pop(),u=l.match(/^(\s+)/),a=u&&u[1]||"";return n=s[o=t.slice(-1)],"\\"===o?!0:c?!(e&&t&&n&&n===e[0]&&!t.endsWith("\\"+o))||(r.record().peel(o,n).record(),!1):(n=s[o=t.trim().slice(-1)],n&&o&&(e.startsWith(" "+n)&&t.endsWith(o+" ")||e.startsWith("\n"+a+n)&&t.endsWith(o+"\n"+a))?(r.trim("","").record(),!1):l.endsWith(i)?(r.pull(i).record(),!1):!(e&&t&&!t.endsWith("\\"+o)&&n===e[0]&&o===t.slice(-1))||(r.peel(o,n).record(),!1))}let{after:f,before:p,start:d,value:h}=r.$();if("\\"===(o=p.slice(-1)))return!0;if(n=t(f[0],c)?f[0]:s[o],!h&&f&&p&&n&&l===n)return r.select(d+1).record(),!1;for(o in s){if(n=s[o],l===o&&n)return r.wrap(o,n).record(),!1;if(l===n){if(h)return r.record().wrap(o,n).record(),!1;break}}return!0},e.canKeyDownDent=function(e,t){let r=t.state.source.tab||t.state.tab||"\t",n=e+"";return"Control-]"===n?(t.push(r).record(),!1):"Control-["!==n||(t.pull(r).record(),!1)},e.canKeyDownEnter=function(e,t){let{key:r,queue:o}=e;if(o.Control&&o.Enter){let{after:e,before:r,end:i,start:s,value:c}=t.$(),l=e.split("\n").shift(),u=r.split("\n").pop(),a=u.match(/^(\s+)/),f=a&&a[1]||"";if(r||e)return o.Shift?(t.select(s-n(u)).wrap(f,"\n").insert(c).record(),!1):(t.select(i+n(l)).wrap("\n"+f,"").insert(c).record(),!1)}return!0},e.canKeyDownHistory=function(e,t){let r=e+"";return"Control-y"===r?(t.redo(),!1):"Control-z"!==r||(t.undo(),!1)},e.canKeyDownMove=function(e,r){let{key:o,queue:i}=e,c=e+"";if(!i.Control)return!0;let l,u,a,{after:f,before:p,end:d,start:h,value:w}=r.$(),b=r.state.source.pairs||{},y=[];if(w){for(l in b)(u=b[l])&&y.push("(?:\\"+l+"(?:\\\\.|[^\\"+l+(u!==l?"\\"+u:"")+"])*\\"+u+")");if(y.push("\\w+"),y.push("\\s+"),y.push("[\\s\\S]"),"Control-ArrowLeft"===c)return(a=p.match(s("("+y.join("|")+")$","")))?(r.insert("").select(h-n(a[0])).insert(w),r.record(),!1):(r.select(),!1);if("Control-ArrowRight"===c)return(a=f.match(s("^("+y.join("|")+")","")))?(r.insert("").select(d+n(a[0])-n(w)).insert(w),r.record(),!1):(r.select(),!1)}let m=f.split("\n").shift(),$=p.split("\n").pop(),v=$.match(/^(\s+)/);if(v&&v[1],d+=n(m),h-=n($),w=$+w+m,"Control-ArrowUp"===c){if(!t("\n",p))return r.select(),!1;r.insert(""),r.replace(/^([^\n]*?)(\n|$)/,"$2",1),r.replace(/(^|\n)([^\n]*?)$/,"",-1);let e=r.$();return p=e.before,h=e.start,$=p.split("\n").pop(),r.select(h-=n($)).wrap(w,"\n"),r.select(h,h+n(w)),r.record(),!1}if("Control-ArrowDown"===c){if(!t("\n",f))return r.select(),!1;r.insert(""),r.replace(/^([^\n]*?)(\n|$)/,"",1),r.replace(/(^|\n)([^\n]*?)$/,"$1",-1);let e=r.$();return f=e.after,d=e.end,m=f.split("\n").shift(),r.select(d+=n(m)).wrap("\n",w),d+=1,r.select(d,d+n(w)),r.record(),!1}return!0},e.canKeyDownTab=function(e,t){let r=t.state.source.tab||t.state.tab||"\t",n=e+"";return"Tab"===n?(t.push(r).record(),!1):"Shift-Tab"!==n||(t.pull(r).record(),!1)},e.canKeyUp=function(e,t){return u(t),!0},e.state=d,e.that=l}));
package/index.mjs ADDED
@@ -0,0 +1,334 @@
1
+ import {W} from '@taufik-nurrohman/document';
2
+ import {debounce} from '@taufik-nurrohman/tick';
3
+ import {esc, escChar, toPattern} from '@taufik-nurrohman/pattern';
4
+ import {hasValue} from '@taufik-nurrohman/has';
5
+ import {isArray, isString} from '@taufik-nurrohman/is';
6
+ import {toCount, toObjectValues} from '@taufik-nurrohman/to';
7
+
8
+ const pairs = {
9
+ '`': '`',
10
+ '(': ')',
11
+ '{': '}',
12
+ '[': ']',
13
+ '"': '"',
14
+ "'": "'",
15
+ '<': '>'
16
+ };
17
+
18
+ function promisify(type, lot) {
19
+ return new Promise((resolve, reject) => {
20
+ let r = W[type].apply(W, lot);
21
+ return r ? resolve(r) : reject(r);
22
+ });
23
+ }
24
+
25
+ const defaults = {
26
+ source: {
27
+ pairs,
28
+ type: null
29
+ }
30
+ };
31
+
32
+ ['alert', 'confirm', 'prompt'].forEach(type => {
33
+ defaults.source[type] = (...lot) => promisify(type, lot);
34
+ });
35
+
36
+ export const that = {};
37
+
38
+ that.toggle = function(open, close, wrap, tidy = false) {
39
+ if (!close && "" !== close) {
40
+ close = open;
41
+ }
42
+ let t = this,
43
+ {after, before, value} = t.$(),
44
+ closeCount = toCount(close),
45
+ openCount = toCount(open);
46
+ if (
47
+ (wrap && close === value.slice(-closeCount) && open === value.slice(0, openCount)) ||
48
+ (close === after.slice(0, closeCount) && open === before.slice(-openCount))
49
+ ) {
50
+ return t.peel(open, close, wrap);
51
+ }
52
+ if (false !== tidy) {
53
+ if (isString(tidy)) {
54
+ tidy = [tidy, tidy];
55
+ } else if (!isArray(tidy)) {
56
+ tidy = ["", ""];
57
+ }
58
+ if (!isSet(tidy[1])) {
59
+ tidy[1] = tidy[0];
60
+ }
61
+ t.trim(tidy[0], tidy[1]);
62
+ }
63
+ return t.wrap(open, close, wrap);
64
+ };
65
+
66
+ const ALT_PREFIX = 'Alt-';
67
+ const CTRL_PREFIX = 'Control-';
68
+ const SHIFT_PREFIX = 'Shift-';
69
+
70
+ export function canKeyDown(map, that) {
71
+ let charAfter,
72
+ charBefore,
73
+ charIndent = that.state.source.tab || that.state.tab || '\t',
74
+ charPairs = that.state.source.pairs || {},
75
+ charPairsValues = toObjectValues(charPairs),
76
+ {key, queue} = map,
77
+ keyValue = map + "";
78
+ // Do nothing
79
+ if (queue.Alt || queue.Control) {
80
+ return true;
81
+ }
82
+ if (' ' === keyValue) {
83
+ let {after, before, value} = that.$();
84
+ charAfter = charPairs[charBefore = before.slice(-1)];
85
+ if (!value && charAfter && charBefore && charAfter === after[0]) {
86
+ that.wrap(' ', ' ');
87
+ return false;
88
+ }
89
+ return true;
90
+ }
91
+ if ('Enter' === keyValue) {
92
+ let {after, before, value} = that.$(),
93
+ lineBefore = before.split('\n').pop(),
94
+ lineMatch = lineBefore.match(/^(\s+)/),
95
+ lineMatchIndent = lineMatch && lineMatch[1] || "";
96
+ if (!value) {
97
+ if (after && before && (charAfter = charPairs[charBefore = before.slice(-1)]) && charAfter === after[0]) {
98
+ that.wrap('\n' + lineMatchIndent + (charBefore !== charAfter ? charIndent : ""), '\n' + lineMatchIndent).record();
99
+ return false;
100
+ }
101
+ if (lineMatchIndent) {
102
+ that.insert('\n' + lineMatchIndent, -1).record();
103
+ return false;
104
+ }
105
+ }
106
+ return true;
107
+ }
108
+ if ('Backspace' === keyValue) {
109
+ let {after, before, value} = that.$(),
110
+ lineAfter = after.split('\n')[0],
111
+ lineBefore = before.split('\n').pop(),
112
+ lineMatch = lineBefore.match(/^(\s+)/),
113
+ lineMatchIndent = lineMatch && lineMatch[1] || "";
114
+ charAfter = charPairs[charBefore = before.slice(-1)];
115
+ // Do nothing on escape
116
+ if ('\\' === charBefore) {
117
+ return true;
118
+ }
119
+ if (value) {
120
+ if (after && before && charAfter && charAfter === after[0] && !before.endsWith('\\' + charBefore)) {
121
+ that.record().peel(charBefore, charAfter).record();
122
+ return false;
123
+ }
124
+ return true;
125
+ }
126
+ charAfter = charPairs[charBefore = before.trim().slice(-1)];
127
+ if (charAfter && charBefore) {
128
+ if (
129
+ after.startsWith(' ' + charAfter) && before.endsWith(charBefore + ' ') ||
130
+ after.startsWith('\n' + lineMatchIndent + charAfter) && before.endsWith(charBefore + '\n' + lineMatchIndent)
131
+ ) {
132
+ // Collapse bracket(s)
133
+ that.trim("", "").record();
134
+ return false;
135
+ }
136
+ }
137
+ // Outdent
138
+ if (lineBefore.endsWith(charIndent)) {
139
+ that.pull(charIndent).record();
140
+ return false;
141
+ }
142
+ if (after && before && !before.endsWith('\\' + charBefore)) {
143
+ if (charAfter === after[0] && charBefore === before.slice(-1)) {
144
+ // Peel pair
145
+ that.peel(charBefore, charAfter).record();
146
+ return false;
147
+ }
148
+ }
149
+ return true;
150
+ }
151
+ let {after, before, start, value} = that.$();
152
+ // Do nothing on escape
153
+ if ('\\' === (charBefore = before.slice(-1))) {
154
+ return true;
155
+ }
156
+ charAfter = hasValue(after[0], charPairsValues) ? after[0] : charPairs[charBefore];
157
+ // `|}`
158
+ if (!value && after && before && charAfter && key === charAfter) {
159
+ // Move to the next character
160
+ // `}|`
161
+ that.select(start + 1).record();
162
+ return false;
163
+ }
164
+ for (charBefore in charPairs) {
165
+ charAfter = charPairs[charBefore];
166
+ // `{|`
167
+ if (key === charBefore && charAfter) {
168
+ // Wrap pair or selection
169
+ // `{|}` `{|aaa|}`
170
+ that.wrap(charBefore, charAfter).record();
171
+ return false;
172
+ }
173
+ // `|}`
174
+ if (key === charAfter) {
175
+ if (value) {
176
+ // Wrap selection
177
+ // `{|aaa|}`
178
+ that.record().wrap(charBefore, charAfter).record();
179
+ return false;
180
+ }
181
+ break;
182
+ }
183
+ }
184
+ return true;
185
+ }
186
+
187
+ export function canKeyDownDent(map, that) {
188
+ let charIndent = that.state.source.tab || that.state.tab || '\t',
189
+ {key, queue} = map,
190
+ keyValue = map + "";
191
+ // Indent with `⌘+]`
192
+ if (CTRL_PREFIX + ']' === keyValue) {
193
+ that.push(charIndent).record();
194
+ return false;
195
+ }
196
+ // Outdent with `⌘+[`
197
+ if (CTRL_PREFIX + '[' === keyValue) {
198
+ that.pull(charIndent).record();
199
+ return false;
200
+ }
201
+ return true;
202
+ }
203
+
204
+ export function canKeyDownEnter(map, that) {
205
+ let {key, queue} = map;
206
+ if (queue.Control && queue.Enter) {
207
+ let {after, before, end, start, value} = that.$(),
208
+ lineAfter = after.split('\n').shift(),
209
+ lineBefore = before.split('\n').pop(),
210
+ lineMatch = lineBefore.match(/^(\s+)/),
211
+ lineMatchIndent = lineMatch && lineMatch[1] || "";
212
+ if (before || after) {
213
+ if (queue.Shift) {
214
+ // Insert line over with `⌘+⇧+↵`
215
+ return that.select(start - toCount(lineBefore)).wrap(lineMatchIndent, '\n').insert(value).record(), false;
216
+ }
217
+ // Insert line below with `⌘+↵`
218
+ return that.select(end + toCount(lineAfter)).wrap('\n' + lineMatchIndent, "").insert(value).record(), false;
219
+ }
220
+ }
221
+ return true;
222
+ }
223
+
224
+ export function canKeyDownHistory(map, that) {
225
+ let keyValue = map + "";
226
+ // Redo with `⌘+y`
227
+ if (CTRL_PREFIX + 'y' === keyValue) {
228
+ return that.redo(), false;
229
+ }
230
+ // Undo with `⌘+z`
231
+ if (CTRL_PREFIX + 'z' === keyValue) {
232
+ return that.undo(), false;
233
+ }
234
+ return true;
235
+ }
236
+
237
+ export function canKeyDownMove(map, that) {
238
+ let {key, queue} = map,
239
+ keyValue = map + "";
240
+ if (!queue.Control) {
241
+ return true;
242
+ }
243
+ let {after, before, end, start, value} = that.$(),
244
+ charPair, charPairValue,
245
+ charPairs = that.state.source.pairs || {},
246
+ boundaries = [], m;
247
+ if (value) {
248
+ for (charPair in charPairs) {
249
+ if (!(charPairValue = charPairs[charPair])) {
250
+ continue;
251
+ }
252
+ boundaries.push('(?:\\' + charPair + '(?:\\\\.|[^\\' + charPair + (charPairValue !== charPair ? '\\' + charPairValue : "") + '])*\\' + charPairValue + ')');
253
+ }
254
+ boundaries.push('\\w+'); // Word(s)
255
+ boundaries.push('\\s+'); // White-space(s)
256
+ boundaries.push('[\\s\\S]'); // Last try!
257
+ if (CTRL_PREFIX + 'ArrowLeft' === keyValue) {
258
+ if (m = before.match(toPattern('(' + boundaries.join('|') + ')$', ""))) {
259
+ that.insert("").select(start - toCount(m[0])).insert(value);
260
+ return that.record(), false;
261
+ }
262
+ return that.select(), false;
263
+ }
264
+ if (CTRL_PREFIX + 'ArrowRight' === keyValue) {
265
+ if (m = after.match(toPattern('^(' + boundaries.join('|') + ')', ""))) {
266
+ that.insert("").select(end + toCount(m[0]) - toCount(value)).insert(value);
267
+ return that.record(), false;
268
+ }
269
+ return that.select(), false;
270
+ }
271
+ }
272
+ let lineAfter = after.split('\n').shift(),
273
+ lineBefore = before.split('\n').pop(),
274
+ lineMatch = lineBefore.match(/^(\s+)/),
275
+ lineMatchIndent = lineMatch && lineMatch[1] || "";
276
+ // Force to select the current line if there is no selection
277
+ end += toCount(lineAfter);
278
+ start -= toCount(lineBefore);
279
+ value = lineBefore + value + lineAfter;
280
+ if (CTRL_PREFIX + 'ArrowUp' === keyValue) {
281
+ if (!hasValue('\n', before)) {
282
+ return that.select(), false;
283
+ }
284
+ that.insert("");
285
+ that.replace(/^([^\n]*?)(\n|$)/, '$2', 1);
286
+ that.replace(/(^|\n)([^\n]*?)$/, "", -1);
287
+ let $ = that.$();
288
+ before = $.before;
289
+ start = $.start;
290
+ lineBefore = before.split('\n').pop();
291
+ that.select(start = start - toCount(lineBefore)).wrap(value, '\n');
292
+ that.select(start, start + toCount(value));
293
+ return that.record(), false;
294
+ }
295
+ if (CTRL_PREFIX + 'ArrowDown' === keyValue) {
296
+ if (!hasValue('\n', after)) {
297
+ return that.select(), false;
298
+ }
299
+ that.insert("");
300
+ that.replace(/^([^\n]*?)(\n|$)/, "", 1);
301
+ that.replace(/(^|\n)([^\n]*?)$/, '$1', -1);
302
+ let $ = that.$();
303
+ after = $.after;
304
+ end = $.end;
305
+ lineAfter = after.split('\n').shift();
306
+ that.select(end = end + toCount(lineAfter)).wrap('\n', value);
307
+ end += 1;
308
+ that.select(end, end + toCount(value));
309
+ return that.record(), false;
310
+ }
311
+ return true;
312
+ }
313
+
314
+ export function canKeyDownTab(map, that) {
315
+ let charIndent = that.state.source.tab || that.state.tab || '\t',
316
+ keyValue = map + "";
317
+ // Indent with `⇥`
318
+ if ('Tab' === keyValue) {
319
+ return that.push(charIndent).record(), false;
320
+ }
321
+ // Outdent with `⇧+⇥`
322
+ if (SHIFT_PREFIX + 'Tab' === keyValue) {
323
+ return that.pull(charIndent).record(), false;
324
+ }
325
+ return true;
326
+ }
327
+
328
+ let bounce = debounce(that => that.record(), 100);
329
+
330
+ export function canKeyUp(map, that) {
331
+ return bounce(that), true;
332
+ }
333
+
334
+ export const state = defaults;
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "author": "Taufik Nurrohman",
3
+ "browser": "index.min.js",
4
+ "bugs": "https://github.com/taufik-nurrohman/text-editor.source/issues",
5
+ "dependencies": {
6
+ "@taufik-nurrohman/has": "^0.0.3",
7
+ "@taufik-nurrohman/key": "^2.0.4",
8
+ "@taufik-nurrohman/pattern": "^0.1.7",
9
+ "@taufik-nurrohman/text-editor": "^3.3.9",
10
+ "@taufik-nurrohman/text-editor.history": "^2.1.11",
11
+ "@taufik-nurrohman/tick": "^0.0.2",
12
+ "@taufik-nurrohman/to": "^0.0.18"
13
+ },
14
+ "description": "Provides a set of key strokes to generate responses like in a typical source code editor.",
15
+ "devDependencies": {
16
+ "@taufik-nurrohman/factory": "^0.1.20"
17
+ },
18
+ "exports": {
19
+ "import": "./index.mjs",
20
+ "require": "./index.js"
21
+ },
22
+ "files": [
23
+ "index.js",
24
+ "index.min.js",
25
+ "index.mjs"
26
+ ],
27
+ "funding": {
28
+ "url": "https://paypal.me/tatautaufik"
29
+ },
30
+ "homepage": "https://taufik-nurrohman.js.org/text-editor",
31
+ "keywords": [
32
+ "code",
33
+ "editor",
34
+ "extension",
35
+ "ide",
36
+ "range",
37
+ "selection",
38
+ "source",
39
+ "text"
40
+ ],
41
+ "license": "MIT",
42
+ "main": "index.js",
43
+ "module": "index.mjs",
44
+ "name": "@taufik-nurrohman/text-editor.source",
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/taufik-nurrohman/text-editor.source.git"
48
+ },
49
+ "scripts": {
50
+ "pack": "pack --clean=false --from=.github/source --js-format=umd --js-name=TE.Source --js-top='%(js.license)' --mjs=true --to=."
51
+ },
52
+ "type": "module",
53
+ "version": "2.2.0"
54
+ }