lexgui 0.7.1 → 0.7.3
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/build/extensions/codeeditor.js +1756 -1059
- package/build/extensions/videoeditor.js +21 -7
- package/build/lexgui.css +31 -53
- package/build/lexgui.js +12 -14
- package/build/lexgui.min.css +2 -2
- package/build/lexgui.min.js +1 -1
- package/build/lexgui.module.js +12 -14
- package/build/lexgui.module.min.js +1 -1
- package/changelog.md +28 -1
- package/examples/code-editor.html +12 -3
- package/examples/video-editor.html +2 -1
- package/examples/video-editor2.html +5 -4
- package/package.json +1 -1
|
@@ -6,26 +6,13 @@ if(!LX) {
|
|
|
6
6
|
|
|
7
7
|
LX.extensions.push( 'CodeEditor' );
|
|
8
8
|
|
|
9
|
-
function swapElements( obj, a, b ) {
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function
|
|
14
|
-
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
function sliceChars( str, idx, n = 1 ) {
|
|
18
|
-
return str.substr(0, idx) + str.substr(idx + n);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function firstNonspaceIndex( str ) {
|
|
22
|
-
const index = str.search(/\S|$/)
|
|
23
|
-
return index < str.length ? index : -1;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function strReverse( str ) {
|
|
27
|
-
return str.split( "" ).reverse().join( "" );
|
|
28
|
-
}
|
|
9
|
+
function swapElements( obj, a, b ) { [obj[a], obj[b]] = [obj[b], obj[a]]; }
|
|
10
|
+
function swapArrayElements( array, id0, id1 ) { [array[id0], array[id1]] = [array[id1], array[id0]]; };
|
|
11
|
+
function sliceChars( str, idx, n = 1 ) { return str.substr(0, idx) + str.substr(idx + n); }
|
|
12
|
+
function firstNonspaceIndex( str ) { const index = str.search(/\S|$/); return index < str.length ? index : -1; }
|
|
13
|
+
function strReverse( str ) { return str.split( "" ).reverse().join( "" ); }
|
|
14
|
+
function isLetter( c ){ return /[a-zA-Z]/.test( c ); };
|
|
15
|
+
function isSymbol( c ){ return /[^\w\s]/.test( c ); };
|
|
29
16
|
|
|
30
17
|
function indexOfFrom( str, reg, from, reverse ) {
|
|
31
18
|
|
|
@@ -46,13 +33,13 @@ function indexOfFrom( str, reg, from, reverse ) {
|
|
|
46
33
|
}
|
|
47
34
|
}
|
|
48
35
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
36
|
+
function codeScopesEqual( a, b ) {
|
|
37
|
+
if( a.length !== b.length ) return false;
|
|
38
|
+
for( let i = 0; i < a.length; i++ )
|
|
39
|
+
{
|
|
40
|
+
if( a[ i ].type !== b[ i ].type ) return false;
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
56
43
|
}
|
|
57
44
|
|
|
58
45
|
class CodeSelection {
|
|
@@ -104,7 +91,6 @@ class CodeSelection {
|
|
|
104
91
|
|
|
105
92
|
var domEl = document.createElement( 'div' );
|
|
106
93
|
domEl.className = this.className;
|
|
107
|
-
|
|
108
94
|
domEl._top = y * this.editor.lineHeight;
|
|
109
95
|
domEl.style.top = domEl._top + "px";
|
|
110
96
|
domEl._left = x * this.editor.charWidth;
|
|
@@ -156,6 +142,9 @@ class ScrollBar {
|
|
|
156
142
|
static SCROLLBAR_VERTICAL = 1;
|
|
157
143
|
static SCROLLBAR_HORIZONTAL = 2;
|
|
158
144
|
|
|
145
|
+
static SCROLLBAR_VERTICAL_WIDTH = 10;
|
|
146
|
+
static SCROLLBAR_HORIZONTAL_HEIGHT = 10;
|
|
147
|
+
|
|
159
148
|
constructor( editor, type ) {
|
|
160
149
|
|
|
161
150
|
this.editor = editor;
|
|
@@ -213,6 +202,80 @@ class ScrollBar {
|
|
|
213
202
|
|
|
214
203
|
}
|
|
215
204
|
|
|
205
|
+
/* Highlight rules
|
|
206
|
+
- test: function that receives a context object and returns true or false
|
|
207
|
+
- className: class to apply if test is true
|
|
208
|
+
- action: optional function to execute if test is true, receives context and editor as parameter
|
|
209
|
+
- discard: optional boolean, if true the token is discarded, action value is returned
|
|
210
|
+
to "ctx.discardToken" and no class is applied
|
|
211
|
+
*/
|
|
212
|
+
|
|
213
|
+
const HighlightRules = {
|
|
214
|
+
|
|
215
|
+
common: [
|
|
216
|
+
{ test: ctx => ctx.inBlockComment, className: "cm-com" },
|
|
217
|
+
{ test: ctx => ctx.inString, action: (ctx, editor) => editor._appendStringToken( ctx.token ), discard: true },
|
|
218
|
+
{ test: ctx => ctx.token.substr( 0, ctx.singleLineCommentToken.length ) == ctx.singleLineCommentToken, className: "cm-com" },
|
|
219
|
+
{ test: (ctx, editor) => editor._isKeyword( ctx ), className: "cm-kwd" },
|
|
220
|
+
{ test: (ctx, editor) => editor._mustHightlightWord( ctx.token, CodeEditor.builtIn, ctx.lang ) && ( ctx.lang.tags ?? false ? ( editor._enclosedByTokens( ctx.token, ctx.tokenIndex, '<', '>' ) ) : true ), className: "cm-bln" },
|
|
221
|
+
{ test: (ctx, editor) => editor._mustHightlightWord( ctx.token, CodeEditor.statements, ctx.lang ), className: "cm-std" },
|
|
222
|
+
{ test: (ctx, editor) => editor._mustHightlightWord( ctx.token, CodeEditor.symbols, ctx.lang ), className: "cm-sym" },
|
|
223
|
+
{ test: (ctx, editor) => editor._mustHightlightWord( ctx.token, CodeEditor.types, ctx.lang ), className: "cm-typ" },
|
|
224
|
+
{ test: (ctx, editor) => editor._isNumber( ctx.token ) || editor._isNumber( ctx.token.replace(/[px]|[em]|%/g,'') ), className: "cm-dec" },
|
|
225
|
+
{ test: ctx => ctx.lang.usePreprocessor && ctx.token.includes( '#' ), className: "cm-ppc" },
|
|
226
|
+
],
|
|
227
|
+
|
|
228
|
+
javascript: [
|
|
229
|
+
{ test: ctx => (ctx.prev === 'class' && ctx.next === '{') , className: "cm-typ" },
|
|
230
|
+
],
|
|
231
|
+
|
|
232
|
+
typescript: [
|
|
233
|
+
{ test: ctx => ctx.scope && (ctx.token !== ',' && ctx.scope.type == "enum"), className: "cm-enu" },
|
|
234
|
+
{ test: ctx => (ctx.prev === ':' && ctx.next !== undefined && isLetter(ctx.token) ) || (ctx.prev === 'interface' && ctx.next === '{') || (ctx.prev === 'enum' && ctx.next === '{'), className: "cm-typ" },
|
|
235
|
+
{ test: ctx => (ctx.prev === 'class' && ctx.next === '{') || (ctx.prev === 'class' && ctx.next === '<') || (ctx.prev === 'new' && ctx.next === '(') || (ctx.prev === 'new' && ctx.next === '<'), className: "cm-typ" },
|
|
236
|
+
{ test: (ctx, editor) => ctx.token !== ',' && editor._enclosedByTokens( ctx.token, ctx.tokenIndex, '<', '>' ), className: "cm-typ" },
|
|
237
|
+
],
|
|
238
|
+
|
|
239
|
+
cpp: [
|
|
240
|
+
{ test: ctx => ctx.scope && (ctx.token !== ',' && ctx.scope.type == "enum"), className: "cm-enu" },
|
|
241
|
+
{ test: ctx => ctx.isEnumValueSymbol( ctx.token ), className: "cm-enu" },
|
|
242
|
+
{ test: ctx => (ctx.prev === 'class' && ctx.next === '{') || (ctx.prev === 'struct' && ctx.next === '{'), className: "cm-typ" },
|
|
243
|
+
{ test: ctx => ctx.prev === "<" && (ctx.next === ">" || ctx.next === "*"), className: "cm-typ" }, // Defining template type in C++
|
|
244
|
+
{ test: ctx => ctx.next === "::" || (ctx.prev === "::" && ctx.next !== "("), className: "cm-typ" }, // C++ Class
|
|
245
|
+
{ test: ctx => ctx.isClassSymbol( ctx.token ) || ctx.isStructSymbol( ctx.token ), className: "cm-typ" },
|
|
246
|
+
],
|
|
247
|
+
|
|
248
|
+
wgsl: [
|
|
249
|
+
{ test: ctx => ctx.prev === '>' && (!ctx.next || ctx.next === '{'), className: "cm-typ" }, // Function return type
|
|
250
|
+
{ test: ctx => (ctx.prev === ':' && ctx.next !== undefined) || (ctx.prev === 'struct' && ctx.next === '{'), className: "cm-typ" },
|
|
251
|
+
{ test: (ctx, editor) => ctx.token !== ',' && editor._enclosedByTokens( ctx.token, ctx.tokenIndex, '<', '>' ), className: "cm-typ" },
|
|
252
|
+
],
|
|
253
|
+
|
|
254
|
+
css: [
|
|
255
|
+
{ test: ctx => ( ctx.prev == '.' || ctx.prev == '::' || ( ctx.prev == ':' && ctx.next == '{' ) || ( ctx.token[ 0 ] == '#' && ctx.prev != ':' ) ), className: "cm-kwd" },
|
|
256
|
+
{ test: ctx => ctx.prev === ':' && (ctx.next === ';' || ctx.next === '!important'), className: "cm-str" }, // CSS value
|
|
257
|
+
{ test: ctx => ( ctx.prev === undefined || ctx.prev === '{' || ctx.prev === ';' ) && ctx.next === ":", className: "cm-typ" }, // CSS attribute
|
|
258
|
+
{ test: ctx => ctx.prev === "(" && ctx.next === ")" && ctx.token.startsWith( "--" ), className: "cm-typ" }, // CSS vars
|
|
259
|
+
],
|
|
260
|
+
|
|
261
|
+
batch: [
|
|
262
|
+
{ test: ctx => ctx.token === '@' || ctx.prev === ':' || ctx.prev === '@', className: "cm-kwd" }
|
|
263
|
+
],
|
|
264
|
+
|
|
265
|
+
markdown: [
|
|
266
|
+
{ test: ctx => ctx.isFirstToken && ctx.token.replaceAll('#', '').length != ctx.token.length, action: (ctx, editor) => editor._markdownHeader = true, className: "cm-kwd" }
|
|
267
|
+
],
|
|
268
|
+
|
|
269
|
+
php: [
|
|
270
|
+
{ test: ctx => ctx.token.startsWith( '$' ), className: "cm-var" },
|
|
271
|
+
{ test: ctx => (ctx.prev === 'class' && (ctx.next === '{' || ctx.next === 'implements') ) || (ctx.prev === 'enum'), className: "cm-typ" },
|
|
272
|
+
],
|
|
273
|
+
|
|
274
|
+
post_common: [
|
|
275
|
+
{ test: ctx => isLetter(ctx.token) && (ctx.token[ 0 ] != '@') && (ctx.token[ 0 ] != ',') && (ctx.next === '('), className: "cm-mtd" }
|
|
276
|
+
],
|
|
277
|
+
};
|
|
278
|
+
|
|
216
279
|
/**
|
|
217
280
|
* @class CodeEditor
|
|
218
281
|
*/
|
|
@@ -238,8 +301,13 @@ class CodeEditor {
|
|
|
238
301
|
static CODE_MIN_FONT_SIZE = 9;
|
|
239
302
|
static CODE_MAX_FONT_SIZE = 22;
|
|
240
303
|
|
|
304
|
+
static LINE_GUTTER_WIDTH = 48;
|
|
241
305
|
static LINE_GUTTER_WIDTH = 48;
|
|
242
306
|
|
|
307
|
+
static RESIZE_SCROLLBAR_H = 1;
|
|
308
|
+
static RESIZE_SCROLLBAR_V = 2;
|
|
309
|
+
static RESIZE_SCROLLBAR_H_V = CodeEditor.RESIZE_SCROLLBAR_H | CodeEditor.RESIZE_SCROLLBAR_V;
|
|
310
|
+
|
|
243
311
|
/**
|
|
244
312
|
* @param {*} options
|
|
245
313
|
* name:
|
|
@@ -255,11 +323,17 @@ class CodeEditor {
|
|
|
255
323
|
|
|
256
324
|
CodeEditor.__instances.push( this );
|
|
257
325
|
|
|
326
|
+
this.skipInfo = options.skipInfo ?? false;
|
|
327
|
+
this.disableEdition = options.disableEdition ?? false;
|
|
328
|
+
this.skipTabs = options.skipTabs ?? false;
|
|
329
|
+
this.useFileExplorer = ( options.fileExplorer ?? false ) && !this.skipTabs;
|
|
330
|
+
this.useAutoComplete = options.autocomplete ?? true;
|
|
331
|
+
|
|
258
332
|
// File explorer
|
|
259
|
-
if(
|
|
333
|
+
if( this.useFileExplorer )
|
|
260
334
|
{
|
|
261
|
-
|
|
262
|
-
explorerArea.setLimitBox( 180, 20, 512 );
|
|
335
|
+
let [ explorerArea, editorArea ] = area.split({ sizes:[ "15%","85%" ] });
|
|
336
|
+
// explorerArea.setLimitBox( 180, 20, 512 );
|
|
263
337
|
this.explorerArea = explorerArea;
|
|
264
338
|
|
|
265
339
|
let panel = new LX.Panel();
|
|
@@ -310,35 +384,73 @@ class CodeEditor {
|
|
|
310
384
|
explorerArea.attach( panel );
|
|
311
385
|
|
|
312
386
|
// Update area
|
|
313
|
-
area =
|
|
387
|
+
area = editorArea;
|
|
314
388
|
}
|
|
315
389
|
|
|
316
|
-
this.
|
|
390
|
+
this.baseArea = area;
|
|
317
391
|
this.area = new LX.Area( { className: "lexcodeeditor", height: "100%", skipAppend: true } );
|
|
318
392
|
|
|
319
|
-
this.
|
|
320
|
-
|
|
393
|
+
if( !this.skipTabs )
|
|
394
|
+
{
|
|
395
|
+
this.tabs = this.area.addTabs( { onclose: (name) => {
|
|
396
|
+
delete this.openedTabs[ name ];
|
|
397
|
+
if( Object.keys( this.openedTabs ).length < 2 )
|
|
398
|
+
{
|
|
399
|
+
clearInterval( this.blinker );
|
|
400
|
+
this.cursors.classList.remove( 'show' );
|
|
401
|
+
}
|
|
402
|
+
} } );
|
|
321
403
|
|
|
322
|
-
|
|
323
|
-
this.tabs = this.area.addTabs( { onclose: (name) => {
|
|
324
|
-
delete this.openedTabs[ name ];
|
|
325
|
-
if( Object.keys( this.openedTabs ).length < 2 )
|
|
404
|
+
if( !this.disableEdition )
|
|
326
405
|
{
|
|
327
|
-
|
|
328
|
-
|
|
406
|
+
this.tabs.root.addEventListener( 'dblclick', (e) => {
|
|
407
|
+
if( options.allowAddScripts ?? true )
|
|
408
|
+
{
|
|
409
|
+
e.preventDefault();
|
|
410
|
+
this.addTab( "unnamed.js", true );
|
|
411
|
+
}
|
|
412
|
+
} );
|
|
329
413
|
}
|
|
330
|
-
} } );
|
|
331
414
|
|
|
332
|
-
|
|
415
|
+
this.codeArea = this.tabs.area;
|
|
416
|
+
}
|
|
417
|
+
else
|
|
333
418
|
{
|
|
334
|
-
this.
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
419
|
+
this.codeArea = new LX.Area( { skipAppend: true } );
|
|
420
|
+
this.area.attach( this.codeArea );
|
|
421
|
+
this._loadFileButton = LX.makeElement( "button",
|
|
422
|
+
"grid absolute self-center z-100 p-3 rounded-full bg-secondary hover:bg-tertiary cursor-pointer border",
|
|
423
|
+
LX.makeIcon( "FolderOpen" ).innerHTML,
|
|
424
|
+
this.area,
|
|
425
|
+
{
|
|
426
|
+
bottom: "8px"
|
|
338
427
|
}
|
|
428
|
+
);
|
|
429
|
+
this._loadFileButton.addEventListener( "click", e => {
|
|
430
|
+
|
|
431
|
+
const dropdownOptions = [];
|
|
432
|
+
|
|
433
|
+
for( const [ key, value ] of [ ...Object.entries( this.loadedTabs ).slice( 1 ), ...Object.entries( this._tabStorage ) ] )
|
|
434
|
+
{
|
|
435
|
+
const icon = this._getFileIcon( key );
|
|
436
|
+
const classes = icon ? icon.split( ' ' ) : [];
|
|
437
|
+
dropdownOptions.push( {
|
|
438
|
+
name: key,
|
|
439
|
+
icon: classes[ 0 ],
|
|
440
|
+
svgClass: classes.slice( 0 ).join( ' ' ),
|
|
441
|
+
callback: (v) => {
|
|
442
|
+
this.loadCode( v );
|
|
443
|
+
}
|
|
444
|
+
} );
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
new LX.DropdownMenu( this._loadFileButton, dropdownOptions, { side: "top", align: "center" });
|
|
448
|
+
|
|
339
449
|
} );
|
|
340
450
|
}
|
|
341
451
|
|
|
452
|
+
this.codeArea.root.classList.add( 'lexcodearea' );
|
|
453
|
+
|
|
342
454
|
// Full editor
|
|
343
455
|
area.root.classList.add('codebasearea');
|
|
344
456
|
|
|
@@ -354,9 +466,6 @@ class CodeEditor {
|
|
|
354
466
|
attributeFilter: ['class', 'style'],
|
|
355
467
|
});
|
|
356
468
|
|
|
357
|
-
// Code area
|
|
358
|
-
this.tabs.area.root.classList.add( 'codetabsarea' );
|
|
359
|
-
|
|
360
469
|
this.root = this.area.root;
|
|
361
470
|
this.root.tabIndex = -1;
|
|
362
471
|
area.attach( this.root );
|
|
@@ -385,12 +494,12 @@ class CodeEditor {
|
|
|
385
494
|
|
|
386
495
|
this.cursors = document.createElement( 'div' );
|
|
387
496
|
this.cursors.className = 'cursors';
|
|
388
|
-
this.
|
|
497
|
+
this.codeArea.attach( this.cursors );
|
|
389
498
|
|
|
390
499
|
this.searchResultSelections = document.createElement( 'div' );
|
|
391
500
|
this.searchResultSelections.id = 'search-selections';
|
|
392
501
|
this.searchResultSelections.className = 'selections';
|
|
393
|
-
this.
|
|
502
|
+
this.codeArea.attach( this.searchResultSelections );
|
|
394
503
|
|
|
395
504
|
// Store here selections per cursor
|
|
396
505
|
this.selections = {};
|
|
@@ -403,7 +512,7 @@ class CodeEditor {
|
|
|
403
512
|
|
|
404
513
|
// Scroll stuff
|
|
405
514
|
{
|
|
406
|
-
this.codeScroller = this.
|
|
515
|
+
this.codeScroller = this.codeArea.root;
|
|
407
516
|
this.firstLineInViewport = 0;
|
|
408
517
|
this.lineScrollMargin = new LX.vec2( 20, 20 ); // [ mUp, mDown ]
|
|
409
518
|
|
|
@@ -428,12 +537,14 @@ class CodeEditor {
|
|
|
428
537
|
{
|
|
429
538
|
if( this.visibleLinesViewport.y < (this.code.lines.length - 1) )
|
|
430
539
|
{
|
|
431
|
-
const totalLinesInViewport = ((this.codeScroller.offsetHeight
|
|
540
|
+
const totalLinesInViewport = ( ( this.codeScroller.offsetHeight ) / this.lineHeight )|0;
|
|
432
541
|
const scrollDownBoundary =
|
|
433
542
|
( Math.max( this.visibleLinesViewport.y - totalLinesInViewport, 0 ) - 1 ) * this.lineHeight;
|
|
434
543
|
|
|
435
544
|
if( scrollTop >= scrollDownBoundary )
|
|
545
|
+
{
|
|
436
546
|
this.processLines( CodeEditor.UPDATE_VISIBLE_LINES );
|
|
547
|
+
}
|
|
437
548
|
}
|
|
438
549
|
}
|
|
439
550
|
// Scroll up...
|
|
@@ -441,7 +552,9 @@ class CodeEditor {
|
|
|
441
552
|
{
|
|
442
553
|
const scrollUpBoundary = parseInt( this.code.style.top );
|
|
443
554
|
if( scrollTop < scrollUpBoundary )
|
|
555
|
+
{
|
|
444
556
|
this.processLines( CodeEditor.UPDATE_VISIBLE_LINES );
|
|
557
|
+
}
|
|
445
558
|
}
|
|
446
559
|
|
|
447
560
|
lastScrollTopValue = scrollTop;
|
|
@@ -451,9 +564,13 @@ class CodeEditor {
|
|
|
451
564
|
if( e.ctrlKey )
|
|
452
565
|
{
|
|
453
566
|
e.preventDefault();
|
|
567
|
+
e.stopPropagation();
|
|
454
568
|
( e.deltaY > 0.0 ? this._decreaseFontSize() : this._increaseFontSize() );
|
|
455
569
|
}
|
|
456
|
-
|
|
570
|
+
} );
|
|
571
|
+
|
|
572
|
+
this.codeScroller.addEventListener( 'wheel', e => {
|
|
573
|
+
if( !e.ctrlKey )
|
|
457
574
|
{
|
|
458
575
|
const dX = ( e.deltaY > 0.0 ? 10.0 : -10.0 ) * ( e.shiftKey ? 1.0 : 0.0 );
|
|
459
576
|
if( dX != 0.0 ) this.setScrollBarValue( 'horizontal', dX );
|
|
@@ -462,50 +579,47 @@ class CodeEditor {
|
|
|
462
579
|
}
|
|
463
580
|
}
|
|
464
581
|
|
|
465
|
-
//
|
|
582
|
+
// Line numbers and scrollbars
|
|
466
583
|
{
|
|
584
|
+
// This is only the container, line numbers are in the same line div
|
|
467
585
|
this.gutter = document.createElement( 'div' );
|
|
468
586
|
this.gutter.className = "lexcodegutter";
|
|
469
587
|
area.attach( this.gutter );
|
|
470
|
-
}
|
|
471
588
|
|
|
472
|
-
|
|
473
|
-
{
|
|
589
|
+
// Add custom vertical scroll bar
|
|
474
590
|
this.vScrollbar = new ScrollBar( this, ScrollBar.SCROLLBAR_VERTICAL );
|
|
475
591
|
area.attach( this.vScrollbar.root );
|
|
476
|
-
}
|
|
477
592
|
|
|
478
|
-
|
|
479
|
-
{
|
|
593
|
+
// Add custom horizontal scroll bar
|
|
480
594
|
this.hScrollbar = new ScrollBar( this, ScrollBar.SCROLLBAR_HORIZONTAL );
|
|
481
595
|
area.attach( this.hScrollbar.root );
|
|
482
596
|
}
|
|
483
597
|
|
|
598
|
+
// Add autocomplete, search boxes (IF edition enabled)
|
|
484
599
|
if( !this.disableEdition )
|
|
485
600
|
{
|
|
486
601
|
// Add autocomplete box
|
|
487
602
|
{
|
|
488
|
-
|
|
603
|
+
const box = document.createElement( 'div' );
|
|
489
604
|
box.className = "autocomplete";
|
|
490
605
|
this.autocomplete = box;
|
|
491
|
-
this.
|
|
492
|
-
|
|
606
|
+
this.codeArea.attach( box );
|
|
493
607
|
this.isAutoCompleteActive = false;
|
|
494
608
|
}
|
|
495
609
|
|
|
496
610
|
// Add search box
|
|
497
611
|
{
|
|
498
|
-
|
|
612
|
+
const box = document.createElement( 'div' );
|
|
499
613
|
box.className = "searchbox";
|
|
500
614
|
|
|
501
|
-
|
|
615
|
+
const searchPanel = new LX.Panel();
|
|
502
616
|
box.appendChild( searchPanel.root );
|
|
503
617
|
|
|
504
618
|
searchPanel.sameLine( 4 );
|
|
505
619
|
searchPanel.addText( null, "", null, { placeholder: "Find" } );
|
|
506
|
-
searchPanel.addButton( null, "up", () => this.search( null, true ), { icon: "ArrowUp" } );
|
|
507
|
-
searchPanel.addButton( null, "down", () => this.search(), { icon: "ArrowDown" } );
|
|
508
|
-
searchPanel.addButton( null, "x", this.hideSearchBox.bind( this ), { icon: "X" } );
|
|
620
|
+
searchPanel.addButton( null, "up", () => this.search( null, true ), { icon: "ArrowUp", title: "Previous Match", tooltip: true } );
|
|
621
|
+
searchPanel.addButton( null, "down", () => this.search(), { icon: "ArrowDown", title: "Next Match", tooltip: true } );
|
|
622
|
+
searchPanel.addButton( null, "x", this.hideSearchBox.bind( this ), { icon: "X", title: "Close", tooltip: true } );
|
|
509
623
|
|
|
510
624
|
box.querySelector( 'input' ).addEventListener( 'keyup', e => {
|
|
511
625
|
if( e.key == 'Escape' ) this.hideSearchBox();
|
|
@@ -513,7 +627,7 @@ class CodeEditor {
|
|
|
513
627
|
} );
|
|
514
628
|
|
|
515
629
|
this.searchbox = box;
|
|
516
|
-
this.
|
|
630
|
+
this.codeArea.attach( box );
|
|
517
631
|
}
|
|
518
632
|
|
|
519
633
|
// Add search LINE box
|
|
@@ -535,7 +649,7 @@ class CodeEditor {
|
|
|
535
649
|
} );
|
|
536
650
|
|
|
537
651
|
this.searchlinebox = box;
|
|
538
|
-
this.
|
|
652
|
+
this.codeArea.attach( box );
|
|
539
653
|
}
|
|
540
654
|
}
|
|
541
655
|
|
|
@@ -546,7 +660,9 @@ class CodeEditor {
|
|
|
546
660
|
|
|
547
661
|
// Append all childs
|
|
548
662
|
while( this.codeScroller.firstChild )
|
|
663
|
+
{
|
|
549
664
|
this.codeSizer.appendChild( this.codeScroller.firstChild );
|
|
665
|
+
}
|
|
550
666
|
|
|
551
667
|
this.codeScroller.appendChild( this.codeSizer );
|
|
552
668
|
}
|
|
@@ -562,7 +678,6 @@ class CodeEditor {
|
|
|
562
678
|
|
|
563
679
|
// Code
|
|
564
680
|
|
|
565
|
-
this.useAutoComplete = options.autocomplete ?? true;
|
|
566
681
|
this.highlight = options.highlight ?? 'Plain Text';
|
|
567
682
|
this.onsave = options.onsave ?? ((code) => { console.log( code, "save" ) });
|
|
568
683
|
this.onrun = options.onrun ?? ((code) => { this.runScript(code) });
|
|
@@ -575,6 +690,7 @@ class CodeEditor {
|
|
|
575
690
|
this.defaultSingleLineCommentToken = '//';
|
|
576
691
|
this.defaultBlockCommentTokens = [ '/*', '*/' ];
|
|
577
692
|
this._lastTime = null;
|
|
693
|
+
this._tabStorage = {};
|
|
578
694
|
|
|
579
695
|
this.pairKeys = {
|
|
580
696
|
"\"": "\"",
|
|
@@ -592,24 +708,6 @@ class CodeEditor {
|
|
|
592
708
|
// Scan tokens..
|
|
593
709
|
// setInterval( this.scanWordSuggestions.bind( this ), 2000 );
|
|
594
710
|
|
|
595
|
-
this.languages = {
|
|
596
|
-
'Plain Text': { ext: 'txt', blockComments: false, singleLineComments: false },
|
|
597
|
-
'JavaScript': { ext: 'js' },
|
|
598
|
-
'C': { ext: [ 'c', 'h' ] },
|
|
599
|
-
'C++': { ext: [ 'cpp', 'hpp' ] },
|
|
600
|
-
'CSS': { ext: 'css' },
|
|
601
|
-
'CMake': { ext: 'cmake', singleLineCommentToken: '#', blockComments: false, ignoreCase: true },
|
|
602
|
-
'GLSL': { ext: 'glsl' },
|
|
603
|
-
'WGSL': { ext: 'wgsl' },
|
|
604
|
-
'JSON': { ext: 'json', blockComments: false, singleLineComments: false },
|
|
605
|
-
'XML': { ext: 'xml', tags: true },
|
|
606
|
-
'Rust': { ext: 'rs' },
|
|
607
|
-
'Python': { ext: 'py', singleLineCommentToken: '#' },
|
|
608
|
-
'HTML': { ext: 'html', tags: true, singleLineComments: false, blockCommentsTokens: [ '<!--', '-->' ] },
|
|
609
|
-
'Batch': { ext: 'bat', blockComments: false, singleLineCommentToken: '::' },
|
|
610
|
-
'Markdown': { ext: 'md', blockComments: false, singleLineCommentToken: '::', tags: true }
|
|
611
|
-
};
|
|
612
|
-
|
|
613
711
|
this.specialKeys = [
|
|
614
712
|
'Backspace', 'Enter', 'ArrowUp', 'ArrowDown',
|
|
615
713
|
'ArrowRight', 'ArrowLeft', 'Delete', 'Home',
|
|
@@ -622,476 +720,485 @@ class CodeEditor {
|
|
|
622
720
|
|
|
623
721
|
if( !CodeEditor._staticReady )
|
|
624
722
|
{
|
|
625
|
-
for( let lang in CodeEditor.keywords ) CodeEditor.keywords[lang] = CodeEditor.keywords[lang]
|
|
626
|
-
for( let lang in CodeEditor.utils ) CodeEditor.utils[lang] = CodeEditor.utils[lang]
|
|
627
|
-
for( let lang in CodeEditor.types ) CodeEditor.types[lang] = CodeEditor.types[lang]
|
|
628
|
-
for( let lang in CodeEditor.builtIn ) CodeEditor.builtIn[lang] = CodeEditor.builtIn[lang]
|
|
629
|
-
for( let lang in CodeEditor.
|
|
630
|
-
for( let lang in CodeEditor.symbols ) CodeEditor.symbols[lang] = CodeEditor.symbols[lang]
|
|
723
|
+
for( let lang in CodeEditor.keywords ) CodeEditor.keywords[lang] = new Set( CodeEditor.keywords[lang] );
|
|
724
|
+
for( let lang in CodeEditor.utils ) CodeEditor.utils[lang] = new Set( CodeEditor.utils[lang] );
|
|
725
|
+
for( let lang in CodeEditor.types ) CodeEditor.types[lang] = new Set( CodeEditor.types[lang] );
|
|
726
|
+
for( let lang in CodeEditor.builtIn ) CodeEditor.builtIn[lang] = new Set( CodeEditor.builtIn[lang] );
|
|
727
|
+
for( let lang in CodeEditor.statements ) CodeEditor.statements[lang] = new Set( CodeEditor.statements[lang] );
|
|
728
|
+
for( let lang in CodeEditor.symbols ) CodeEditor.symbols[lang] = new Set( CodeEditor.symbols[lang] );
|
|
631
729
|
|
|
632
730
|
CodeEditor._staticReady = true;
|
|
633
731
|
}
|
|
634
732
|
|
|
635
733
|
// Action keys
|
|
734
|
+
{
|
|
735
|
+
this.action( 'Escape', false, ( ln, cursor, e ) => {
|
|
736
|
+
if( this.hideAutoCompleteBox() )
|
|
737
|
+
return;
|
|
738
|
+
if( this.hideSearchBox() )
|
|
739
|
+
return;
|
|
740
|
+
// Remove selections and cursors
|
|
741
|
+
this.endSelection();
|
|
742
|
+
this._removeSecondaryCursors();
|
|
743
|
+
});
|
|
636
744
|
|
|
637
|
-
|
|
638
|
-
if( this.hideAutoCompleteBox() )
|
|
639
|
-
return;
|
|
640
|
-
if( this.hideSearchBox() )
|
|
641
|
-
return;
|
|
642
|
-
// Remove selections and cursors
|
|
643
|
-
this.endSelection();
|
|
644
|
-
this._removeSecondaryCursors();
|
|
645
|
-
});
|
|
646
|
-
|
|
647
|
-
this.action( 'Backspace', false, ( ln, cursor, e ) => {
|
|
745
|
+
this.action( 'Backspace', false, ( ln, cursor, e ) => {
|
|
648
746
|
|
|
649
|
-
|
|
747
|
+
this._addUndoStep( cursor );
|
|
650
748
|
|
|
651
|
-
|
|
652
|
-
this.deleteSelection( cursor );
|
|
653
|
-
// Remove entire line when selecting with triple click
|
|
654
|
-
if( this._tripleClickSelection )
|
|
749
|
+
if( cursor.selection )
|
|
655
750
|
{
|
|
656
|
-
this.
|
|
657
|
-
|
|
751
|
+
this.deleteSelection( cursor );
|
|
752
|
+
// Remove entire line when selecting with triple click
|
|
753
|
+
if( this._tripleClickSelection )
|
|
754
|
+
{
|
|
755
|
+
this.actions['Backspace'].callback( ln, cursor, e );
|
|
756
|
+
this.lineDown( cursor, true );
|
|
757
|
+
}
|
|
658
758
|
}
|
|
659
|
-
|
|
660
|
-
else {
|
|
759
|
+
else {
|
|
661
760
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
var deleteFromPosition = cursor.position - 1;
|
|
666
|
-
var numCharsDeleted = 1;
|
|
667
|
-
|
|
668
|
-
// Delete full word
|
|
669
|
-
if( e.shiftKey )
|
|
761
|
+
var letter = this.getCharAtPos( cursor, -1 );
|
|
762
|
+
if( letter )
|
|
670
763
|
{
|
|
671
|
-
|
|
764
|
+
var deleteFromPosition = cursor.position - 1;
|
|
765
|
+
var numCharsDeleted = 1;
|
|
672
766
|
|
|
673
|
-
|
|
767
|
+
// Delete full word
|
|
768
|
+
if( e.shiftKey )
|
|
674
769
|
{
|
|
675
|
-
|
|
676
|
-
|
|
770
|
+
const [word, from, to] = this.getWordAtPos( cursor, -1 );
|
|
771
|
+
|
|
772
|
+
if( word.length > 1 )
|
|
773
|
+
{
|
|
774
|
+
deleteFromPosition = from;
|
|
775
|
+
numCharsDeleted = word.length;
|
|
776
|
+
}
|
|
677
777
|
}
|
|
678
|
-
}
|
|
679
778
|
|
|
680
|
-
|
|
681
|
-
|
|
779
|
+
this.code.lines[ ln ] = sliceChars( this.code.lines[ ln ], deleteFromPosition, numCharsDeleted );
|
|
780
|
+
this.processLine( ln );
|
|
682
781
|
|
|
683
|
-
|
|
782
|
+
this.cursorToPosition( cursor, deleteFromPosition );
|
|
684
783
|
|
|
685
|
-
|
|
784
|
+
if( this.useAutoComplete )
|
|
785
|
+
{
|
|
786
|
+
this.showAutoCompleteBox( 'foo', cursor );
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
else if( this.code.lines[ ln - 1 ] != undefined )
|
|
686
790
|
{
|
|
687
|
-
this.
|
|
791
|
+
this.lineUp( cursor );
|
|
792
|
+
e.cancelShift = true;
|
|
793
|
+
this.actions[ 'End' ].callback( cursor.line, cursor, e );
|
|
794
|
+
// Move line on top
|
|
795
|
+
this.code.lines[ ln - 1 ] += this.code.lines[ ln ];
|
|
796
|
+
this.code.lines.splice( ln, 1 );
|
|
797
|
+
this.processLines();
|
|
688
798
|
}
|
|
689
799
|
}
|
|
690
|
-
else if( this.code.lines[ ln - 1 ] != undefined ) {
|
|
691
|
-
|
|
692
|
-
this.lineUp( cursor );
|
|
693
|
-
e.cancelShift = true;
|
|
694
|
-
this.actions[ 'End' ].callback( cursor.line, cursor, e );
|
|
695
|
-
// Move line on top
|
|
696
|
-
this.code.lines[ ln - 1 ] += this.code.lines[ ln ];
|
|
697
|
-
this.code.lines.splice( ln, 1 );
|
|
698
|
-
this.processLines();
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
});
|
|
702
800
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
this._addUndoStep( cursor );
|
|
706
|
-
|
|
707
|
-
if( cursor.selection ) {
|
|
708
|
-
// Use 'Backspace' as it's the same callback...
|
|
709
|
-
this.actions['Backspace'].callback( ln, cursor, e );
|
|
710
|
-
}
|
|
711
|
-
else
|
|
712
|
-
{
|
|
713
|
-
var letter = this.getCharAtPos( cursor );
|
|
714
|
-
if( letter ) {
|
|
715
|
-
this.code.lines[ ln ] = sliceChars( this.code.lines[ ln ], cursor.position );
|
|
716
|
-
this.processLine( ln );
|
|
717
|
-
}
|
|
718
|
-
else if( this.code.lines[ ln + 1 ] != undefined ) {
|
|
719
|
-
this.code.lines[ ln ] += this.code.lines[ ln + 1 ];
|
|
720
|
-
this.code.lines.splice( ln + 1, 1 );
|
|
721
|
-
this.processLines();
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
});
|
|
801
|
+
this.resizeIfNecessary( cursor, true );
|
|
802
|
+
});
|
|
725
803
|
|
|
726
|
-
|
|
804
|
+
this.action( 'Delete', false, ( ln, cursor, e ) => {
|
|
727
805
|
|
|
728
|
-
if( this._skipTabs )
|
|
729
|
-
{
|
|
730
|
-
this._skipTabs--;
|
|
731
|
-
if( !this._skipTabs )
|
|
732
|
-
delete this._skipTabs;
|
|
733
|
-
}
|
|
734
|
-
else if( this.isAutoCompleteActive )
|
|
735
|
-
{
|
|
736
|
-
this.autoCompleteWord();
|
|
737
|
-
}
|
|
738
|
-
else
|
|
739
|
-
{
|
|
740
806
|
this._addUndoStep( cursor );
|
|
741
807
|
|
|
742
|
-
if(
|
|
808
|
+
if( cursor.selection )
|
|
743
809
|
{
|
|
744
|
-
|
|
810
|
+
// Use 'Backspace' as it's the same callback...
|
|
811
|
+
this.actions['Backspace'].callback( ln, cursor, e );
|
|
745
812
|
}
|
|
746
813
|
else
|
|
747
814
|
{
|
|
748
|
-
|
|
749
|
-
|
|
815
|
+
var letter = this.getCharAtPos( cursor );
|
|
816
|
+
if( letter )
|
|
817
|
+
{
|
|
818
|
+
this.code.lines[ ln ] = sliceChars( this.code.lines[ ln ], cursor.position );
|
|
819
|
+
this.processLine( ln );
|
|
820
|
+
}
|
|
821
|
+
else if( this.code.lines[ ln + 1 ] != undefined )
|
|
822
|
+
{
|
|
823
|
+
this.code.lines[ ln ] += this.code.lines[ ln + 1 ];
|
|
824
|
+
this.code.lines.splice( ln + 1, 1 );
|
|
825
|
+
this.processLines();
|
|
826
|
+
}
|
|
750
827
|
}
|
|
751
|
-
}
|
|
752
|
-
}, "shiftKey");
|
|
753
|
-
|
|
754
|
-
this.action( 'Home', false, ( ln, cursor, e ) => {
|
|
755
|
-
|
|
756
|
-
let idx = firstNonspaceIndex( this.code.lines[ ln ] );
|
|
757
828
|
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
const prestring = this.code.lines[ ln ].substring( 0, idx );
|
|
762
|
-
let lastX = cursor.position;
|
|
763
|
-
|
|
764
|
-
this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
|
|
765
|
-
if( idx > 0 )
|
|
766
|
-
{
|
|
767
|
-
this.cursorToString( cursor, prestring );
|
|
768
|
-
}
|
|
769
|
-
else
|
|
770
|
-
{
|
|
771
|
-
// No spaces, start from char 0
|
|
772
|
-
idx = 0;
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
this.setScrollLeft( 0 );
|
|
776
|
-
this.mergeCursors( ln );
|
|
829
|
+
this.resizeIfNecessary( cursor, true );
|
|
830
|
+
});
|
|
777
831
|
|
|
778
|
-
|
|
779
|
-
{
|
|
780
|
-
// Get last selection range
|
|
781
|
-
if( cursor.selection )
|
|
782
|
-
{
|
|
783
|
-
lastX += cursor.selection.chars;
|
|
784
|
-
}
|
|
832
|
+
this.action( 'Tab', true, ( ln, cursor, e ) => {
|
|
785
833
|
|
|
786
|
-
if(
|
|
834
|
+
if( this._skipTabs )
|
|
787
835
|
{
|
|
788
|
-
this.
|
|
836
|
+
this._skipTabs--;
|
|
837
|
+
if( !this._skipTabs )
|
|
838
|
+
delete this._skipTabs;
|
|
789
839
|
}
|
|
790
|
-
|
|
791
|
-
var string = this.code.lines[ ln ].substring( idx, lastX );
|
|
792
|
-
if( cursor.selection.sameLine() )
|
|
840
|
+
else if( this.isAutoCompleteActive )
|
|
793
841
|
{
|
|
794
|
-
|
|
842
|
+
this.autoCompleteWord();
|
|
795
843
|
}
|
|
796
844
|
else
|
|
797
845
|
{
|
|
798
|
-
this.
|
|
846
|
+
this._addUndoStep( cursor );
|
|
847
|
+
|
|
848
|
+
if( e && e.shiftKey )
|
|
849
|
+
{
|
|
850
|
+
this._removeSpaces( cursor );
|
|
851
|
+
}
|
|
852
|
+
else
|
|
853
|
+
{
|
|
854
|
+
const indentSpaces = this.tabSpaces - (cursor.position % this.tabSpaces);
|
|
855
|
+
this._addSpaces( indentSpaces );
|
|
856
|
+
}
|
|
799
857
|
}
|
|
800
|
-
}
|
|
801
|
-
this.endSelection();
|
|
802
|
-
});
|
|
858
|
+
}, "shiftKey");
|
|
803
859
|
|
|
804
|
-
|
|
860
|
+
this.action( 'Home', false, ( ln, cursor, e ) => {
|
|
805
861
|
|
|
806
|
-
|
|
862
|
+
let idx = firstNonspaceIndex( this.code.lines[ ln ] );
|
|
807
863
|
|
|
808
|
-
|
|
809
|
-
if(
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
864
|
+
// We already are in the first non space index...
|
|
865
|
+
if( idx == cursor.position ) idx = 0;
|
|
866
|
+
|
|
867
|
+
const prestring = this.code.lines[ ln ].substring( 0, idx );
|
|
868
|
+
let lastX = cursor.position;
|
|
869
|
+
|
|
870
|
+
this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
|
|
871
|
+
if( idx > 0 )
|
|
872
|
+
{
|
|
873
|
+
this.cursorToString( cursor, prestring );
|
|
874
|
+
}
|
|
813
875
|
else
|
|
814
876
|
{
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
this._processSelection( cursor, e );
|
|
877
|
+
// No spaces, start from char 0
|
|
878
|
+
idx = 0;
|
|
818
879
|
}
|
|
819
|
-
} else if( !e.keepSelection )
|
|
820
|
-
this.endSelection();
|
|
821
|
-
|
|
822
|
-
this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
|
|
823
|
-
this.cursorToString( cursor, this.code.lines[ ln ] );
|
|
824
|
-
|
|
825
|
-
var viewportSizeX = ( this.codeScroller.clientWidth + this.getScrollLeft() ) - CodeEditor.LINE_GUTTER_WIDTH; // Gutter offset
|
|
826
|
-
if( ( cursor.position * this.charWidth ) >= viewportSizeX )
|
|
827
|
-
this.setScrollLeft( this.code.lines[ ln ].length * this.charWidth );
|
|
828
880
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
});
|
|
881
|
+
this.setScrollLeft( 0 );
|
|
882
|
+
this.mergeCursors( ln );
|
|
832
883
|
|
|
833
|
-
|
|
884
|
+
if( e.shiftKey && !e.cancelShift )
|
|
885
|
+
{
|
|
886
|
+
// Get last selection range
|
|
887
|
+
if( cursor.selection )
|
|
888
|
+
{
|
|
889
|
+
lastX += cursor.selection.chars;
|
|
890
|
+
}
|
|
834
891
|
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
return;
|
|
840
|
-
}
|
|
892
|
+
if( !cursor.selection )
|
|
893
|
+
{
|
|
894
|
+
this.startSelection( cursor );
|
|
895
|
+
}
|
|
841
896
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
897
|
+
var string = this.code.lines[ ln ].substring( idx, lastX );
|
|
898
|
+
if( cursor.selection.sameLine() )
|
|
899
|
+
{
|
|
900
|
+
cursor.selection.selectInline( cursor, idx, cursor.line, this.measureString( string ) );
|
|
901
|
+
}
|
|
902
|
+
else
|
|
903
|
+
{
|
|
904
|
+
this._processSelection( cursor, e );
|
|
905
|
+
}
|
|
906
|
+
} else if( !e.keepSelection )
|
|
907
|
+
this.endSelection();
|
|
908
|
+
});
|
|
847
909
|
|
|
848
|
-
this.
|
|
910
|
+
this.action( 'End', false, ( ln, cursor, e ) => {
|
|
849
911
|
|
|
850
|
-
|
|
851
|
-
var _c1 = this.getCharAtPos( cursor );
|
|
912
|
+
if( ( e.shiftKey || e._shiftKey ) && !e.cancelShift ) {
|
|
852
913
|
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
914
|
+
var string = this.code.lines[ ln ].substring( cursor.position );
|
|
915
|
+
if( !cursor.selection )
|
|
916
|
+
this.startSelection( cursor );
|
|
917
|
+
if( cursor.selection.sameLine() )
|
|
918
|
+
cursor.selection.selectInline( cursor, cursor.position, cursor.line, this.measureString( string ));
|
|
919
|
+
else
|
|
920
|
+
{
|
|
921
|
+
this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
|
|
922
|
+
this.cursorToString( cursor, this.code.lines[ ln ] );
|
|
923
|
+
this._processSelection( cursor, e );
|
|
924
|
+
}
|
|
925
|
+
} else if( !e.keepSelection )
|
|
926
|
+
this.endSelection();
|
|
856
927
|
|
|
857
|
-
|
|
928
|
+
this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
|
|
929
|
+
this.cursorToString( cursor, this.code.lines[ ln ] );
|
|
858
930
|
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
931
|
+
var viewportSizeX = ( this.codeScroller.clientWidth + this.getScrollLeft() ) - CodeEditor.LINE_GUTTER_WIDTH; // Gutter offset
|
|
932
|
+
if( ( cursor.position * this.charWidth ) >= viewportSizeX )
|
|
933
|
+
this.setScrollLeft( this.code.lines[ ln ].length * this.charWidth );
|
|
862
934
|
|
|
863
|
-
|
|
864
|
-
this.
|
|
865
|
-
|
|
866
|
-
this.code.lines[ cursor.line + 1 ] = " ".repeat(spaces) + this.code.lines[ cursor.line + 1 ];
|
|
867
|
-
} else {
|
|
868
|
-
this._addSpaceTabs( cursor, tabs );
|
|
869
|
-
}
|
|
935
|
+
// Merge cursors
|
|
936
|
+
this.mergeCursors( ln );
|
|
937
|
+
});
|
|
870
938
|
|
|
871
|
-
this.
|
|
872
|
-
});
|
|
939
|
+
this.action( 'Enter', true, ( ln, cursor, e ) => {
|
|
873
940
|
|
|
874
|
-
|
|
941
|
+
// Add word
|
|
942
|
+
if( this.isAutoCompleteActive )
|
|
943
|
+
{
|
|
944
|
+
this.autoCompleteWord();
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
875
947
|
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
this.startSelection( cursor );
|
|
948
|
+
if( e.ctrlKey )
|
|
949
|
+
{
|
|
950
|
+
this.onrun( this.getText() );
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
882
953
|
|
|
883
|
-
|
|
954
|
+
this._addUndoStep( cursor, true );
|
|
884
955
|
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
this.cursorToPosition( cursor, this.code.lines[ cursor.line ].length );
|
|
888
|
-
}
|
|
956
|
+
var _c0 = this.getCharAtPos( cursor, -1 );
|
|
957
|
+
var _c1 = this.getCharAtPos( cursor );
|
|
889
958
|
|
|
890
|
-
|
|
959
|
+
this.code.lines.splice( cursor.line + 1, 0, "" );
|
|
960
|
+
this.code.lines[cursor.line + 1] = this.code.lines[ ln ].substr( cursor.position ); // new line (below)
|
|
961
|
+
this.code.lines[ ln ] = this.code.lines[ ln ].substr( 0, cursor.position ); // line above
|
|
891
962
|
|
|
892
|
-
|
|
893
|
-
this.endSelection();
|
|
894
|
-
this.lineUp( cursor );
|
|
895
|
-
// Go to end of line if out of line
|
|
896
|
-
var letter = this.getCharAtPos( cursor );
|
|
897
|
-
if( !letter ) this.actions['End'].callback( cursor.line, cursor, e );
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
// Move up autocomplete selection
|
|
901
|
-
else
|
|
902
|
-
{
|
|
903
|
-
this._moveArrowSelectedAutoComplete('up');
|
|
904
|
-
}
|
|
905
|
-
});
|
|
963
|
+
this.lineDown( cursor, true );
|
|
906
964
|
|
|
907
|
-
|
|
965
|
+
// Check indentation
|
|
966
|
+
var spaces = firstNonspaceIndex( this.code.lines[ ln ]);
|
|
967
|
+
var tabs = Math.floor( spaces / this.tabSpaces );
|
|
908
968
|
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
if( !cursor.selection )
|
|
914
|
-
this.startSelection( cursor );
|
|
969
|
+
if( _c0 == '{' && _c1 == '}' ) {
|
|
970
|
+
this.code.lines.splice( cursor.line, 0, "" );
|
|
971
|
+
this._addSpaceTabs( cursor, tabs + 1 );
|
|
972
|
+
this.code.lines[ cursor.line + 1 ] = " ".repeat(spaces) + this.code.lines[ cursor.line + 1 ];
|
|
915
973
|
} else {
|
|
916
|
-
this.
|
|
974
|
+
this._addSpaceTabs( cursor, tabs );
|
|
917
975
|
}
|
|
918
976
|
|
|
919
|
-
|
|
920
|
-
|
|
977
|
+
this.processLines();
|
|
978
|
+
});
|
|
921
979
|
|
|
922
|
-
|
|
923
|
-
if( !letter || !canGoDown ) {
|
|
924
|
-
this.cursorToPosition( cursor, Math.max(this.code.lines[ cursor.line ].length, 0) );
|
|
925
|
-
}
|
|
980
|
+
this.action( 'ArrowUp', false, ( ln, cursor, e ) => {
|
|
926
981
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
{
|
|
934
|
-
this._moveArrowSelectedAutoComplete('down');
|
|
935
|
-
}
|
|
936
|
-
});
|
|
982
|
+
// Move cursor..
|
|
983
|
+
if( !this.isAutoCompleteActive )
|
|
984
|
+
{
|
|
985
|
+
if( e.shiftKey ) {
|
|
986
|
+
if( !cursor.selection )
|
|
987
|
+
this.startSelection( cursor );
|
|
937
988
|
|
|
938
|
-
|
|
989
|
+
this.lineUp( cursor );
|
|
939
990
|
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
991
|
+
var letter = this.getCharAtPos( cursor );
|
|
992
|
+
if( !letter ) {
|
|
993
|
+
this.cursorToPosition( cursor, this.code.lines[ cursor.line ].length );
|
|
994
|
+
}
|
|
943
995
|
|
|
944
|
-
|
|
945
|
-
e.preventDefault();
|
|
946
|
-
this.actions[ 'Home' ].callback( ln, cursor, e );
|
|
947
|
-
}
|
|
948
|
-
else if( e.ctrlKey ) {
|
|
949
|
-
// Get next word
|
|
950
|
-
const [word, from, to] = this.getWordAtPos( cursor, -1 );
|
|
951
|
-
// If no length, we change line..
|
|
952
|
-
if( !word.length && this.lineUp( cursor, true ) ) {
|
|
953
|
-
const cS = e.cancelShift, kS = e.keepSelection;
|
|
954
|
-
e.cancelShift = true;
|
|
955
|
-
e.keepSelection = true;
|
|
956
|
-
this.actions[ 'End' ].callback( cursor.line, cursor, e );
|
|
957
|
-
e.cancelShift = cS;
|
|
958
|
-
e.keepSelection = kS;
|
|
959
|
-
}
|
|
960
|
-
var diff = Math.max( cursor.position - from, 1 );
|
|
961
|
-
var substr = word.substr( 0, diff );
|
|
996
|
+
this._processSelection( cursor, e, false );
|
|
962
997
|
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
998
|
+
} else {
|
|
999
|
+
this.endSelection();
|
|
1000
|
+
this.lineUp( cursor );
|
|
1001
|
+
// Go to end of line if out of line
|
|
1002
|
+
var letter = this.getCharAtPos( cursor );
|
|
1003
|
+
if( !letter ) this.actions['End'].callback( cursor.line, cursor, e );
|
|
1004
|
+
}
|
|
967
1005
|
}
|
|
1006
|
+
// Move up autocomplete selection
|
|
968
1007
|
else
|
|
969
|
-
|
|
1008
|
+
{
|
|
1009
|
+
this._moveArrowSelectedAutoComplete('up');
|
|
1010
|
+
}
|
|
1011
|
+
});
|
|
970
1012
|
|
|
971
|
-
|
|
1013
|
+
this.action( 'ArrowDown', false, ( ln, cursor, e ) => {
|
|
972
1014
|
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
else {
|
|
977
|
-
var letter = this.getCharAtPos( cursor, -1 );
|
|
978
|
-
if( letter ) {
|
|
1015
|
+
// Move cursor..
|
|
1016
|
+
if( !this.isAutoCompleteActive )
|
|
1017
|
+
{
|
|
979
1018
|
if( e.shiftKey ) {
|
|
980
|
-
if( !cursor.selection )
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
else {
|
|
985
|
-
if( !cursor.selection ) {
|
|
986
|
-
this.cursorToLeft( letter, cursor );
|
|
987
|
-
if( this.useAutoComplete && this.isAutoCompleteActive )
|
|
988
|
-
this.showAutoCompleteBox( 'foo', cursor );
|
|
989
|
-
}
|
|
990
|
-
else {
|
|
991
|
-
cursor.selection.invertIfNecessary();
|
|
992
|
-
this.resetCursorPos( CodeEditor.CURSOR_LEFT_TOP, cursor );
|
|
993
|
-
this.cursorToLine( cursor, cursor.selection.fromY, true );
|
|
994
|
-
this.cursorToPosition( cursor, cursor.selection.fromX );
|
|
995
|
-
this.endSelection();
|
|
996
|
-
}
|
|
1019
|
+
if( !cursor.selection )
|
|
1020
|
+
this.startSelection( cursor );
|
|
1021
|
+
} else {
|
|
1022
|
+
this.endSelection();
|
|
997
1023
|
}
|
|
998
|
-
}
|
|
999
|
-
else if( cursor.line > 0 ) {
|
|
1000
1024
|
|
|
1001
|
-
|
|
1025
|
+
const canGoDown = this.lineDown( cursor );
|
|
1026
|
+
const letter = this.getCharAtPos( cursor );
|
|
1002
1027
|
|
|
1003
|
-
|
|
1028
|
+
// Go to end of line if out of range
|
|
1029
|
+
if( !letter || !canGoDown ) {
|
|
1030
|
+
this.cursorToPosition( cursor, Math.max(this.code.lines[ cursor.line ].length, 0) );
|
|
1031
|
+
}
|
|
1004
1032
|
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1033
|
+
if( e.shiftKey ) {
|
|
1034
|
+
this._processSelection( cursor, e );
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
// Move down autocomplete selection
|
|
1038
|
+
else
|
|
1039
|
+
{
|
|
1040
|
+
this._moveArrowSelectedAutoComplete('down');
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
1043
|
+
|
|
1044
|
+
this.action( 'ArrowLeft', false, ( ln, cursor, e ) => {
|
|
1008
1045
|
|
|
1009
|
-
|
|
1046
|
+
// Nothing to do..
|
|
1047
|
+
if( cursor.line == 0 && cursor.position == 0 )
|
|
1048
|
+
return;
|
|
1049
|
+
|
|
1050
|
+
if( e.metaKey ) { // Apple devices (Command)
|
|
1051
|
+
e.preventDefault();
|
|
1052
|
+
this.actions[ 'Home' ].callback( ln, cursor, e );
|
|
1010
1053
|
}
|
|
1011
|
-
|
|
1012
|
-
|
|
1054
|
+
else if( e.ctrlKey ) {
|
|
1055
|
+
// Get next word
|
|
1056
|
+
const [word, from, to] = this.getWordAtPos( cursor, -1 );
|
|
1057
|
+
// If no length, we change line..
|
|
1058
|
+
if( !word.length && this.lineUp( cursor, true ) ) {
|
|
1059
|
+
const cS = e.cancelShift, kS = e.keepSelection;
|
|
1060
|
+
e.cancelShift = true;
|
|
1061
|
+
e.keepSelection = true;
|
|
1062
|
+
this.actions[ 'End' ].callback( cursor.line, cursor, e );
|
|
1063
|
+
e.cancelShift = cS;
|
|
1064
|
+
e.keepSelection = kS;
|
|
1065
|
+
}
|
|
1066
|
+
var diff = Math.max( cursor.position - from, 1 );
|
|
1067
|
+
var substr = word.substr( 0, diff );
|
|
1013
1068
|
|
|
1014
|
-
|
|
1069
|
+
// Selections...
|
|
1070
|
+
if( e.shiftKey ) {
|
|
1071
|
+
if( !cursor.selection )
|
|
1072
|
+
this.startSelection( cursor );
|
|
1073
|
+
}
|
|
1074
|
+
else
|
|
1075
|
+
this.endSelection();
|
|
1015
1076
|
|
|
1016
|
-
|
|
1017
|
-
if( cursor.line == this.code.lines.length - 1 &&
|
|
1018
|
-
cursor.position == this.code.lines[ cursor.line ].length )
|
|
1019
|
-
return;
|
|
1077
|
+
this.cursorToString( cursor, substr, true );
|
|
1020
1078
|
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1079
|
+
if( e.shiftKey )
|
|
1080
|
+
this._processSelection( cursor, e );
|
|
1081
|
+
}
|
|
1082
|
+
else {
|
|
1083
|
+
var letter = this.getCharAtPos( cursor, -1 );
|
|
1084
|
+
if( letter ) {
|
|
1085
|
+
if( e.shiftKey ) {
|
|
1086
|
+
if( !cursor.selection ) this.startSelection( cursor );
|
|
1087
|
+
this.cursorToLeft( letter, cursor );
|
|
1088
|
+
this._processSelection( cursor, e, false, CodeEditor.SELECTION_X );
|
|
1089
|
+
}
|
|
1090
|
+
else {
|
|
1091
|
+
if( !cursor.selection ) {
|
|
1092
|
+
this.cursorToLeft( letter, cursor );
|
|
1093
|
+
if( this.useAutoComplete && this.isAutoCompleteActive )
|
|
1094
|
+
this.showAutoCompleteBox( 'foo', cursor );
|
|
1095
|
+
}
|
|
1096
|
+
else {
|
|
1097
|
+
cursor.selection.invertIfNecessary();
|
|
1098
|
+
this.resetCursorPos( CodeEditor.CURSOR_LEFT_TOP, cursor );
|
|
1099
|
+
this.cursorToLine( cursor, cursor.selection.fromY, true );
|
|
1100
|
+
this.cursorToPosition( cursor, cursor.selection.fromX );
|
|
1101
|
+
this.endSelection();
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
else if( cursor.line > 0 ) {
|
|
1030
1106
|
|
|
1031
|
-
|
|
1032
|
-
if( !word.length ) this.lineDown( cursor, true );
|
|
1033
|
-
var diff = cursor.position - from;
|
|
1034
|
-
var substr = word.substr( diff );
|
|
1107
|
+
if( e.shiftKey && !cursor.selection ) this.startSelection( cursor );
|
|
1035
1108
|
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
this.
|
|
1109
|
+
this.lineUp( cursor );
|
|
1110
|
+
|
|
1111
|
+
e.cancelShift = e.keepSelection = true;
|
|
1112
|
+
this.actions[ 'End' ].callback( cursor.line, cursor, e );
|
|
1113
|
+
delete e.cancelShift; delete e.keepSelection;
|
|
1114
|
+
|
|
1115
|
+
if( e.shiftKey ) this._processSelection( cursor, e, false );
|
|
1116
|
+
}
|
|
1040
1117
|
}
|
|
1041
|
-
|
|
1042
|
-
this.endSelection();
|
|
1118
|
+
});
|
|
1043
1119
|
|
|
1044
|
-
|
|
1120
|
+
this.action( 'ArrowRight', false, ( ln, cursor, e ) => {
|
|
1045
1121
|
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
{
|
|
1051
|
-
var letter = this.getCharAtPos( cursor );
|
|
1052
|
-
if( letter ) {
|
|
1122
|
+
// Nothing to do..
|
|
1123
|
+
if( cursor.line == this.code.lines.length - 1 &&
|
|
1124
|
+
cursor.position == this.code.lines[ cursor.line ].length )
|
|
1125
|
+
return;
|
|
1053
1126
|
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1127
|
+
if( e.metaKey ) // Apple devices (Command)
|
|
1128
|
+
{
|
|
1129
|
+
e.preventDefault();
|
|
1130
|
+
this.actions[ 'End' ].callback( ln, cursor );
|
|
1131
|
+
}
|
|
1132
|
+
else if( e.ctrlKey ) // Next word
|
|
1133
|
+
{
|
|
1134
|
+
// Get next word
|
|
1135
|
+
const [ word, from, to ] = this.getWordAtPos( cursor );
|
|
1136
|
+
|
|
1137
|
+
// If no length, we change line..
|
|
1138
|
+
if( !word.length ) this.lineDown( cursor, true );
|
|
1139
|
+
var diff = cursor.position - from;
|
|
1140
|
+
var substr = word.substr( diff );
|
|
1141
|
+
|
|
1142
|
+
// Selections...
|
|
1143
|
+
if( e.shiftKey ) {
|
|
1057
1144
|
if( !cursor.selection )
|
|
1058
1145
|
this.startSelection( cursor );
|
|
1059
|
-
|
|
1060
|
-
this.cursorToRight( letter, cursor );
|
|
1061
|
-
this._processSelection( cursor, e, false, CodeEditor.SELECTION_X );
|
|
1062
1146
|
}
|
|
1063
1147
|
else
|
|
1064
|
-
|
|
1065
|
-
|
|
1148
|
+
this.endSelection();
|
|
1149
|
+
|
|
1150
|
+
this.cursorToString( cursor, substr );
|
|
1151
|
+
|
|
1152
|
+
if( e.shiftKey )
|
|
1153
|
+
this._processSelection( cursor, e );
|
|
1154
|
+
}
|
|
1155
|
+
else // Next char
|
|
1156
|
+
{
|
|
1157
|
+
var letter = this.getCharAtPos( cursor );
|
|
1158
|
+
if( letter ) {
|
|
1159
|
+
|
|
1160
|
+
// Selecting chars
|
|
1161
|
+
if( e.shiftKey )
|
|
1162
|
+
{
|
|
1163
|
+
if( !cursor.selection )
|
|
1164
|
+
this.startSelection( cursor );
|
|
1165
|
+
|
|
1066
1166
|
this.cursorToRight( letter, cursor );
|
|
1067
|
-
|
|
1068
|
-
this.showAutoCompleteBox( 'foo', cursor );
|
|
1167
|
+
this._processSelection( cursor, e, false, CodeEditor.SELECTION_X );
|
|
1069
1168
|
}
|
|
1070
1169
|
else
|
|
1071
1170
|
{
|
|
1072
|
-
cursor.selection
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1171
|
+
if( !cursor.selection ) {
|
|
1172
|
+
this.cursorToRight( letter, cursor );
|
|
1173
|
+
if( this.useAutoComplete && this.isAutoCompleteActive )
|
|
1174
|
+
this.showAutoCompleteBox( 'foo', cursor );
|
|
1175
|
+
}
|
|
1176
|
+
else
|
|
1177
|
+
{
|
|
1178
|
+
cursor.selection.invertIfNecessary();
|
|
1179
|
+
this.resetCursorPos( CodeEditor.CURSOR_LEFT_TOP, cursor );
|
|
1180
|
+
this.cursorToLine( cursor, cursor.selection.toY );
|
|
1181
|
+
this.cursorToPosition( cursor, cursor.selection.toX );
|
|
1182
|
+
this.endSelection();
|
|
1183
|
+
}
|
|
1077
1184
|
}
|
|
1078
1185
|
}
|
|
1079
|
-
|
|
1080
|
-
else if( this.code.lines[ cursor.line + 1 ] !== undefined ) {
|
|
1186
|
+
else if( this.code.lines[ cursor.line + 1 ] !== undefined ) {
|
|
1081
1187
|
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1188
|
+
if( e.shiftKey ) {
|
|
1189
|
+
if( !cursor.selection ) this.startSelection( cursor );
|
|
1190
|
+
}
|
|
1191
|
+
else this.endSelection();
|
|
1086
1192
|
|
|
1087
|
-
|
|
1193
|
+
this.lineDown( cursor, true );
|
|
1088
1194
|
|
|
1089
|
-
|
|
1195
|
+
if( e.shiftKey ) this._processSelection( cursor, e, false );
|
|
1090
1196
|
|
|
1091
|
-
|
|
1197
|
+
this.hideAutoCompleteBox();
|
|
1198
|
+
}
|
|
1092
1199
|
}
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1095
1202
|
|
|
1096
1203
|
// Default code tab
|
|
1097
1204
|
|
|
@@ -1101,15 +1208,44 @@ class CodeEditor {
|
|
|
1101
1208
|
const onLoadAll = () => {
|
|
1102
1209
|
// Create inspector panel when the initial state is complete
|
|
1103
1210
|
// and we have at least 1 tab opened
|
|
1104
|
-
this.
|
|
1105
|
-
if( this.
|
|
1211
|
+
this.statusPanel = this._createStatusPanel();
|
|
1212
|
+
if( this.statusPanel )
|
|
1106
1213
|
{
|
|
1107
|
-
area.attach( this.
|
|
1214
|
+
area.attach( this.statusPanel );
|
|
1108
1215
|
}
|
|
1109
1216
|
|
|
1110
1217
|
// Wait until the fonts are all loaded
|
|
1111
1218
|
document.fonts.ready.then(() => {
|
|
1112
|
-
|
|
1219
|
+
// Load any font size from local storage
|
|
1220
|
+
const savedFontSize = window.localStorage.getItem( "lexcodeeditor-font-size" );
|
|
1221
|
+
if( savedFontSize )
|
|
1222
|
+
{
|
|
1223
|
+
this._setFontSize( parseInt( savedFontSize ) );
|
|
1224
|
+
}
|
|
1225
|
+
else // Use default size
|
|
1226
|
+
{
|
|
1227
|
+
const r = document.querySelector( ':root' );
|
|
1228
|
+
const s = getComputedStyle( r );
|
|
1229
|
+
this.fontSize = parseInt( s.getPropertyValue( "--code-editor-font-size" ) );
|
|
1230
|
+
this.charWidth = this._measureChar( "a", true );
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
LX.emit( "@font-size", this.fontSize );
|
|
1234
|
+
|
|
1235
|
+
// Get final sizes for editor elements based on Tabs and status bar offsets
|
|
1236
|
+
LX.doAsync( () => {
|
|
1237
|
+
this._verticalTopOffset = this.tabs?.root.getBoundingClientRect().height ?? 0;
|
|
1238
|
+
this._verticalBottomOffset = this.statusPanel?.root.getBoundingClientRect().height ?? 0;
|
|
1239
|
+
this._fullVerticalOffset = this._verticalTopOffset + this._verticalBottomOffset;
|
|
1240
|
+
|
|
1241
|
+
this.gutter.style.marginTop = `${ this._verticalTopOffset }px`;
|
|
1242
|
+
this.gutter.style.height = `calc(100% - ${ this._fullVerticalOffset }px)`;
|
|
1243
|
+
this.vScrollbar.root.style.marginTop = `${ this.tabs?.root.getBoundingClientRect().height ?? 0 }px`;
|
|
1244
|
+
this.vScrollbar.root.style.height = `calc(100% - ${ this._fullVerticalOffset }px)`;
|
|
1245
|
+
this.hScrollbar.root.style.bottom = `${ this._verticalBottomOffset }px`;
|
|
1246
|
+
this.codeArea.root.style.height = `calc(100% - ${ this._verticalBottomOffset }px)`;
|
|
1247
|
+
}, 50 );
|
|
1248
|
+
|
|
1113
1249
|
});
|
|
1114
1250
|
|
|
1115
1251
|
window.editor = this;
|
|
@@ -1153,39 +1289,38 @@ class CodeEditor {
|
|
|
1153
1289
|
onKeyPressed( e ) {
|
|
1154
1290
|
|
|
1155
1291
|
// Toggle visibility of the file explorer
|
|
1156
|
-
if( e.key == 'b' && e.ctrlKey && this.
|
|
1292
|
+
if( e.key == 'b' && e.ctrlKey && this.useFileExplorer )
|
|
1157
1293
|
{
|
|
1158
1294
|
this.explorerArea.root.classList.toggle( "hidden" );
|
|
1159
1295
|
if( this._lastBaseareaWidth )
|
|
1160
1296
|
{
|
|
1161
|
-
this.
|
|
1297
|
+
this.baseArea.root.style.width = this._lastBaseareaWidth;
|
|
1162
1298
|
delete this._lastBaseareaWidth;
|
|
1163
1299
|
|
|
1164
1300
|
} else
|
|
1165
1301
|
{
|
|
1166
|
-
this._lastBaseareaWidth = this.
|
|
1167
|
-
this.
|
|
1302
|
+
this._lastBaseareaWidth = this.baseArea.root.style.width;
|
|
1303
|
+
this.baseArea.root.style.width = "100%";
|
|
1168
1304
|
}
|
|
1169
1305
|
}
|
|
1170
1306
|
}
|
|
1171
1307
|
|
|
1172
1308
|
getText( min ) {
|
|
1173
|
-
|
|
1174
1309
|
return this.code.lines.join( min ? ' ' : '\n' );
|
|
1175
1310
|
}
|
|
1176
1311
|
|
|
1177
1312
|
// This can be used to empty all text...
|
|
1178
1313
|
setText( text = "", lang ) {
|
|
1179
1314
|
|
|
1180
|
-
let
|
|
1181
|
-
this.code.lines = [].concat(
|
|
1315
|
+
let newLines = text.split( '\n' );
|
|
1316
|
+
this.code.lines = [].concat( newLines );
|
|
1182
1317
|
|
|
1183
1318
|
this._removeSecondaryCursors();
|
|
1184
1319
|
|
|
1185
|
-
let cursor = this.
|
|
1186
|
-
let lastLine =
|
|
1320
|
+
let cursor = this.getCurrentCursor( true );
|
|
1321
|
+
let lastLine = newLines.pop();
|
|
1187
1322
|
|
|
1188
|
-
this.cursorToLine( cursor,
|
|
1323
|
+
this.cursorToLine( cursor, newLines.length ); // Already substracted 1
|
|
1189
1324
|
this.cursorToPosition( cursor, lastLine.length );
|
|
1190
1325
|
this.processLines();
|
|
1191
1326
|
|
|
@@ -1199,21 +1334,22 @@ class CodeEditor {
|
|
|
1199
1334
|
|
|
1200
1335
|
let lidx = cursor.line;
|
|
1201
1336
|
|
|
1202
|
-
if( cursor.selection )
|
|
1337
|
+
if( cursor.selection )
|
|
1338
|
+
{
|
|
1203
1339
|
this.deleteSelection( cursor );
|
|
1204
1340
|
lidx = cursor.line;
|
|
1205
1341
|
}
|
|
1206
1342
|
|
|
1207
1343
|
this.endSelection();
|
|
1208
1344
|
|
|
1209
|
-
const
|
|
1345
|
+
const newLines = text.replaceAll( '\r', '' ).split( '\n' );
|
|
1210
1346
|
|
|
1211
1347
|
// Pasting Multiline...
|
|
1212
|
-
if(
|
|
1348
|
+
if( newLines.length != 1 )
|
|
1213
1349
|
{
|
|
1214
|
-
let num_lines =
|
|
1350
|
+
let num_lines = newLines.length;
|
|
1215
1351
|
console.assert( num_lines > 0 );
|
|
1216
|
-
const first_line =
|
|
1352
|
+
const first_line = newLines.shift();
|
|
1217
1353
|
num_lines--;
|
|
1218
1354
|
|
|
1219
1355
|
const remaining = this.code.lines[ lidx ].slice( cursor.position );
|
|
@@ -1230,11 +1366,11 @@ class CodeEditor {
|
|
|
1230
1366
|
|
|
1231
1367
|
let _text = null;
|
|
1232
1368
|
|
|
1233
|
-
for( var i = 0; i <
|
|
1234
|
-
_text =
|
|
1369
|
+
for( var i = 0; i < newLines.length; ++i ) {
|
|
1370
|
+
_text = newLines[ i ];
|
|
1235
1371
|
this.cursorToLine( cursor, cursor.line++, true );
|
|
1236
1372
|
// Add remaining...
|
|
1237
|
-
if( i == (
|
|
1373
|
+
if( i == (newLines.length - 1) )
|
|
1238
1374
|
_text += remaining;
|
|
1239
1375
|
this.code.lines.splice( 1 + lidx + i, 0, _text );
|
|
1240
1376
|
}
|
|
@@ -1248,24 +1384,26 @@ class CodeEditor {
|
|
|
1248
1384
|
{
|
|
1249
1385
|
this.code.lines[ lidx ] = [
|
|
1250
1386
|
this.code.lines[ lidx ].slice( 0, cursor.position ),
|
|
1251
|
-
|
|
1387
|
+
newLines[ 0 ],
|
|
1252
1388
|
this.code.lines[ lidx ].slice( cursor.position )
|
|
1253
1389
|
].join('');
|
|
1254
1390
|
|
|
1255
|
-
this.cursorToPosition( cursor, ( cursor.position +
|
|
1391
|
+
this.cursorToPosition( cursor, ( cursor.position + newLines[ 0 ].length ) );
|
|
1256
1392
|
this.processLine( lidx );
|
|
1257
1393
|
}
|
|
1258
1394
|
|
|
1259
|
-
this.resize( null, ( scrollWidth, scrollHeight ) => {
|
|
1395
|
+
this.resize( CodeEditor.RESIZE_SCROLLBAR_H_V, null, ( scrollWidth, scrollHeight ) => {
|
|
1260
1396
|
var viewportSizeX = ( this.codeScroller.clientWidth + this.getScrollLeft() ) - CodeEditor.LINE_GUTTER_WIDTH; // Gutter offset
|
|
1261
1397
|
if( ( cursor.position * this.charWidth ) >= viewportSizeX )
|
|
1398
|
+
{
|
|
1262
1399
|
this.setScrollLeft( this.code.lines[ lidx ].length * this.charWidth );
|
|
1400
|
+
}
|
|
1263
1401
|
} );
|
|
1264
1402
|
}
|
|
1265
1403
|
|
|
1266
1404
|
loadFile( file, options = {} ) {
|
|
1267
1405
|
|
|
1268
|
-
const
|
|
1406
|
+
const _innerAddTab = ( text, name, title ) => {
|
|
1269
1407
|
|
|
1270
1408
|
// Remove Carriage Return in some cases and sub tabs using spaces
|
|
1271
1409
|
text = text.replaceAll( '\r', '' );
|
|
@@ -1277,16 +1415,24 @@ class CodeEditor {
|
|
|
1277
1415
|
|
|
1278
1416
|
// Add item in the explorer if used
|
|
1279
1417
|
|
|
1280
|
-
if( this.
|
|
1418
|
+
if( this.useFileExplorer || this.skipTabs )
|
|
1281
1419
|
{
|
|
1282
1420
|
this._tabStorage[ name ] = {
|
|
1283
1421
|
lines: lines,
|
|
1284
1422
|
options: options
|
|
1285
1423
|
};
|
|
1286
1424
|
|
|
1287
|
-
const ext =
|
|
1288
|
-
|
|
1289
|
-
this.
|
|
1425
|
+
const ext = CodeEditor.languages[ options.language ] ?. ext;
|
|
1426
|
+
|
|
1427
|
+
if( this.useFileExplorer )
|
|
1428
|
+
{
|
|
1429
|
+
this.addExplorerItem( { id: name, skipVisibility: true, icon: this._getFileIcon( name, ext ) } );
|
|
1430
|
+
this.explorer.innerTree.frefresh( name );
|
|
1431
|
+
}
|
|
1432
|
+
else
|
|
1433
|
+
{
|
|
1434
|
+
|
|
1435
|
+
}
|
|
1290
1436
|
}
|
|
1291
1437
|
else
|
|
1292
1438
|
{
|
|
@@ -1311,7 +1457,7 @@ class CodeEditor {
|
|
|
1311
1457
|
let filename = file;
|
|
1312
1458
|
LX.request({ url: filename, success: text => {
|
|
1313
1459
|
const name = filename.substring(filename.lastIndexOf( '/' ) + 1);
|
|
1314
|
-
|
|
1460
|
+
_innerAddTab( text, name, filename );
|
|
1315
1461
|
} });
|
|
1316
1462
|
}
|
|
1317
1463
|
else // File Blob
|
|
@@ -1320,82 +1466,11 @@ class CodeEditor {
|
|
|
1320
1466
|
fr.readAsText( file );
|
|
1321
1467
|
fr.onload = e => {
|
|
1322
1468
|
const text = e.currentTarget.result;
|
|
1323
|
-
|
|
1469
|
+
_innerAddTab( text, file.name );
|
|
1324
1470
|
};
|
|
1325
1471
|
}
|
|
1326
1472
|
}
|
|
1327
1473
|
|
|
1328
|
-
_addCursor( line = 0, position = 0, force, isMain = false ) {
|
|
1329
|
-
|
|
1330
|
-
// If cursor in that position exists, remove it instead..
|
|
1331
|
-
const exists = Array.from( this.cursors.children ).find( v => v.position == position && v.line == line );
|
|
1332
|
-
if( exists && !force )
|
|
1333
|
-
{
|
|
1334
|
-
if( !exists.isMain )
|
|
1335
|
-
exists.remove();
|
|
1336
|
-
|
|
1337
|
-
return;
|
|
1338
|
-
}
|
|
1339
|
-
|
|
1340
|
-
let cursor = document.createElement( 'div' );
|
|
1341
|
-
cursor.name = "cursor" + this.cursors.childElementCount;
|
|
1342
|
-
cursor.className = "cursor";
|
|
1343
|
-
cursor.innerHTML = " ";
|
|
1344
|
-
cursor.isMain = isMain;
|
|
1345
|
-
cursor._left = position * this.charWidth;
|
|
1346
|
-
cursor.style.left = "calc( " + cursor._left + "px + " + this.xPadding + " )";
|
|
1347
|
-
cursor._top = line * this.lineHeight;
|
|
1348
|
-
cursor.style.top = cursor._top + "px";
|
|
1349
|
-
cursor._position = position;
|
|
1350
|
-
cursor._line = line;
|
|
1351
|
-
cursor.print = (function() { console.log( this._line, this._position ) }).bind( cursor );
|
|
1352
|
-
cursor.isLast = (function() { return this.cursors.lastChild == cursor; }).bind( this );
|
|
1353
|
-
|
|
1354
|
-
Object.defineProperty( cursor, 'line', {
|
|
1355
|
-
get: (v) => { return cursor._line },
|
|
1356
|
-
set: (v) => {
|
|
1357
|
-
cursor._line = v;
|
|
1358
|
-
if( cursor.isMain ) this._setActiveLine( v );
|
|
1359
|
-
}
|
|
1360
|
-
} );
|
|
1361
|
-
|
|
1362
|
-
Object.defineProperty( cursor, 'position', {
|
|
1363
|
-
get: (v) => { return cursor._position },
|
|
1364
|
-
set: (v) => {
|
|
1365
|
-
cursor._position = v;
|
|
1366
|
-
if( cursor.isMain ) this._updateDataInfoPanel( "@cursor-pos", "Col " + ( v + 1 ) );
|
|
1367
|
-
}
|
|
1368
|
-
} );
|
|
1369
|
-
|
|
1370
|
-
this.cursors.appendChild( cursor );
|
|
1371
|
-
|
|
1372
|
-
return cursor;
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
|
-
_getCurrentCursor( removeOthers ) {
|
|
1376
|
-
|
|
1377
|
-
if( removeOthers )
|
|
1378
|
-
{
|
|
1379
|
-
this._removeSecondaryCursors();
|
|
1380
|
-
}
|
|
1381
|
-
|
|
1382
|
-
return this.cursors.children[ 0 ];
|
|
1383
|
-
}
|
|
1384
|
-
|
|
1385
|
-
_removeSecondaryCursors() {
|
|
1386
|
-
|
|
1387
|
-
while( this.cursors.childElementCount > 1 )
|
|
1388
|
-
this.cursors.lastChild.remove();
|
|
1389
|
-
}
|
|
1390
|
-
|
|
1391
|
-
_logCursors() {
|
|
1392
|
-
|
|
1393
|
-
for( let cursor of this.cursors.children )
|
|
1394
|
-
{
|
|
1395
|
-
cursor.print();
|
|
1396
|
-
}
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1399
1474
|
_addUndoStep( cursor, force, deleteRedo = true ) {
|
|
1400
1475
|
|
|
1401
1476
|
// Only the mainc cursor stores undo steps
|
|
@@ -1407,12 +1482,18 @@ class CodeEditor {
|
|
|
1407
1482
|
|
|
1408
1483
|
if( !force )
|
|
1409
1484
|
{
|
|
1410
|
-
if( !this._lastTime )
|
|
1485
|
+
if( !this._lastTime )
|
|
1486
|
+
{
|
|
1411
1487
|
this._lastTime = current;
|
|
1412
|
-
}
|
|
1413
|
-
|
|
1488
|
+
}
|
|
1489
|
+
else
|
|
1490
|
+
{
|
|
1491
|
+
if( ( current - this._lastTime ) > 2000 )
|
|
1492
|
+
{
|
|
1414
1493
|
this._lastTime = null;
|
|
1415
|
-
}
|
|
1494
|
+
}
|
|
1495
|
+
else
|
|
1496
|
+
{
|
|
1416
1497
|
// If time not enough, reset timer
|
|
1417
1498
|
this._lastTime = current;
|
|
1418
1499
|
return;
|
|
@@ -1466,7 +1547,9 @@ class CodeEditor {
|
|
|
1466
1547
|
|
|
1467
1548
|
// Only the mainc cursor stores redo steps
|
|
1468
1549
|
if( !cursor.isMain )
|
|
1550
|
+
{
|
|
1469
1551
|
return;
|
|
1552
|
+
}
|
|
1470
1553
|
|
|
1471
1554
|
this.code.redoSteps.push( {
|
|
1472
1555
|
lines: LX.deepCopy( this.code.lines ),
|
|
@@ -1496,7 +1579,9 @@ class CodeEditor {
|
|
|
1496
1579
|
|
|
1497
1580
|
// Generate new if needed
|
|
1498
1581
|
if( !currentCursor )
|
|
1582
|
+
{
|
|
1499
1583
|
currentCursor = this._addCursor();
|
|
1584
|
+
}
|
|
1500
1585
|
|
|
1501
1586
|
this.restoreCursor( currentCursor, step.cursors[ i ] );
|
|
1502
1587
|
}
|
|
@@ -1515,10 +1600,11 @@ class CodeEditor {
|
|
|
1515
1600
|
this._updateDataInfoPanel( "@highlight", lang );
|
|
1516
1601
|
this.processLines();
|
|
1517
1602
|
|
|
1518
|
-
const ext = langExtension ??
|
|
1603
|
+
const ext = langExtension ?? CodeEditor.languages[ lang ].ext;
|
|
1519
1604
|
const icon = this._getFileIcon( null, ext );
|
|
1520
1605
|
|
|
1521
1606
|
// Update tab icon
|
|
1607
|
+
if( !this.skipTabs )
|
|
1522
1608
|
{
|
|
1523
1609
|
const tab = this.tabs.tabDOMs[ this.code.tabName ];
|
|
1524
1610
|
tab.firstChild.remove();
|
|
@@ -1537,7 +1623,7 @@ class CodeEditor {
|
|
|
1537
1623
|
}
|
|
1538
1624
|
|
|
1539
1625
|
// Update explorer icon
|
|
1540
|
-
if( this.
|
|
1626
|
+
if( this.useFileExplorer )
|
|
1541
1627
|
{
|
|
1542
1628
|
const item = this.explorer.innerTree.data.children.filter( (v) => v.id === this.code.tabName )[ 0 ];
|
|
1543
1629
|
console.assert( item != undefined );
|
|
@@ -1553,9 +1639,9 @@ class CodeEditor {
|
|
|
1553
1639
|
return this._changeLanguage( this.code.language );
|
|
1554
1640
|
}
|
|
1555
1641
|
|
|
1556
|
-
for( let l in
|
|
1642
|
+
for( let l in CodeEditor.languages )
|
|
1557
1643
|
{
|
|
1558
|
-
const langExtension =
|
|
1644
|
+
const langExtension = CodeEditor.languages[ l ].ext;
|
|
1559
1645
|
|
|
1560
1646
|
if( langExtension.constructor == Array )
|
|
1561
1647
|
{
|
|
@@ -1577,103 +1663,150 @@ class CodeEditor {
|
|
|
1577
1663
|
this._changeLanguage( 'Plain Text' );
|
|
1578
1664
|
}
|
|
1579
1665
|
|
|
1580
|
-
|
|
1666
|
+
_createStatusPanel() {
|
|
1581
1667
|
|
|
1582
|
-
if(
|
|
1668
|
+
if( this.skipInfo )
|
|
1583
1669
|
{
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
panel.sameLine();
|
|
1587
|
-
panel.addLabel( this.code.title, { fit: true, signal: "@tab-name" });
|
|
1588
|
-
panel.addLabel( "Ln " + 1, { fit: true, signal: "@cursor-line" });
|
|
1589
|
-
panel.addLabel( "Col " + 1, { fit: true, signal: "@cursor-pos" });
|
|
1590
|
-
panel.addButton( null, "Spaces: " + this.tabSpaces, ( value, event ) => {
|
|
1591
|
-
LX.addContextMenu( "Spaces", event, m => {
|
|
1592
|
-
const options = [ 2, 4, 8 ];
|
|
1593
|
-
for( const n of options )
|
|
1594
|
-
m.add( n, (v) => {
|
|
1595
|
-
this.tabSpaces = v;
|
|
1596
|
-
this.processLines();
|
|
1597
|
-
this._updateDataInfoPanel( "@tab-spaces", "Spaces: " + this.tabSpaces );
|
|
1598
|
-
} );
|
|
1599
|
-
});
|
|
1600
|
-
}, { nameWidth: "15%", signal: "@tab-spaces" });
|
|
1601
|
-
panel.addButton( "<b>{ }</b>", this.highlight, ( value, event ) => {
|
|
1602
|
-
LX.addContextMenu( "Language", event, m => {
|
|
1603
|
-
for( const lang of Object.keys( this.languages ) )
|
|
1604
|
-
{
|
|
1605
|
-
m.add( lang, v => {
|
|
1606
|
-
this._changeLanguage( v, null, true )
|
|
1607
|
-
} );
|
|
1608
|
-
}
|
|
1609
|
-
});
|
|
1610
|
-
}, { nameWidth: "15%", signal: "@highlight" });
|
|
1611
|
-
panel.endLine();
|
|
1612
|
-
|
|
1613
|
-
return panel;
|
|
1670
|
+
return;
|
|
1614
1671
|
}
|
|
1615
|
-
else
|
|
1616
|
-
{
|
|
1617
|
-
doAsync( () => {
|
|
1618
1672
|
|
|
1619
|
-
|
|
1620
|
-
this.gutter.style.height = "calc(100% - 28px)";
|
|
1621
|
-
this.root.querySelectorAll( '.code' ).forEach( e => e.style.height = "calc(100% - 6px)" );
|
|
1622
|
-
this.root.querySelector( '.lexareatabscontent' ).style.height = "calc(100% - 23px)";
|
|
1623
|
-
this.base_area.root.querySelector( '.lexcodescrollbar.vertical' ).style.height = "calc(100% - 27px)";
|
|
1624
|
-
this.tabs.area.root.classList.add( 'no-code-info' );
|
|
1673
|
+
let panel = new LX.Panel({ className: "lexcodetabinfo flex flex-row", height: "auto" });
|
|
1625
1674
|
|
|
1626
|
-
|
|
1675
|
+
let leftStatusPanel = new LX.Panel( { id: "FontSizeZoomStatusComponent", height: "auto" } );
|
|
1676
|
+
leftStatusPanel.sameLine();
|
|
1677
|
+
|
|
1678
|
+
if( this.skipTabs )
|
|
1679
|
+
{
|
|
1680
|
+
leftStatusPanel.addButton( null, "ZoomOutButton", this._decreaseFontSize.bind( this ), { icon: "ZoomOut", width: "32px", title: "Zoom Out", tooltip: true } );
|
|
1627
1681
|
}
|
|
1682
|
+
|
|
1683
|
+
leftStatusPanel.addButton( null, "ZoomOutButton", this._decreaseFontSize.bind( this ), { icon: "ZoomOut", width: "32px", title: "Zoom Out", tooltip: true } );
|
|
1684
|
+
leftStatusPanel.addLabel( this.fontSize ?? 14, { fit: true, signal: "@font-size" });
|
|
1685
|
+
leftStatusPanel.addButton( null, "ZoomInButton", this._increaseFontSize.bind( this ), { icon: "ZoomIn", width: "32px", title: "Zoom In", tooltip: true } );
|
|
1686
|
+
leftStatusPanel.endLine( "justify-start" );
|
|
1687
|
+
panel.attach( leftStatusPanel.root );
|
|
1688
|
+
|
|
1689
|
+
let rightStatusPanel = new LX.Panel( { height: "auto" } );
|
|
1690
|
+
rightStatusPanel.sameLine();
|
|
1691
|
+
rightStatusPanel.addLabel( this.code.title, { id: "EditorFilenameStatusComponent", fit: true, signal: "@tab-name" });
|
|
1692
|
+
rightStatusPanel.addButton( null, "Ln 1, Col 1", this.showSearchLineBox.bind( this ), { id: "EditorSelectionStatusComponent", fit: true, signal: "@cursor-data" });
|
|
1693
|
+
rightStatusPanel.addButton( null, "Spaces: " + this.tabSpaces, ( value, event ) => {
|
|
1694
|
+
LX.addContextMenu( "Spaces", event, m => {
|
|
1695
|
+
const options = [ 2, 4, 8 ];
|
|
1696
|
+
for( const n of options )
|
|
1697
|
+
m.add( n, (v) => {
|
|
1698
|
+
this.tabSpaces = v;
|
|
1699
|
+
this.processLines();
|
|
1700
|
+
this._updateDataInfoPanel( "@tab-spaces", "Spaces: " + this.tabSpaces );
|
|
1701
|
+
} );
|
|
1702
|
+
});
|
|
1703
|
+
}, { id: "EditorIndentationStatusComponent", nameWidth: "15%", signal: "@tab-spaces" });
|
|
1704
|
+
rightStatusPanel.addButton( "<b>{ }</b>", this.highlight, ( value, event ) => {
|
|
1705
|
+
LX.addContextMenu( "Language", event, m => {
|
|
1706
|
+
for( const lang of Object.keys( CodeEditor.languages ) )
|
|
1707
|
+
{
|
|
1708
|
+
m.add( lang, v => {
|
|
1709
|
+
this._changeLanguage( v, null, true )
|
|
1710
|
+
} );
|
|
1711
|
+
}
|
|
1712
|
+
});
|
|
1713
|
+
}, { id: "EditorLanguageStatusComponent", nameWidth: "15%", signal: "@highlight" });
|
|
1714
|
+
rightStatusPanel.endLine( "justify-end" );
|
|
1715
|
+
panel.attach( rightStatusPanel.root );
|
|
1716
|
+
|
|
1717
|
+
const itemVisibilityMap = {
|
|
1718
|
+
"Font Size Zoom": true,
|
|
1719
|
+
"Editor Filename": true,
|
|
1720
|
+
"Editor Selection": true,
|
|
1721
|
+
"Editor Indentation": true,
|
|
1722
|
+
"Editor Language": true,
|
|
1723
|
+
};
|
|
1724
|
+
|
|
1725
|
+
panel.root.addEventListener( "contextmenu", (e) => {
|
|
1726
|
+
|
|
1727
|
+
if( e.target && ( e.target.classList.contains( "lexpanel" ) || e.target.classList.contains( "lexinlinecomponents" ) ) )
|
|
1728
|
+
{
|
|
1729
|
+
return;
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
const menuOptions = Object.keys( itemVisibilityMap ).map( ( itemName, idx ) => {
|
|
1733
|
+
const item = {
|
|
1734
|
+
name: itemName,
|
|
1735
|
+
icon: "Check",
|
|
1736
|
+
callback: () => {
|
|
1737
|
+
itemVisibilityMap[ itemName ] = !itemVisibilityMap[ itemName ];
|
|
1738
|
+
const b = panel.root.querySelector( `#${ itemName.replaceAll( " ", "" ) }StatusComponent` );
|
|
1739
|
+
console.assert( b, `${ itemName } has no status button!` );
|
|
1740
|
+
b.classList.toggle( "hidden", !itemVisibilityMap[ itemName ] );
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
if( !itemVisibilityMap[ itemName ] ) delete item.icon;
|
|
1744
|
+
return item;
|
|
1745
|
+
} );
|
|
1746
|
+
new LX.DropdownMenu( e.target, menuOptions, { side: "top", align: "start" });
|
|
1747
|
+
} );
|
|
1748
|
+
|
|
1749
|
+
return panel;
|
|
1628
1750
|
}
|
|
1629
1751
|
|
|
1630
|
-
_getFileIcon( name, extension ) {
|
|
1752
|
+
_getFileIcon( name, extension, lang ) {
|
|
1631
1753
|
|
|
1632
1754
|
const isNewTabButton = name ? ( name === '+' ) : false;
|
|
1633
|
-
|
|
1634
|
-
if( !extension )
|
|
1755
|
+
if( isNewTabButton )
|
|
1635
1756
|
{
|
|
1636
|
-
|
|
1757
|
+
return;
|
|
1637
1758
|
}
|
|
1638
|
-
else
|
|
1639
|
-
{
|
|
1640
|
-
const possibleExtensions = [].concat( extension );
|
|
1641
1759
|
|
|
1642
|
-
|
|
1760
|
+
if( !lang )
|
|
1761
|
+
{
|
|
1762
|
+
if( !extension )
|
|
1643
1763
|
{
|
|
1644
|
-
|
|
1645
|
-
|
|
1764
|
+
extension = LX.getExtension( name );
|
|
1765
|
+
}
|
|
1766
|
+
else
|
|
1767
|
+
{
|
|
1768
|
+
const possibleExtensions = [].concat( extension );
|
|
1769
|
+
|
|
1770
|
+
if( name )
|
|
1771
|
+
{
|
|
1772
|
+
const fileExtension = LX.getExtension( name );
|
|
1773
|
+
const idx = possibleExtensions.indexOf( fileExtension );
|
|
1646
1774
|
|
|
1647
|
-
|
|
1775
|
+
if( idx > -1)
|
|
1776
|
+
{
|
|
1777
|
+
extension = possibleExtensions[ idx ];
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
else
|
|
1648
1781
|
{
|
|
1649
|
-
extension = possibleExtensions[
|
|
1782
|
+
extension = possibleExtensions[ 0 ];
|
|
1650
1783
|
}
|
|
1651
1784
|
}
|
|
1652
|
-
|
|
1785
|
+
|
|
1786
|
+
for( const [ l, lData ] of Object.entries( CodeEditor.languages ) )
|
|
1653
1787
|
{
|
|
1654
|
-
|
|
1788
|
+
const extensions = [].concat( lData.ext );
|
|
1789
|
+
if( extensions.includes( extension ) )
|
|
1790
|
+
{
|
|
1791
|
+
lang = l;
|
|
1792
|
+
break;
|
|
1793
|
+
}
|
|
1655
1794
|
}
|
|
1795
|
+
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
const iconPlusClasses = CodeEditor.languages[ lang ]?.icon;
|
|
1799
|
+
if( iconPlusClasses )
|
|
1800
|
+
{
|
|
1801
|
+
return iconPlusClasses[ extension ] ?? iconPlusClasses;
|
|
1656
1802
|
}
|
|
1657
1803
|
|
|
1658
|
-
return
|
|
1659
|
-
extension == "css" ? "Hash dodgerblue" :
|
|
1660
|
-
extension == "xml" ? "Rss orange" :
|
|
1661
|
-
extension == "bat" ? "Windows lightblue" :
|
|
1662
|
-
extension == "json" ? "Braces fg-primary" :
|
|
1663
|
-
extension == "js" ? "Js goldenrod" :
|
|
1664
|
-
extension == "py" ? "Python munsellblue" :
|
|
1665
|
-
extension == "rs" ? "Rust fg-primary" :
|
|
1666
|
-
extension == "md" ? "Markdown fg-primary" :
|
|
1667
|
-
extension == "cpp" ? "CPlusPlus pictonblue" :
|
|
1668
|
-
extension == "hpp" ? "CPlusPlus heliotrope" :
|
|
1669
|
-
extension == "c" ? "C pictonblue" :
|
|
1670
|
-
extension == "h" ? "C heliotrope" :
|
|
1671
|
-
!isNewTabButton ? "AlignLeft gray" : undefined;
|
|
1804
|
+
return "AlignLeft gray";
|
|
1672
1805
|
}
|
|
1673
1806
|
|
|
1674
1807
|
_onNewTab( e ) {
|
|
1675
1808
|
|
|
1676
|
-
this.processFocus(false);
|
|
1809
|
+
this.processFocus( false );
|
|
1677
1810
|
|
|
1678
1811
|
LX.addContextMenu( null, e, m => {
|
|
1679
1812
|
m.add( "Create", this.addTab.bind( this, "unnamed.js", true, "", { language: "JavaScript" } ) );
|
|
@@ -1696,7 +1829,7 @@ class CodeEditor {
|
|
|
1696
1829
|
|
|
1697
1830
|
this._removeSecondaryCursors();
|
|
1698
1831
|
|
|
1699
|
-
var cursor = this.
|
|
1832
|
+
var cursor = this.getCurrentCursor( true );
|
|
1700
1833
|
this.saveCursor( cursor, this.code.cursorState );
|
|
1701
1834
|
this.code = this.loadedTabs[ name ];
|
|
1702
1835
|
this.restoreCursor( cursor, this.code.cursorState );
|
|
@@ -1720,12 +1853,12 @@ class CodeEditor {
|
|
|
1720
1853
|
_onContextMenuTab( isNewTabButton, event, name, ) {
|
|
1721
1854
|
|
|
1722
1855
|
if( isNewTabButton )
|
|
1723
|
-
|
|
1856
|
+
return;
|
|
1724
1857
|
|
|
1725
1858
|
LX.addContextMenu( null, event, m => {
|
|
1726
1859
|
m.add( "Close", () => { this.tabs.delete( name ) } );
|
|
1727
|
-
m.add( "" );
|
|
1728
|
-
m.add( "Rename", () => { console.warn( "TODO" )} );
|
|
1860
|
+
// m.add( "" );
|
|
1861
|
+
// m.add( "Rename", () => { console.warn( "TODO" )} );
|
|
1729
1862
|
});
|
|
1730
1863
|
}
|
|
1731
1864
|
|
|
@@ -1752,6 +1885,9 @@ class CodeEditor {
|
|
|
1752
1885
|
code.cursorState = {};
|
|
1753
1886
|
code.undoSteps = [];
|
|
1754
1887
|
code.redoSteps = [];
|
|
1888
|
+
code.lineScopes = [];
|
|
1889
|
+
code.lineSymbols = [];
|
|
1890
|
+
code.symbolsTable = new Map();
|
|
1755
1891
|
code.tabName = name;
|
|
1756
1892
|
code.title = title ?? name;
|
|
1757
1893
|
code.tokens = {};
|
|
@@ -1778,21 +1914,24 @@ class CodeEditor {
|
|
|
1778
1914
|
|
|
1779
1915
|
const tabIcon = this._getFileIcon( name );
|
|
1780
1916
|
|
|
1781
|
-
if( this.
|
|
1917
|
+
if( this.useFileExplorer && !isNewTabButton )
|
|
1782
1918
|
{
|
|
1783
1919
|
this.addExplorerItem( { id: name, skipVisibility: true, icon: tabIcon } );
|
|
1784
1920
|
this.explorer.innerTree.frefresh( name );
|
|
1785
1921
|
}
|
|
1786
1922
|
|
|
1787
|
-
this.
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1923
|
+
if( !this.skipTabs )
|
|
1924
|
+
{
|
|
1925
|
+
this.tabs.add( name, code, {
|
|
1926
|
+
selected: selected,
|
|
1927
|
+
fixed: isNewTabButton,
|
|
1928
|
+
title: code.title,
|
|
1929
|
+
icon: tabIcon,
|
|
1930
|
+
onSelect: this._onSelectTab.bind( this, isNewTabButton ),
|
|
1931
|
+
onContextMenu: this._onContextMenuTab.bind( this, isNewTabButton ),
|
|
1932
|
+
allowDelete: true
|
|
1933
|
+
} );
|
|
1934
|
+
}
|
|
1796
1935
|
|
|
1797
1936
|
// Move into the sizer..
|
|
1798
1937
|
this.codeSizer.appendChild( code );
|
|
@@ -1818,17 +1957,20 @@ class CodeEditor {
|
|
|
1818
1957
|
return name;
|
|
1819
1958
|
}
|
|
1820
1959
|
|
|
1821
|
-
|
|
1960
|
+
loadCode( name ) {
|
|
1961
|
+
|
|
1962
|
+
// Hide all others
|
|
1963
|
+
this.codeSizer.querySelectorAll( ".code" ).forEach( c => c.classList.add( "hidden" ) );
|
|
1822
1964
|
|
|
1823
1965
|
// Already open...
|
|
1824
1966
|
if( this.openedTabs[ name ] )
|
|
1825
1967
|
{
|
|
1826
|
-
this.
|
|
1968
|
+
let code = this.openedTabs[ name ]
|
|
1969
|
+
code.classList.remove( "hidden" );
|
|
1827
1970
|
return;
|
|
1828
1971
|
}
|
|
1829
1972
|
|
|
1830
1973
|
let code = this.loadedTabs[ name ]
|
|
1831
|
-
|
|
1832
1974
|
if( !code )
|
|
1833
1975
|
{
|
|
1834
1976
|
this.addTab( name, true );
|
|
@@ -1856,15 +1998,66 @@ class CodeEditor {
|
|
|
1856
1998
|
|
|
1857
1999
|
this.openedTabs[ name ] = code;
|
|
1858
2000
|
|
|
1859
|
-
|
|
1860
|
-
|
|
2001
|
+
// Move into the sizer..
|
|
2002
|
+
this.codeSizer.appendChild( code );
|
|
1861
2003
|
|
|
1862
|
-
this.
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
2004
|
+
this.endSelection();
|
|
2005
|
+
|
|
2006
|
+
// Select as current...
|
|
2007
|
+
this.code = code;
|
|
2008
|
+
this.resetCursorPos( CodeEditor.CURSOR_LEFT_TOP );
|
|
2009
|
+
this.processLines();
|
|
2010
|
+
this._changeLanguageFromExtension( LX.getExtension( name ) );
|
|
2011
|
+
this._updateDataInfoPanel( "@tab-name", code.tabName );
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
loadTab( name ) {
|
|
2015
|
+
|
|
2016
|
+
// Already open...
|
|
2017
|
+
if( this.openedTabs[ name ] )
|
|
2018
|
+
{
|
|
2019
|
+
this.tabs.select( name );
|
|
2020
|
+
return;
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
let code = this.loadedTabs[ name ]
|
|
2024
|
+
|
|
2025
|
+
if( !code )
|
|
2026
|
+
{
|
|
2027
|
+
this.addTab( name, true );
|
|
2028
|
+
|
|
2029
|
+
// Unload lines from storage...
|
|
2030
|
+
const tabData = this._tabStorage[ name ];
|
|
2031
|
+
if( tabData )
|
|
2032
|
+
{
|
|
2033
|
+
this.code.lines = tabData.lines;
|
|
2034
|
+
|
|
2035
|
+
if( tabData.options.language )
|
|
2036
|
+
{
|
|
2037
|
+
this._changeLanguage( tabData.options.language, null, true );
|
|
2038
|
+
}
|
|
2039
|
+
else
|
|
2040
|
+
{
|
|
2041
|
+
this._changeLanguageFromExtension( LX.getExtension( name ) );
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
delete this._tabStorage[ name ];
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
return;
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
this.openedTabs[ name ] = code;
|
|
2051
|
+
|
|
2052
|
+
const isNewTabButton = ( name === '+' );
|
|
2053
|
+
const tabIcon = this._getFileIcon( name );
|
|
2054
|
+
|
|
2055
|
+
this.tabs.add(name, code, {
|
|
2056
|
+
selected: true,
|
|
2057
|
+
fixed: isNewTabButton,
|
|
2058
|
+
title: code.title,
|
|
2059
|
+
icon: tabIcon,
|
|
2060
|
+
onSelect: this._onSelectTab.bind( this, isNewTabButton ),
|
|
1868
2061
|
onContextMenu: this._onContextMenuTab.bind( this, isNewTabButton ),
|
|
1869
2062
|
allowDelete: true
|
|
1870
2063
|
});
|
|
@@ -1899,12 +2092,14 @@ class CodeEditor {
|
|
|
1899
2092
|
}
|
|
1900
2093
|
|
|
1901
2094
|
loadTabFromFile() {
|
|
2095
|
+
|
|
1902
2096
|
const input = document.createElement( 'input' );
|
|
1903
2097
|
input.type = 'file';
|
|
1904
2098
|
document.body.appendChild( input );
|
|
1905
2099
|
input.click();
|
|
1906
2100
|
input.addEventListener('change', e => {
|
|
1907
|
-
if (e.target.files[ 0 ])
|
|
2101
|
+
if (e.target.files[ 0 ])
|
|
2102
|
+
{
|
|
1908
2103
|
this.loadFile( e.target.files[ 0 ] );
|
|
1909
2104
|
}
|
|
1910
2105
|
input.remove();
|
|
@@ -1914,8 +2109,11 @@ class CodeEditor {
|
|
|
1914
2109
|
processFocus( active ) {
|
|
1915
2110
|
|
|
1916
2111
|
if( active )
|
|
2112
|
+
{
|
|
1917
2113
|
this.restartBlink();
|
|
1918
|
-
|
|
2114
|
+
}
|
|
2115
|
+
else
|
|
2116
|
+
{
|
|
1919
2117
|
clearInterval( this.blinker );
|
|
1920
2118
|
this.cursors.classList.remove( 'show' );
|
|
1921
2119
|
}
|
|
@@ -1923,10 +2121,10 @@ class CodeEditor {
|
|
|
1923
2121
|
|
|
1924
2122
|
processMouse( e ) {
|
|
1925
2123
|
|
|
1926
|
-
if( !e.target.classList.contains('code') && !e.target.classList.contains('
|
|
2124
|
+
if( !e.target.classList.contains('code') && !e.target.classList.contains('lexcodearea') ) return;
|
|
1927
2125
|
if( !this.code ) return;
|
|
1928
2126
|
|
|
1929
|
-
var cursor = this.
|
|
2127
|
+
var cursor = this.getCurrentCursor();
|
|
1930
2128
|
var code_rect = this.code.getBoundingClientRect();
|
|
1931
2129
|
var mouse_pos = [(e.clientX - code_rect.x), (e.clientY - code_rect.y)];
|
|
1932
2130
|
|
|
@@ -2021,14 +2219,17 @@ class CodeEditor {
|
|
|
2021
2219
|
|
|
2022
2220
|
_onMouseUp( e ) {
|
|
2023
2221
|
|
|
2024
|
-
if( (LX.getTime() - this.lastMouseDown) < 120 )
|
|
2222
|
+
if( ( LX.getTime() - this.lastMouseDown ) < 120 )
|
|
2223
|
+
{
|
|
2025
2224
|
this.state.selectingText = false;
|
|
2026
2225
|
this.endSelection();
|
|
2027
2226
|
}
|
|
2028
2227
|
|
|
2029
|
-
const cursor = this.
|
|
2228
|
+
const cursor = this.getCurrentCursor();
|
|
2030
2229
|
if( cursor.selection )
|
|
2230
|
+
{
|
|
2031
2231
|
cursor.selection.invertIfNecessary();
|
|
2232
|
+
}
|
|
2032
2233
|
|
|
2033
2234
|
this.state.selectingText = false;
|
|
2034
2235
|
delete this._lastSelectionKeyDir;
|
|
@@ -2036,7 +2237,7 @@ class CodeEditor {
|
|
|
2036
2237
|
|
|
2037
2238
|
processClick( e ) {
|
|
2038
2239
|
|
|
2039
|
-
var cursor = this.
|
|
2240
|
+
var cursor = this.getCurrentCursor();
|
|
2040
2241
|
var code_rect = this.codeScroller.getBoundingClientRect();
|
|
2041
2242
|
var position = [( e.clientX - code_rect.x ) + this.getScrollLeft(), (e.clientY - code_rect.y) + this.getScrollTop()];
|
|
2042
2243
|
var ln = (position[ 1 ] / this.lineHeight)|0;
|
|
@@ -2053,7 +2254,6 @@ class CodeEditor {
|
|
|
2053
2254
|
{
|
|
2054
2255
|
// Make sure we only keep the main cursor..
|
|
2055
2256
|
this._removeSecondaryCursors();
|
|
2056
|
-
|
|
2057
2257
|
this.cursorToLine( cursor, ln, true );
|
|
2058
2258
|
this.cursorToPosition( cursor, string.length );
|
|
2059
2259
|
}
|
|
@@ -2067,38 +2267,45 @@ class CodeEditor {
|
|
|
2067
2267
|
this.hideAutoCompleteBox();
|
|
2068
2268
|
}
|
|
2069
2269
|
|
|
2070
|
-
updateSelections( e,
|
|
2270
|
+
updateSelections( e, keepRange, flags = CodeEditor.SELECTION_X_Y ) {
|
|
2071
2271
|
|
|
2072
2272
|
for( let cursor of this.cursors.children )
|
|
2073
2273
|
{
|
|
2074
2274
|
if( !cursor.selection )
|
|
2275
|
+
{
|
|
2075
2276
|
continue;
|
|
2277
|
+
}
|
|
2076
2278
|
|
|
2077
|
-
this._processSelection( cursor, e,
|
|
2279
|
+
this._processSelection( cursor, e, keepRange, flags );
|
|
2078
2280
|
}
|
|
2079
2281
|
}
|
|
2080
2282
|
|
|
2081
|
-
processSelections( e,
|
|
2283
|
+
processSelections( e, keepRange, flags = CodeEditor.SELECTION_X_Y ) {
|
|
2082
2284
|
|
|
2083
2285
|
for( let cursor of this.cursors.children )
|
|
2084
2286
|
{
|
|
2085
|
-
this._processSelection( cursor, e,
|
|
2287
|
+
this._processSelection( cursor, e, keepRange, flags );
|
|
2086
2288
|
}
|
|
2087
2289
|
}
|
|
2088
2290
|
|
|
2089
|
-
_processSelection( cursor, e,
|
|
2291
|
+
_processSelection( cursor, e, keepRange, flags = CodeEditor.SELECTION_X_Y ) {
|
|
2090
2292
|
|
|
2091
2293
|
const isMouseEvent = e && ( e.constructor == MouseEvent );
|
|
2092
2294
|
|
|
2093
|
-
if( isMouseEvent )
|
|
2295
|
+
if( isMouseEvent )
|
|
2296
|
+
{
|
|
2297
|
+
this.processClick( e );
|
|
2298
|
+
}
|
|
2094
2299
|
|
|
2095
2300
|
if( !cursor.selection )
|
|
2301
|
+
{
|
|
2096
2302
|
this.startSelection( cursor );
|
|
2303
|
+
}
|
|
2097
2304
|
|
|
2098
2305
|
this._hideActiveLine();
|
|
2099
2306
|
|
|
2100
2307
|
// Update selection
|
|
2101
|
-
if( !
|
|
2308
|
+
if( !keepRange )
|
|
2102
2309
|
{
|
|
2103
2310
|
let ccw = true;
|
|
2104
2311
|
|
|
@@ -2165,19 +2372,19 @@ class CodeEditor {
|
|
|
2165
2372
|
}
|
|
2166
2373
|
|
|
2167
2374
|
// Compute new width and selection margins
|
|
2168
|
-
let string;
|
|
2375
|
+
let string = "";
|
|
2169
2376
|
|
|
2170
|
-
if(sId == 0) // First line 2 cases (single line, multiline)
|
|
2377
|
+
if( sId == 0 ) // First line 2 cases (single line, multiline)
|
|
2171
2378
|
{
|
|
2172
2379
|
const reverse = fromX > toX;
|
|
2173
2380
|
if(deltaY == 0) string = !reverse ? this.code.lines[ i ].substring( fromX, toX ) : this.code.lines[ i ].substring(toX, fromX);
|
|
2174
2381
|
else string = this.code.lines[ i ].substr( fromX );
|
|
2175
|
-
const pixels = (reverse && deltaY == 0 ? toX : fromX) * this.charWidth;
|
|
2176
|
-
if( isVisible ) domEl.style.left =
|
|
2382
|
+
const pixels = ( reverse && deltaY == 0 ? toX : fromX ) * this.charWidth;
|
|
2383
|
+
if( isVisible ) domEl.style.left = `calc(${ pixels }px + ${ this.xPadding })`;
|
|
2177
2384
|
}
|
|
2178
2385
|
else
|
|
2179
2386
|
{
|
|
2180
|
-
string = (i == toY) ? this.code.lines[ i ].substring( 0, toX ) : this.code.lines[ i ]; // Last line, any multiple line...
|
|
2387
|
+
string = ( i == toY ) ? this.code.lines[ i ].substring( 0, toX ) : this.code.lines[ i ]; // Last line, any multiple line...
|
|
2181
2388
|
if( isVisible ) domEl.style.left = this.xPadding;
|
|
2182
2389
|
}
|
|
2183
2390
|
|
|
@@ -2186,7 +2393,7 @@ class CodeEditor {
|
|
|
2186
2393
|
|
|
2187
2394
|
if( isVisible )
|
|
2188
2395
|
{
|
|
2189
|
-
domEl.style.width = (stringWidth || 8) + "px";
|
|
2396
|
+
domEl.style.width = ( stringWidth || 8 ) + "px";
|
|
2190
2397
|
domEl._top = i * this.lineHeight;
|
|
2191
2398
|
domEl.style.top = domEl._top + "px";
|
|
2192
2399
|
}
|
|
@@ -2207,7 +2414,7 @@ class CodeEditor {
|
|
|
2207
2414
|
{
|
|
2208
2415
|
// Make sure that the line selection is generated...
|
|
2209
2416
|
domEl = cursorSelections.childNodes[ sId ];
|
|
2210
|
-
if(!domEl)
|
|
2417
|
+
if( !domEl )
|
|
2211
2418
|
{
|
|
2212
2419
|
domEl = document.createElement( 'div' );
|
|
2213
2420
|
domEl.className = "lexcodeselection";
|
|
@@ -2326,7 +2533,7 @@ class CodeEditor {
|
|
|
2326
2533
|
|
|
2327
2534
|
_processGlobalKeys( e, key ) {
|
|
2328
2535
|
|
|
2329
|
-
let cursor = this.
|
|
2536
|
+
let cursor = this.getCurrentCursor();
|
|
2330
2537
|
|
|
2331
2538
|
if( e.ctrlKey || e.metaKey )
|
|
2332
2539
|
{
|
|
@@ -2394,7 +2601,7 @@ class CodeEditor {
|
|
|
2394
2601
|
|
|
2395
2602
|
async _processKeyAtCursor( e, key, cursor ) {
|
|
2396
2603
|
|
|
2397
|
-
const
|
|
2604
|
+
const skipUndo = e.detail.skipUndo ?? false;
|
|
2398
2605
|
|
|
2399
2606
|
// keys with length > 1 are probably special keys
|
|
2400
2607
|
if( key.length > 1 && this.specialKeys.indexOf( key ) == -1 )
|
|
@@ -2484,7 +2691,7 @@ class CodeEditor {
|
|
|
2484
2691
|
|
|
2485
2692
|
// Add undo steps
|
|
2486
2693
|
|
|
2487
|
-
if( !
|
|
2694
|
+
if( !skipUndo && this.code.lines.length )
|
|
2488
2695
|
{
|
|
2489
2696
|
this._addUndoStep( cursor );
|
|
2490
2697
|
}
|
|
@@ -2545,17 +2752,7 @@ class CodeEditor {
|
|
|
2545
2752
|
this.processLine( lidx );
|
|
2546
2753
|
|
|
2547
2754
|
// We are out of the viewport and max length is different? Resize scrollbars...
|
|
2548
|
-
|
|
2549
|
-
const numViewportChars = Math.floor( ( this.codeScroller.clientWidth - CodeEditor.LINE_GUTTER_WIDTH ) / this.charWidth );
|
|
2550
|
-
if( maxLineLength >= numViewportChars && maxLineLength != this._lastMaxLineLength )
|
|
2551
|
-
{
|
|
2552
|
-
this.resize( maxLineLength, () => {
|
|
2553
|
-
if( cursor.position > numViewportChars )
|
|
2554
|
-
{
|
|
2555
|
-
this.setScrollLeft( cursor.position * this.charWidth );
|
|
2556
|
-
}
|
|
2557
|
-
} );
|
|
2558
|
-
}
|
|
2755
|
+
this.resizeIfNecessary( cursor );
|
|
2559
2756
|
|
|
2560
2757
|
// Manage autocomplete
|
|
2561
2758
|
|
|
@@ -2567,6 +2764,8 @@ class CodeEditor {
|
|
|
2567
2764
|
|
|
2568
2765
|
async _pasteContent( cursor ) {
|
|
2569
2766
|
|
|
2767
|
+
const mustDetectLanguage = ( !this.getText().length );
|
|
2768
|
+
|
|
2570
2769
|
let text = await navigator.clipboard.readText();
|
|
2571
2770
|
|
|
2572
2771
|
// Remove any possible tabs (\t) and add spaces
|
|
@@ -2579,9 +2778,19 @@ class CodeEditor {
|
|
|
2579
2778
|
const currentScroll = this.getScrollTop();
|
|
2580
2779
|
const scroll = Math.max( cursor.line * this.lineHeight - this.codeScroller.offsetWidth, 0 );
|
|
2581
2780
|
|
|
2582
|
-
if( currentScroll < scroll )
|
|
2781
|
+
if( currentScroll < scroll )
|
|
2782
|
+
{
|
|
2583
2783
|
this.codeScroller.scrollTo( 0, cursor.line * this.lineHeight );
|
|
2584
2784
|
}
|
|
2785
|
+
|
|
2786
|
+
if( mustDetectLanguage )
|
|
2787
|
+
{
|
|
2788
|
+
const detectedLang = this._detectLanguage( text );
|
|
2789
|
+
if( detectedLang )
|
|
2790
|
+
{
|
|
2791
|
+
this._changeLanguage( detectedLang );
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2585
2794
|
}
|
|
2586
2795
|
|
|
2587
2796
|
async _copyContent( cursor ) {
|
|
@@ -2604,7 +2813,9 @@ class CodeEditor {
|
|
|
2604
2813
|
let index = 0;
|
|
2605
2814
|
|
|
2606
2815
|
for( let i = 0; i <= cursor.selection.fromY; i++ )
|
|
2816
|
+
{
|
|
2607
2817
|
index += ( i == cursor.selection.fromY ? cursor.selection.fromX : this.code.lines[ i ].length );
|
|
2818
|
+
}
|
|
2608
2819
|
|
|
2609
2820
|
index += cursor.selection.fromY * separator.length;
|
|
2610
2821
|
const num_chars = cursor.selection.chars + ( cursor.selection.toY - cursor.selection.fromY ) * separator.length;
|
|
@@ -2680,7 +2891,7 @@ class CodeEditor {
|
|
|
2680
2891
|
|
|
2681
2892
|
if( cursor.selection )
|
|
2682
2893
|
{
|
|
2683
|
-
var cursor = this.
|
|
2894
|
+
var cursor = this.getCurrentCursor();
|
|
2684
2895
|
this._addUndoStep( cursor, true );
|
|
2685
2896
|
|
|
2686
2897
|
const selectedLines = this.code.lines.slice( cursor.selection.fromY, cursor.selection.toY );
|
|
@@ -2709,7 +2920,7 @@ class CodeEditor {
|
|
|
2709
2920
|
|
|
2710
2921
|
_commentLine( cursor, line, minNonspaceIdx ) {
|
|
2711
2922
|
|
|
2712
|
-
const lang =
|
|
2923
|
+
const lang = CodeEditor.languages[ this.highlight ];
|
|
2713
2924
|
|
|
2714
2925
|
if( !( lang.singleLineComments ?? true ))
|
|
2715
2926
|
return;
|
|
@@ -2739,7 +2950,7 @@ class CodeEditor {
|
|
|
2739
2950
|
|
|
2740
2951
|
if( cursor.selection )
|
|
2741
2952
|
{
|
|
2742
|
-
var cursor = this.
|
|
2953
|
+
var cursor = this.getCurrentCursor();
|
|
2743
2954
|
this._addUndoStep( cursor, true );
|
|
2744
2955
|
|
|
2745
2956
|
for( var i = cursor.selection.fromY; i <= cursor.selection.toY; ++i )
|
|
@@ -2762,7 +2973,7 @@ class CodeEditor {
|
|
|
2762
2973
|
|
|
2763
2974
|
_uncommentLine( cursor, line ) {
|
|
2764
2975
|
|
|
2765
|
-
const lang =
|
|
2976
|
+
const lang = CodeEditor.languages[ this.highlight ];
|
|
2766
2977
|
|
|
2767
2978
|
if( !( lang.singleLineComments ?? true ))
|
|
2768
2979
|
return;
|
|
@@ -2793,7 +3004,6 @@ class CodeEditor {
|
|
|
2793
3004
|
}
|
|
2794
3005
|
|
|
2795
3006
|
_actionMustDelete( cursor, action, e ) {
|
|
2796
|
-
|
|
2797
3007
|
return cursor.selection && action.deleteSelection &&
|
|
2798
3008
|
( action.eventSkipDelete ? !e[ action.eventSkipDelete ] : true );
|
|
2799
3009
|
}
|
|
@@ -2804,26 +3014,24 @@ class CodeEditor {
|
|
|
2804
3014
|
|
|
2805
3015
|
for( let i = 0; i < this.code.lines.length; ++i )
|
|
2806
3016
|
{
|
|
2807
|
-
const
|
|
2808
|
-
const tokens = this._getTokensFromLine(
|
|
3017
|
+
const lineString = this.code.lines[ i ];
|
|
3018
|
+
const tokens = this._getTokensFromLine( lineString, true );
|
|
2809
3019
|
tokens.forEach( t => this.code.tokens[ t ] = 1 );
|
|
2810
3020
|
}
|
|
2811
3021
|
}
|
|
2812
3022
|
|
|
2813
3023
|
toLocalLine( line ) {
|
|
2814
|
-
|
|
2815
3024
|
const d = Math.max( this.firstLineInViewport - this.lineScrollMargin.x, 0 );
|
|
2816
3025
|
return Math.min( Math.max( line - d, 0 ), this.code.lines.length - 1 );
|
|
2817
3026
|
}
|
|
2818
3027
|
|
|
2819
3028
|
getMaxLineLength() {
|
|
2820
|
-
|
|
2821
3029
|
return Math.max(...this.code.lines.map( v => v.length ));
|
|
2822
3030
|
}
|
|
2823
3031
|
|
|
2824
3032
|
processLines( mode ) {
|
|
2825
3033
|
|
|
2826
|
-
var
|
|
3034
|
+
var htmlCode = "";
|
|
2827
3035
|
this._blockCommentCache.length = 0;
|
|
2828
3036
|
|
|
2829
3037
|
// Reset all lines content
|
|
@@ -2833,7 +3041,7 @@ class CodeEditor {
|
|
|
2833
3041
|
const lastScrollTop = this.getScrollTop();
|
|
2834
3042
|
this.firstLineInViewport = ( mode ?? CodeEditor.KEEP_VISIBLE_LINES ) & CodeEditor.UPDATE_VISIBLE_LINES ?
|
|
2835
3043
|
( (lastScrollTop / this.lineHeight)|0 ) : this.firstLineInViewport;
|
|
2836
|
-
const totalLinesInViewport = ((this.codeScroller.offsetHeight
|
|
3044
|
+
const totalLinesInViewport = ( ( this.codeScroller.offsetHeight ) / this.lineHeight )|0;
|
|
2837
3045
|
this.visibleLinesViewport = new LX.vec2(
|
|
2838
3046
|
Math.max( this.firstLineInViewport - this.lineScrollMargin.x, 0 ),
|
|
2839
3047
|
Math.min( this.firstLineInViewport + totalLinesInViewport + this.lineScrollMargin.y, this.code.lines.length )
|
|
@@ -2843,16 +3051,20 @@ class CodeEditor {
|
|
|
2843
3051
|
{
|
|
2844
3052
|
const diff = Math.max( this.code.lines.length - this.visibleLinesViewport.y, 0 );
|
|
2845
3053
|
if( diff <= this.lineScrollMargin.y )
|
|
3054
|
+
{
|
|
2846
3055
|
this.visibleLinesViewport.y += diff;
|
|
3056
|
+
}
|
|
2847
3057
|
}
|
|
2848
3058
|
|
|
3059
|
+
this._scopeStack = [ { name: "", type: "global" } ];
|
|
3060
|
+
|
|
2849
3061
|
// Process visible lines
|
|
2850
3062
|
for( let i = this.visibleLinesViewport.x; i < this.visibleLinesViewport.y; ++i )
|
|
2851
3063
|
{
|
|
2852
|
-
|
|
3064
|
+
htmlCode += this.processLine( i, true );
|
|
2853
3065
|
}
|
|
2854
3066
|
|
|
2855
|
-
this.code.innerHTML =
|
|
3067
|
+
this.code.innerHTML = htmlCode;
|
|
2856
3068
|
|
|
2857
3069
|
// Update scroll data
|
|
2858
3070
|
this.codeScroller.scrollTop = lastScrollTop;
|
|
@@ -2866,37 +3078,34 @@ class CodeEditor {
|
|
|
2866
3078
|
this.resize();
|
|
2867
3079
|
}
|
|
2868
3080
|
|
|
2869
|
-
processLine(
|
|
3081
|
+
processLine( lineNumber, force, skipPropagation ) {
|
|
2870
3082
|
|
|
2871
3083
|
// Check if we are in block comment sections..
|
|
2872
|
-
if( !force && this._inBlockCommentSection(
|
|
3084
|
+
if( !force && this._inBlockCommentSection( lineNumber ) )
|
|
2873
3085
|
{
|
|
2874
3086
|
this.processLines();
|
|
2875
3087
|
return;
|
|
2876
3088
|
}
|
|
2877
3089
|
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
this._setActiveLine( linenum );
|
|
2887
|
-
this._clearTmpVariables();
|
|
2888
|
-
}
|
|
2889
|
-
else // Update all lines at once
|
|
2890
|
-
return "<pre>" + ( gutterLineHtml + html ) + "</pre>";
|
|
3090
|
+
if( this._scopeStack )
|
|
3091
|
+
{
|
|
3092
|
+
this.code.lineScopes[ lineNumber ] = [ ...this._scopeStack ];
|
|
3093
|
+
}
|
|
3094
|
+
else
|
|
3095
|
+
{
|
|
3096
|
+
this.code.lineScopes[ lineNumber ] = this.code.lineScopes[ lineNumber ] ?? [];
|
|
3097
|
+
this._scopeStack = [ ...this.code.lineScopes[ lineNumber ] ];
|
|
2891
3098
|
}
|
|
2892
3099
|
|
|
3100
|
+
const lang = CodeEditor.languages[ this.highlight ];
|
|
3101
|
+
const localLineNum = this.toLocalLine( lineNumber );
|
|
3102
|
+
const lineString = this.code.lines[ lineNumber ];
|
|
3103
|
+
|
|
2893
3104
|
// multi-line strings not supported by now
|
|
2894
3105
|
delete this._buildingString;
|
|
2895
3106
|
delete this._pendingString;
|
|
2896
3107
|
delete this._markdownHeader;
|
|
2897
3108
|
|
|
2898
|
-
let linestring = this.code.lines[ linenum ];
|
|
2899
|
-
|
|
2900
3109
|
// Single line
|
|
2901
3110
|
if( !force )
|
|
2902
3111
|
{
|
|
@@ -2907,35 +3116,37 @@ class CodeEditor {
|
|
|
2907
3116
|
// Early out check for no highlighting languages
|
|
2908
3117
|
if( this.highlight == 'Plain Text' )
|
|
2909
3118
|
{
|
|
2910
|
-
const plainTextHtml =
|
|
2911
|
-
return
|
|
3119
|
+
const plainTextHtml = lineString.replaceAll('<', '<').replaceAll('>', '>');
|
|
3120
|
+
return this._updateLine( force, lineNumber, plainTextHtml, skipPropagation );
|
|
2912
3121
|
}
|
|
2913
3122
|
|
|
2914
|
-
this._currentLineNumber =
|
|
2915
|
-
this._currentLineString =
|
|
2916
|
-
|
|
2917
|
-
const tokensToEvaluate = this._getTokensFromLine( linestring );
|
|
3123
|
+
this._currentLineNumber = lineNumber;
|
|
3124
|
+
this._currentLineString = lineString;
|
|
2918
3125
|
|
|
3126
|
+
const tokensToEvaluate = this._getTokensFromLine( lineString );
|
|
2919
3127
|
if( !tokensToEvaluate.length )
|
|
2920
3128
|
{
|
|
2921
|
-
return
|
|
3129
|
+
return this._updateLine( force, lineNumber, "", skipPropagation );
|
|
2922
3130
|
}
|
|
2923
3131
|
|
|
2924
|
-
|
|
3132
|
+
let lineInnerHtml = "";
|
|
3133
|
+
let pushedScope = false;
|
|
2925
3134
|
|
|
2926
3135
|
// Process all tokens
|
|
2927
|
-
for(
|
|
3136
|
+
for( let i = 0; i < tokensToEvaluate.length; ++i )
|
|
2928
3137
|
{
|
|
2929
3138
|
let it = i - 1;
|
|
2930
3139
|
let prev = tokensToEvaluate[ it ];
|
|
2931
|
-
while( prev == ' ' )
|
|
3140
|
+
while( prev == ' ' )
|
|
3141
|
+
{
|
|
2932
3142
|
it--;
|
|
2933
3143
|
prev = tokensToEvaluate[ it ];
|
|
2934
3144
|
}
|
|
2935
3145
|
|
|
2936
3146
|
it = i + 1;
|
|
2937
3147
|
let next = tokensToEvaluate[ it ];
|
|
2938
|
-
while( next == ' ' || next == '"' )
|
|
3148
|
+
while( next == ' ' || next == '"' )
|
|
3149
|
+
{
|
|
2939
3150
|
it++;
|
|
2940
3151
|
next = tokensToEvaluate[ it ];
|
|
2941
3152
|
}
|
|
@@ -2946,7 +3157,15 @@ class CodeEditor {
|
|
|
2946
3157
|
{
|
|
2947
3158
|
const blockCommentsToken = ( lang.blockCommentsTokens ?? this.defaultBlockCommentTokens )[ 0 ];
|
|
2948
3159
|
if( token.substr( 0, blockCommentsToken.length ) == blockCommentsToken )
|
|
2949
|
-
|
|
3160
|
+
{
|
|
3161
|
+
this._buildingBlockComment = lineNumber;
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
|
|
3165
|
+
// Pop current scope if necessary
|
|
3166
|
+
if( token === "}" && this._scopeStack.length > 1 )
|
|
3167
|
+
{
|
|
3168
|
+
this._scopeStack.pop();
|
|
2950
3169
|
}
|
|
2951
3170
|
|
|
2952
3171
|
lineInnerHtml += this._evaluateToken( {
|
|
@@ -2960,20 +3179,323 @@ class CodeEditor {
|
|
|
2960
3179
|
isLastToken: (i == tokensToEvaluate.length - 1),
|
|
2961
3180
|
tokens: tokensToEvaluate
|
|
2962
3181
|
} );
|
|
3182
|
+
|
|
3183
|
+
if( token !== "{" )
|
|
3184
|
+
{
|
|
3185
|
+
continue;
|
|
3186
|
+
}
|
|
3187
|
+
|
|
3188
|
+
// Store current scopes
|
|
3189
|
+
|
|
3190
|
+
// Get some context about the scope from previous lines
|
|
3191
|
+
let contextTokens = [
|
|
3192
|
+
...this._getTokensFromLine( this.code.lines[ lineNumber ].substring( 0, lineString.indexOf( token ) ) )
|
|
3193
|
+
];
|
|
3194
|
+
|
|
3195
|
+
for( let k = 1; k < 50; k++ )
|
|
3196
|
+
{
|
|
3197
|
+
let kLineString = this.code.lines[ lineNumber - k ];
|
|
3198
|
+
if( !kLineString ) break;
|
|
3199
|
+
const closeIdx = kLineString.lastIndexOf( '}' );
|
|
3200
|
+
if( closeIdx > -1 )
|
|
3201
|
+
{
|
|
3202
|
+
kLineString = kLineString.substr( closeIdx );
|
|
3203
|
+
}
|
|
3204
|
+
|
|
3205
|
+
contextTokens = [ ...this._getTokensFromLine( kLineString ), ...contextTokens ];
|
|
3206
|
+
|
|
3207
|
+
if( kLineString.length !== this.code.lines[ lineNumber - k ] )
|
|
3208
|
+
{
|
|
3209
|
+
break;
|
|
3210
|
+
}
|
|
3211
|
+
}
|
|
3212
|
+
|
|
3213
|
+
contextTokens = contextTokens.reverse().filter( v => v.length && v != ' ' );
|
|
3214
|
+
|
|
3215
|
+
// Keywords that can open a *named* scope
|
|
3216
|
+
// TODO: Do this per language
|
|
3217
|
+
const scopeKeywords = ["class", "enum", "function", "interface", "type", "struct", "namespace"];
|
|
3218
|
+
|
|
3219
|
+
let scopeType = null; // This is the type of scope (function, class, enum, etc)
|
|
3220
|
+
let scopeName = null;
|
|
3221
|
+
|
|
3222
|
+
for( let i = 0; i < contextTokens.length; i++ )
|
|
3223
|
+
{
|
|
3224
|
+
const t = contextTokens[ i ];
|
|
3225
|
+
|
|
3226
|
+
if ( scopeKeywords.includes( t ) )
|
|
3227
|
+
{
|
|
3228
|
+
scopeType = t;
|
|
3229
|
+
scopeName = contextTokens[ i - 1 ]; // usually right before the keyword in reversed array
|
|
3230
|
+
break;
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
|
|
3234
|
+
// Special case: enum type specification `enum Foo : int {`
|
|
3235
|
+
if( scopeType === "enum" && contextTokens.includes( ":" ) )
|
|
3236
|
+
{
|
|
3237
|
+
const colonIndex = contextTokens.indexOf( ":" );
|
|
3238
|
+
scopeName = contextTokens[ colonIndex + 1 ] || scopeName;
|
|
3239
|
+
}
|
|
3240
|
+
|
|
3241
|
+
if( !scopeType )
|
|
3242
|
+
{
|
|
3243
|
+
const parOpenIndex = contextTokens.indexOf( "(" );
|
|
3244
|
+
scopeName = contextTokens[ parOpenIndex + 1 ] || scopeName;
|
|
3245
|
+
|
|
3246
|
+
if( scopeName )
|
|
3247
|
+
{
|
|
3248
|
+
scopeType = "method";
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3251
|
+
|
|
3252
|
+
if( scopeType )
|
|
3253
|
+
{
|
|
3254
|
+
this._scopeStack.push( { name: scopeName ?? "", type: scopeType } );
|
|
3255
|
+
}
|
|
3256
|
+
else
|
|
3257
|
+
{
|
|
3258
|
+
this._scopeStack.push( { name: "", type: "anonymous" } ); // anonymous scope
|
|
3259
|
+
}
|
|
3260
|
+
|
|
3261
|
+
pushedScope = true;
|
|
3262
|
+
}
|
|
3263
|
+
|
|
3264
|
+
const symbols = this._parseLineForSymbols( lineNumber, lineString, tokensToEvaluate, pushedScope );
|
|
3265
|
+
return this._updateLine( force, lineNumber, lineInnerHtml, skipPropagation, symbols );
|
|
3266
|
+
}
|
|
3267
|
+
|
|
3268
|
+
_processExtraLineIfNecessary( lineNumber, oldSymbols ) {
|
|
3269
|
+
|
|
3270
|
+
if( ( (lineNumber + 1) === this.code.lines.length ) || !this.code.lineScopes[ lineNumber + 1 ] )
|
|
3271
|
+
{
|
|
3272
|
+
return;
|
|
3273
|
+
}
|
|
3274
|
+
|
|
3275
|
+
if( !this._scopeStack )
|
|
3276
|
+
{
|
|
3277
|
+
console.warn( "CodeEditor: No scope available" );
|
|
3278
|
+
return;
|
|
3279
|
+
}
|
|
3280
|
+
|
|
3281
|
+
// Only update scope stack if something changed when editing a single line
|
|
3282
|
+
if( codeScopesEqual( this._scopeStack, this.code.lineScopes[ lineNumber + 1 ] ) )
|
|
3283
|
+
{
|
|
3284
|
+
// First check for occurrencies of the old symbols, to reprocess that lines
|
|
3285
|
+
|
|
3286
|
+
for( const sym of oldSymbols )
|
|
3287
|
+
{
|
|
3288
|
+
const tableSymbol = this.code.symbolsTable.get( sym.name );
|
|
3289
|
+
if( tableSymbol === undefined )
|
|
3290
|
+
{
|
|
3291
|
+
return;
|
|
3292
|
+
}
|
|
3293
|
+
|
|
3294
|
+
for( const occ of tableSymbol )
|
|
3295
|
+
{
|
|
3296
|
+
if( occ.line === lineNumber )
|
|
3297
|
+
{
|
|
3298
|
+
continue;
|
|
3299
|
+
}
|
|
3300
|
+
|
|
3301
|
+
this.processLine( occ.line, false, true );
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
|
|
3305
|
+
return;
|
|
3306
|
+
}
|
|
3307
|
+
|
|
3308
|
+
this.code.lineScopes[ lineNumber + 1 ] = [ ...this._scopeStack ];
|
|
3309
|
+
this.processLine( lineNumber + 1 );
|
|
3310
|
+
}
|
|
3311
|
+
|
|
3312
|
+
_updateLine( force, lineNumber, html, skipPropagation, symbols = [] ) {
|
|
3313
|
+
|
|
3314
|
+
const gutterLineHtml = `<span class='line-gutter'>${ lineNumber + 1 }</span>`;
|
|
3315
|
+
const oldSymbols = this._updateLineSymbols( lineNumber, symbols );
|
|
3316
|
+
const lineScope = CodeEditor.debugScopes ? this.code.lineScopes[ lineNumber ].map( s => `${ s.type }` ).join( ", " ) : "";
|
|
3317
|
+
const lineSymbols = CodeEditor.debugSymbols ? this.code.lineSymbols[ lineNumber ].map( s => `${ s.name }(${ s.kind })` ).join( ", " ) : "";
|
|
3318
|
+
const debugString = lineScope + ( lineScope.length ? " - " : "" ) + lineSymbols;
|
|
3319
|
+
|
|
3320
|
+
if( !force ) // Single line update
|
|
3321
|
+
{
|
|
3322
|
+
this.code.childNodes[ this.toLocalLine( lineNumber ) ].innerHTML = ( gutterLineHtml + html + debugString );
|
|
3323
|
+
|
|
3324
|
+
if( !skipPropagation )
|
|
3325
|
+
{
|
|
3326
|
+
this._processExtraLineIfNecessary( lineNumber, oldSymbols );
|
|
3327
|
+
}
|
|
3328
|
+
|
|
3329
|
+
this._setActiveLine( lineNumber );
|
|
3330
|
+
this._clearTmpVariables();
|
|
3331
|
+
}
|
|
3332
|
+
else // Update all lines at once
|
|
3333
|
+
{
|
|
3334
|
+
|
|
3335
|
+
return `<pre>${ ( gutterLineHtml + html + debugString ) }</pre>`;
|
|
3336
|
+
}
|
|
3337
|
+
}
|
|
3338
|
+
|
|
3339
|
+
/**
|
|
3340
|
+
* Parses a single line of code and extract declared symbols
|
|
3341
|
+
*/
|
|
3342
|
+
_parseLineForSymbols( lineNumber, lineString, tokens, pushedScope ) {
|
|
3343
|
+
|
|
3344
|
+
const scope = this._scopeStack.at( pushedScope ? -2 : -1 );
|
|
3345
|
+
|
|
3346
|
+
if( !scope )
|
|
3347
|
+
{
|
|
3348
|
+
return [];
|
|
3349
|
+
}
|
|
3350
|
+
|
|
3351
|
+
const scopeName = scope.name;
|
|
3352
|
+
const scopeType = scope.type;
|
|
3353
|
+
const symbols = [];
|
|
3354
|
+
const text = lineString.trim();
|
|
3355
|
+
|
|
3356
|
+
// Don't make symbols from preprocessor lines
|
|
3357
|
+
if( text.startsWith( "#" ) )
|
|
3358
|
+
{
|
|
3359
|
+
return [];
|
|
3360
|
+
}
|
|
3361
|
+
|
|
3362
|
+
const topLevelRegexes = [
|
|
3363
|
+
[/^class\s+([A-Za-z0-9_]+)/, "class"],
|
|
3364
|
+
[/^struct\s+([A-Za-z0-9_]+)/, "struct"],
|
|
3365
|
+
[/^enum\s+([A-Za-z0-9_]+)/, "enum"],
|
|
3366
|
+
[/^interface\s+([A-Za-z0-9_]+)/, "interface"],
|
|
3367
|
+
[/^type\s+([A-Za-z0-9_]+)/, "type"],
|
|
3368
|
+
[/^function\s+([A-Za-z0-9_]+)/, "method"],
|
|
3369
|
+
[/^([A-Za-z0-9_]+)\s*=\s*\(?.*\)?\s*=>/, "method"] // arrow functions
|
|
3370
|
+
];
|
|
3371
|
+
|
|
3372
|
+
{
|
|
3373
|
+
const nativeTypes = CodeEditor.nativeTypes[ this.highlight ];
|
|
3374
|
+
if( nativeTypes )
|
|
3375
|
+
{
|
|
3376
|
+
const nativeTypes = ['int', 'float', 'double', 'bool', 'long', 'short', 'char', 'wchar_t', 'void'];
|
|
3377
|
+
const regex = `^(?:${nativeTypes.join('|')})\\s+([A-Za-z0-9_]+)\s*[\(]+`;
|
|
3378
|
+
topLevelRegexes.push( [ new RegExp( regex ), 'method' ] );
|
|
3379
|
+
}
|
|
3380
|
+
|
|
3381
|
+
const declarationKeywords = CodeEditor.declarationKeywords[ this.highlight ] ?? [ "const", "let", "var" ];
|
|
3382
|
+
const regex = `^(?:${declarationKeywords.join('|')})\\s+([A-Za-z0-9_]+)`;
|
|
3383
|
+
topLevelRegexes.push( [ new RegExp( regex ), 'variable' ] );
|
|
3384
|
+
}
|
|
3385
|
+
|
|
3386
|
+
for( let [ regex, kind ] of topLevelRegexes )
|
|
3387
|
+
{
|
|
3388
|
+
const m = text.match( regex );
|
|
3389
|
+
if( m )
|
|
3390
|
+
{
|
|
3391
|
+
symbols.push( { name: m[ 1 ], kind, scope: scopeName, line: lineNumber } );
|
|
3392
|
+
break;
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
|
|
3396
|
+
const usageRegexes = [
|
|
3397
|
+
[/new\s+([A-Za-z0-9_]+)\s*\(/, "constructor-call"],
|
|
3398
|
+
[/this.([A-Za-z_][A-Za-z0-9_]*)\s*\=/, "class-property"],
|
|
3399
|
+
];
|
|
3400
|
+
|
|
3401
|
+
for( let [ regex, kind ] of usageRegexes )
|
|
3402
|
+
{
|
|
3403
|
+
const m = text.match( regex );
|
|
3404
|
+
if( m )
|
|
3405
|
+
{
|
|
3406
|
+
symbols.push( { name: m[ 1 ], kind, scope: scopeName, line: lineNumber } );
|
|
3407
|
+
}
|
|
3408
|
+
}
|
|
3409
|
+
|
|
3410
|
+
// Stop after matches for top-level declarations and usage symbols
|
|
3411
|
+
if( symbols.length )
|
|
3412
|
+
{
|
|
3413
|
+
return symbols;
|
|
3414
|
+
}
|
|
3415
|
+
|
|
3416
|
+
const nonWhiteSpaceTokens = tokens.filter( t => t.trim().length );
|
|
3417
|
+
|
|
3418
|
+
for( let i = 0; i < nonWhiteSpaceTokens.length; i++ )
|
|
3419
|
+
{
|
|
3420
|
+
const prev = nonWhiteSpaceTokens[ i - 1 ];
|
|
3421
|
+
const token = nonWhiteSpaceTokens[ i ];
|
|
3422
|
+
const next = nonWhiteSpaceTokens[ i + 1 ];
|
|
3423
|
+
|
|
3424
|
+
if( scopeType.startsWith("class") )
|
|
3425
|
+
{
|
|
3426
|
+
if( next === "(" && /^[a-zA-Z_]\w*$/.test( token ) && prev === undefined )
|
|
3427
|
+
{
|
|
3428
|
+
symbols.push( { name: token, kind: "method", scope: scopeName, line: lineNumber } );
|
|
3429
|
+
}
|
|
3430
|
+
}
|
|
3431
|
+
else if( scopeType.startsWith("enum") )
|
|
3432
|
+
{
|
|
3433
|
+
if( !isSymbol( token ) && !this._isNumber( token ) && !this._mustHightlightWord( token, CodeEditor.statements ) )
|
|
3434
|
+
{
|
|
3435
|
+
symbols.push({ name: token, kind: "enum_value", scope: scopeName, line: lineNumber });
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3439
|
+
|
|
3440
|
+
return symbols;
|
|
3441
|
+
}
|
|
3442
|
+
|
|
3443
|
+
/**
|
|
3444
|
+
* Updates the symbol table for a single line
|
|
3445
|
+
* - Removes old symbols from that line
|
|
3446
|
+
* - Inserts the new symbols
|
|
3447
|
+
*/
|
|
3448
|
+
_updateLineSymbols( lineNumber, newSymbols ) {
|
|
3449
|
+
|
|
3450
|
+
this.code.lineSymbols[ lineNumber ] = this.code.lineSymbols[ lineNumber ] ?? [];
|
|
3451
|
+
const oldSymbols = LX.deepCopy( this.code.lineSymbols[ lineNumber ] );
|
|
3452
|
+
|
|
3453
|
+
// Clean old symbols from current line
|
|
3454
|
+
for( let sym of this.code.lineSymbols[ lineNumber ] )
|
|
3455
|
+
{
|
|
3456
|
+
let array = this.code.symbolsTable.get( sym.name );
|
|
3457
|
+
if( !array )
|
|
3458
|
+
{
|
|
3459
|
+
continue;
|
|
3460
|
+
}
|
|
3461
|
+
|
|
3462
|
+
array = array.filter( s => s.line !== lineNumber );
|
|
3463
|
+
|
|
3464
|
+
if( array.length )
|
|
3465
|
+
{
|
|
3466
|
+
this.code.symbolsTable.set( sym.name, array );
|
|
3467
|
+
}
|
|
3468
|
+
else
|
|
3469
|
+
{
|
|
3470
|
+
this.code.symbolsTable.delete( sym.name );
|
|
3471
|
+
}
|
|
2963
3472
|
}
|
|
2964
3473
|
|
|
2965
|
-
|
|
3474
|
+
// Add new symbols to table
|
|
3475
|
+
for( let sym of newSymbols )
|
|
3476
|
+
{
|
|
3477
|
+
let arr = this.code.symbolsTable.get( sym.name ) ?? [];
|
|
3478
|
+
arr.push(sym);
|
|
3479
|
+
this.code.symbolsTable.set( sym.name, arr );
|
|
3480
|
+
}
|
|
3481
|
+
|
|
3482
|
+
// Keep lineSymbols in sync
|
|
3483
|
+
this.code.lineSymbols[ lineNumber ] = newSymbols;
|
|
3484
|
+
|
|
3485
|
+
return oldSymbols;
|
|
2966
3486
|
}
|
|
2967
3487
|
|
|
2968
|
-
_lineHasComment(
|
|
3488
|
+
_lineHasComment( lineString ) {
|
|
2969
3489
|
|
|
2970
|
-
const lang =
|
|
3490
|
+
const lang = CodeEditor.languages[ this.highlight ];
|
|
2971
3491
|
|
|
2972
3492
|
if( !(lang.singleLineComments ?? true) )
|
|
3493
|
+
{
|
|
2973
3494
|
return;
|
|
3495
|
+
}
|
|
2974
3496
|
|
|
2975
3497
|
const singleLineCommentToken = lang.singleLineCommentToken ?? this.defaultSingleLineCommentToken;
|
|
2976
|
-
const idx =
|
|
3498
|
+
const idx = lineString.indexOf( singleLineCommentToken );
|
|
2977
3499
|
|
|
2978
3500
|
if( idx > -1 )
|
|
2979
3501
|
{
|
|
@@ -2982,22 +3504,27 @@ class CodeEditor {
|
|
|
2982
3504
|
var err = false;
|
|
2983
3505
|
err |= stringKeys.some( function(v) {
|
|
2984
3506
|
var re = new RegExp( v, "g" );
|
|
2985
|
-
var matches = (
|
|
3507
|
+
var matches = (lineString.substring( 0, idx ).match( re ) || []);
|
|
2986
3508
|
return (matches.length % 2) !== 0;
|
|
2987
3509
|
} );
|
|
2988
3510
|
return err ? undefined : idx;
|
|
2989
3511
|
}
|
|
2990
3512
|
}
|
|
2991
3513
|
|
|
2992
|
-
_getTokensFromLine(
|
|
3514
|
+
_getTokensFromLine( lineString, skipNonWords ) {
|
|
3515
|
+
|
|
3516
|
+
if( !lineString || !lineString.length )
|
|
3517
|
+
{
|
|
3518
|
+
return [];
|
|
3519
|
+
}
|
|
2993
3520
|
|
|
2994
3521
|
// Check if line comment
|
|
2995
|
-
const ogLine =
|
|
2996
|
-
const hasCommentIdx = this._lineHasComment(
|
|
3522
|
+
const ogLine = lineString;
|
|
3523
|
+
const hasCommentIdx = this._lineHasComment( lineString );
|
|
2997
3524
|
|
|
2998
3525
|
if( hasCommentIdx != undefined )
|
|
2999
3526
|
{
|
|
3000
|
-
|
|
3527
|
+
lineString = ogLine.substring( 0, hasCommentIdx );
|
|
3001
3528
|
}
|
|
3002
3529
|
|
|
3003
3530
|
let tokensToEvaluate = []; // store in a temp array so we know prev and next tokens...
|
|
@@ -3013,25 +3540,25 @@ class CodeEditor {
|
|
|
3013
3540
|
charCounter += t.length;
|
|
3014
3541
|
};
|
|
3015
3542
|
|
|
3016
|
-
let iter =
|
|
3543
|
+
let iter = lineString.matchAll(/(<!--|-->|\*\/|\/\*|::|[\[\](){}<>.,;:*"'%@$!/= ])/g);
|
|
3017
3544
|
let subtokens = iter.next();
|
|
3018
3545
|
if( subtokens.value )
|
|
3019
3546
|
{
|
|
3020
3547
|
let idx = 0;
|
|
3021
3548
|
while( subtokens.value != undefined )
|
|
3022
3549
|
{
|
|
3023
|
-
const _pt =
|
|
3550
|
+
const _pt = lineString.substring(idx, subtokens.value.index);
|
|
3024
3551
|
if( _pt.length ) pushToken( _pt );
|
|
3025
3552
|
pushToken( subtokens.value[ 0 ] );
|
|
3026
3553
|
idx = subtokens.value.index + subtokens.value[ 0 ].length;
|
|
3027
3554
|
subtokens = iter.next();
|
|
3028
3555
|
if(!subtokens.value) {
|
|
3029
|
-
const _at =
|
|
3556
|
+
const _at = lineString.substring(idx);
|
|
3030
3557
|
if( _at.length ) pushToken( _at );
|
|
3031
3558
|
}
|
|
3032
3559
|
}
|
|
3033
3560
|
}
|
|
3034
|
-
else pushToken(
|
|
3561
|
+
else pushToken( lineString );
|
|
3035
3562
|
|
|
3036
3563
|
if( hasCommentIdx != undefined )
|
|
3037
3564
|
{
|
|
@@ -3061,16 +3588,47 @@ class CodeEditor {
|
|
|
3061
3588
|
// Scan for numbers again
|
|
3062
3589
|
return this._processTokens( tokens, idx );
|
|
3063
3590
|
}
|
|
3591
|
+
|
|
3592
|
+
const importantIdx = tokens.indexOf( 'important' );
|
|
3593
|
+
if( this.highlight == 'CSS' && importantIdx > -1 && tokens[ importantIdx - 1 ] === '!' )
|
|
3594
|
+
{
|
|
3595
|
+
tokens[ importantIdx - 1 ] = "!important";
|
|
3596
|
+
tokens.splice( importantIdx, 1 );
|
|
3597
|
+
}
|
|
3598
|
+
}
|
|
3599
|
+
else if( this.highlight == 'PHP' )
|
|
3600
|
+
{
|
|
3601
|
+
let offset = 0;
|
|
3602
|
+
let dollarIdx = tokens.indexOf( '$' );
|
|
3603
|
+
|
|
3604
|
+
while( dollarIdx > -1 )
|
|
3605
|
+
{
|
|
3606
|
+
const offsetIdx = dollarIdx + offset;
|
|
3607
|
+
|
|
3608
|
+
if( tokens[ offsetIdx + 1 ] === 'this-' )
|
|
3609
|
+
{
|
|
3610
|
+
tokens[ offsetIdx ] = "$this";
|
|
3611
|
+
tokens[ offsetIdx + 1 ] = "-";
|
|
3612
|
+
}
|
|
3613
|
+
else
|
|
3614
|
+
{
|
|
3615
|
+
tokens[ offsetIdx ] += ( tokens[ offsetIdx + 1 ] ?? "" );
|
|
3616
|
+
tokens.splice( offsetIdx + 1, 1 );
|
|
3617
|
+
}
|
|
3618
|
+
|
|
3619
|
+
dollarIdx = tokens.slice( offsetIdx ).indexOf( '$' );
|
|
3620
|
+
offset = offsetIdx;
|
|
3621
|
+
}
|
|
3064
3622
|
}
|
|
3065
3623
|
|
|
3066
3624
|
return tokens;
|
|
3067
3625
|
}
|
|
3068
3626
|
|
|
3069
|
-
_mustHightlightWord( token,
|
|
3627
|
+
_mustHightlightWord( token, wordCategory, lang ) {
|
|
3070
3628
|
|
|
3071
3629
|
if( !lang )
|
|
3072
3630
|
{
|
|
3073
|
-
lang =
|
|
3631
|
+
lang = CodeEditor.languages[ this.highlight ];
|
|
3074
3632
|
}
|
|
3075
3633
|
|
|
3076
3634
|
let t = token;
|
|
@@ -3080,21 +3638,36 @@ class CodeEditor {
|
|
|
3080
3638
|
t = t.toLowerCase();
|
|
3081
3639
|
}
|
|
3082
3640
|
|
|
3083
|
-
return
|
|
3641
|
+
return wordCategory[ this.highlight ] && wordCategory[ this.highlight ].has( t );
|
|
3642
|
+
}
|
|
3643
|
+
|
|
3644
|
+
_getTokenHighlighting( ctx, highlight ) {
|
|
3645
|
+
|
|
3646
|
+
const rules = [ ...HighlightRules.common, ...( HighlightRules[ highlight ] || [] ), ...HighlightRules.post_common ];
|
|
3647
|
+
|
|
3648
|
+
for( const rule of rules )
|
|
3649
|
+
{
|
|
3650
|
+
if( !rule.test( ctx, this ) )
|
|
3651
|
+
{
|
|
3652
|
+
continue;
|
|
3653
|
+
}
|
|
3654
|
+
|
|
3655
|
+
const r = rule.action ? rule.action( ctx, this ) : undefined;
|
|
3656
|
+
if( rule.discard ) ctx.discardToken = r;
|
|
3657
|
+
return rule.className;
|
|
3658
|
+
}
|
|
3659
|
+
|
|
3660
|
+
return null;
|
|
3084
3661
|
}
|
|
3085
3662
|
|
|
3086
3663
|
_evaluateToken( ctxData ) {
|
|
3087
3664
|
|
|
3088
|
-
let token
|
|
3089
|
-
prev = ctxData.prev,
|
|
3090
|
-
next = ctxData.next,
|
|
3091
|
-
tokenIndex = ctxData.tokenIndex,
|
|
3092
|
-
isFirstToken = ctxData.isFirstToken,
|
|
3093
|
-
isLastToken = ctxData.isLastToken;
|
|
3665
|
+
let { token, prev, next, tokenIndex, isFirstToken, isLastToken } = ctxData;
|
|
3094
3666
|
|
|
3095
|
-
const lang =
|
|
3667
|
+
const lang = CodeEditor.languages[ this.highlight ],
|
|
3096
3668
|
highlight = this.highlight.replace( /\s/g, '' ).replaceAll( "+", "p" ).toLowerCase(),
|
|
3097
|
-
customStringKeys = Object.assign( {}, this.stringKeys )
|
|
3669
|
+
customStringKeys = Object.assign( {}, this.stringKeys ),
|
|
3670
|
+
lineNumber = this._currentLineNumber;
|
|
3098
3671
|
|
|
3099
3672
|
var usePreviousTokenToCheckString = false;
|
|
3100
3673
|
|
|
@@ -3109,102 +3682,57 @@ class CodeEditor {
|
|
|
3109
3682
|
customStringKeys['@['] = ']';
|
|
3110
3683
|
}
|
|
3111
3684
|
|
|
3112
|
-
// Manage strings
|
|
3113
|
-
this._stringEnded = false;
|
|
3114
|
-
|
|
3115
|
-
if( usePreviousTokenToCheckString || ( this._buildingBlockComment === undefined && ( lang.tags ?? false ? ( this._enclosedByTokens( token, tokenIndex, '<', '>' ) ) : true ) ) )
|
|
3116
|
-
{
|
|
3117
|
-
const checkIfStringEnded = t => {
|
|
3118
|
-
const idx = Object.values( customStringKeys ).indexOf( t );
|
|
3119
|
-
this._stringEnded = (idx > -1) && (idx == Object.values(customStringKeys).indexOf( customStringKeys[ '@' + this._buildingString ] ));
|
|
3120
|
-
};
|
|
3121
|
-
|
|
3122
|
-
if( this._buildingString != undefined )
|
|
3123
|
-
{
|
|
3124
|
-
checkIfStringEnded( usePreviousTokenToCheckString ? ctxData.nextWithSpaces : token );
|
|
3125
|
-
}
|
|
3126
|
-
else if( customStringKeys[ '@' + ( usePreviousTokenToCheckString ? ctxData.prevWithSpaces : token ) ] )
|
|
3127
|
-
{
|
|
3128
|
-
// Start new string
|
|
3129
|
-
this._buildingString = ( usePreviousTokenToCheckString ? ctxData.prevWithSpaces : token );
|
|
3130
|
-
|
|
3131
|
-
// Check if string ended in same token using next...
|
|
3132
|
-
if( usePreviousTokenToCheckString )
|
|
3133
|
-
{
|
|
3134
|
-
checkIfStringEnded( ctxData.nextWithSpaces );
|
|
3135
|
-
}
|
|
3136
|
-
}
|
|
3137
|
-
}
|
|
3138
|
-
|
|
3139
|
-
const usesBlockComments = lang.blockComments ?? true;
|
|
3140
|
-
const blockCommentsTokens = lang.blockCommentsTokens ?? this.defaultBlockCommentTokens;
|
|
3141
|
-
const singleLineCommentToken = lang.singleLineCommentToken ?? this.defaultSingleLineCommentToken;
|
|
3142
|
-
|
|
3143
|
-
let token_classname = "";
|
|
3144
|
-
let discardToken = false;
|
|
3145
|
-
|
|
3146
|
-
if( this._buildingBlockComment != undefined )
|
|
3147
|
-
token_classname = "cm-com";
|
|
3148
|
-
|
|
3149
|
-
else if( this._buildingString != undefined )
|
|
3150
|
-
discardToken = this._appendStringToken( token );
|
|
3151
|
-
|
|
3152
|
-
else if( this._isKeyword( ctxData, lang ) )
|
|
3153
|
-
token_classname = "cm-kwd";
|
|
3154
|
-
|
|
3155
|
-
else if( this._mustHightlightWord( token, CodeEditor.builtIn, lang ) && ( lang.tags ?? false ? ( this._enclosedByTokens( token, tokenIndex, '<', '>' ) ) : true ) )
|
|
3156
|
-
token_classname = "cm-bln";
|
|
3157
|
-
|
|
3158
|
-
else if( this._mustHightlightWord( token, CodeEditor.statementsAndDeclarations, lang ) )
|
|
3159
|
-
token_classname = "cm-std";
|
|
3160
|
-
|
|
3161
|
-
else if( this._mustHightlightWord( token, CodeEditor.symbols, lang ) )
|
|
3162
|
-
token_classname = "cm-sym";
|
|
3163
|
-
|
|
3164
|
-
else if( token.substr( 0, singleLineCommentToken.length ) == singleLineCommentToken )
|
|
3165
|
-
token_classname = "cm-com";
|
|
3166
|
-
|
|
3167
|
-
else if( this._isNumber( token ) || this._isNumber( token.replace(/[px]|[em]|%/g,'') ) )
|
|
3168
|
-
token_classname = "cm-dec";
|
|
3169
|
-
|
|
3170
|
-
else if( this._isCSSClass( ctxData ) )
|
|
3171
|
-
token_classname = "cm-kwd";
|
|
3172
|
-
|
|
3173
|
-
else if ( this._isType( ctxData, lang ) )
|
|
3174
|
-
token_classname = "cm-typ";
|
|
3175
|
-
|
|
3176
|
-
else if ( highlight == 'batch' && ( token == '@' || prev == ':' || prev == '@' ) )
|
|
3177
|
-
token_classname = "cm-kwd";
|
|
3178
|
-
|
|
3179
|
-
else if ( [ 'cpp', 'c', 'wgsl', 'glsl' ].indexOf( highlight ) > -1 && token.includes( '#' ) ) // C++ preprocessor
|
|
3180
|
-
token_classname = "cm-ppc";
|
|
3181
|
-
|
|
3182
|
-
else if ( highlight == 'cpp' && prev == '<' && (next == '>' || next == '*') ) // Defining template type in C++
|
|
3183
|
-
token_classname = "cm-typ";
|
|
3184
|
-
|
|
3185
|
-
else if ( highlight == 'cpp' && (next == '::' || prev == '::' && next != '(' )) // C++ Class
|
|
3186
|
-
token_classname = "cm-typ";
|
|
3685
|
+
// Manage strings
|
|
3686
|
+
this._stringEnded = false;
|
|
3187
3687
|
|
|
3188
|
-
|
|
3189
|
-
|
|
3688
|
+
if( usePreviousTokenToCheckString || ( this._buildingBlockComment === undefined && ( lang.tags ?? false ? ( this._enclosedByTokens( token, tokenIndex, '<', '>' ) ) : true ) ) )
|
|
3689
|
+
{
|
|
3690
|
+
const _checkIfStringEnded = t => {
|
|
3691
|
+
const idx = Object.values( customStringKeys ).indexOf( t );
|
|
3692
|
+
this._stringEnded = (idx > -1) && (idx == Object.values(customStringKeys).indexOf( customStringKeys[ '@' + this._buildingString ] ));
|
|
3693
|
+
};
|
|
3190
3694
|
|
|
3191
|
-
|
|
3192
|
-
|
|
3695
|
+
if( this._buildingString != undefined )
|
|
3696
|
+
{
|
|
3697
|
+
_checkIfStringEnded( usePreviousTokenToCheckString ? ctxData.nextWithSpaces : token );
|
|
3698
|
+
}
|
|
3699
|
+
else if( customStringKeys[ '@' + ( usePreviousTokenToCheckString ? ctxData.prevWithSpaces : token ) ] )
|
|
3700
|
+
{
|
|
3701
|
+
// Start new string
|
|
3702
|
+
this._buildingString = ( usePreviousTokenToCheckString ? ctxData.prevWithSpaces : token );
|
|
3193
3703
|
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3704
|
+
// Check if string ended in same token using next...
|
|
3705
|
+
if( usePreviousTokenToCheckString )
|
|
3706
|
+
{
|
|
3707
|
+
_checkIfStringEnded( ctxData.nextWithSpaces );
|
|
3708
|
+
}
|
|
3709
|
+
}
|
|
3198
3710
|
}
|
|
3199
3711
|
|
|
3200
|
-
|
|
3201
|
-
|
|
3712
|
+
// Update context data for next tests
|
|
3713
|
+
ctxData.discardToken = false;
|
|
3714
|
+
ctxData.inBlockComment = this._buildingBlockComment;
|
|
3715
|
+
ctxData.markdownHeader = this._markdownHeader;
|
|
3716
|
+
ctxData.inString = this._buildingString;
|
|
3717
|
+
ctxData.singleLineCommentToken = lang.singleLineCommentToken ?? this.defaultSingleLineCommentToken;
|
|
3718
|
+
ctxData.lang = lang;
|
|
3719
|
+
ctxData.scope = this._scopeStack.at( -1 );
|
|
3202
3720
|
|
|
3721
|
+
// Add utils functions for the rules
|
|
3722
|
+
ctxData.isVariableSymbol = ( token ) => this.code.symbolsTable.has( token ) && this.code.symbolsTable.get( token )[ 0 ].kind === "variable";
|
|
3723
|
+
ctxData.isEnumValueSymbol = ( token ) => this.code.symbolsTable.has( token ) && this.code.symbolsTable.get( token )[ 0 ].kind === "enum_value";
|
|
3724
|
+
ctxData.isClassSymbol = ( token ) => this.code.symbolsTable.has( token ) && this.code.symbolsTable.get( token )[ 0 ].kind === "class";
|
|
3725
|
+
ctxData.isStructSymbol = ( token ) => this.code.symbolsTable.has( token ) && this.code.symbolsTable.get( token )[ 0 ].kind === "struct";
|
|
3726
|
+
ctxData.isEnumSymbol = ( token ) => this.code.symbolsTable.has( token ) && this.code.symbolsTable.get( token )[ 0 ].kind === "enum";
|
|
3203
3727
|
|
|
3204
|
-
|
|
3728
|
+
// Get highlighting class based on language common and specific rules
|
|
3729
|
+
let tokenClass = this._getTokenHighlighting( ctxData, highlight );
|
|
3730
|
+
|
|
3731
|
+
const blockCommentsTokens = lang.blockCommentsTokens ?? this.defaultBlockCommentTokens;
|
|
3732
|
+
if( ( lang.blockComments ?? true ) && this._buildingBlockComment != undefined
|
|
3205
3733
|
&& token.substr( 0, blockCommentsTokens[ 1 ].length ) == blockCommentsTokens[ 1 ] )
|
|
3206
3734
|
{
|
|
3207
|
-
this._blockCommentCache.push( new LX.vec2( this._buildingBlockComment,
|
|
3735
|
+
this._blockCommentCache.push( new LX.vec2( this._buildingBlockComment, lineNumber ) );
|
|
3208
3736
|
delete this._buildingBlockComment;
|
|
3209
3737
|
}
|
|
3210
3738
|
|
|
@@ -3212,34 +3740,36 @@ class CodeEditor {
|
|
|
3212
3740
|
if( this._buildingString && ( this._stringEnded || isLastToken ) )
|
|
3213
3741
|
{
|
|
3214
3742
|
token = this._getCurrentString();
|
|
3215
|
-
|
|
3216
|
-
discardToken = false;
|
|
3743
|
+
tokenClass = "cm-str";
|
|
3744
|
+
ctxData.discardToken = false;
|
|
3217
3745
|
}
|
|
3218
3746
|
|
|
3219
3747
|
// Update state
|
|
3220
3748
|
this._buildingString = this._stringEnded ? undefined : this._buildingString;
|
|
3221
3749
|
|
|
3222
|
-
if( discardToken )
|
|
3750
|
+
if( ctxData.discardToken )
|
|
3223
3751
|
{
|
|
3224
3752
|
return "";
|
|
3225
3753
|
}
|
|
3226
3754
|
|
|
3227
|
-
|
|
3228
|
-
token = token.replace( ">", ">" );
|
|
3755
|
+
// Replace html chars
|
|
3756
|
+
token = token.replace( "<", "<" ).replace( ">", ">" );
|
|
3229
3757
|
|
|
3230
3758
|
// No highlighting, no need to put it inside another span..
|
|
3231
|
-
if( !
|
|
3759
|
+
if( !tokenClass )
|
|
3232
3760
|
{
|
|
3233
3761
|
return token;
|
|
3234
3762
|
}
|
|
3235
3763
|
|
|
3236
|
-
return
|
|
3764
|
+
return `<span class="${ highlight } ${ tokenClass }">${ token }</span>`;
|
|
3237
3765
|
}
|
|
3238
3766
|
|
|
3239
3767
|
_appendStringToken( token ) {
|
|
3240
3768
|
|
|
3241
3769
|
if( !this._pendingString )
|
|
3770
|
+
{
|
|
3242
3771
|
this._pendingString = "";
|
|
3772
|
+
}
|
|
3243
3773
|
|
|
3244
3774
|
this._pendingString += token;
|
|
3245
3775
|
|
|
@@ -3247,7 +3777,6 @@ class CodeEditor {
|
|
|
3247
3777
|
}
|
|
3248
3778
|
|
|
3249
3779
|
_getCurrentString() {
|
|
3250
|
-
|
|
3251
3780
|
const chars = this._pendingString;
|
|
3252
3781
|
delete this._pendingString;
|
|
3253
3782
|
return chars;
|
|
@@ -3285,11 +3814,9 @@ class CodeEditor {
|
|
|
3285
3814
|
return false;
|
|
3286
3815
|
}
|
|
3287
3816
|
|
|
3288
|
-
_isKeyword( ctxData
|
|
3817
|
+
_isKeyword( ctxData ) {
|
|
3289
3818
|
|
|
3290
|
-
const token = ctxData
|
|
3291
|
-
const tokenIndex = ctxData.tokenIndex;
|
|
3292
|
-
const tokens = ctxData.tokens;
|
|
3819
|
+
const { token, tokenIndex, tokens, lang } = ctxData;
|
|
3293
3820
|
|
|
3294
3821
|
let isKwd = this._mustHightlightWord( token, CodeEditor.keywords ) || this.highlight == 'XML';
|
|
3295
3822
|
|
|
@@ -3306,6 +3833,10 @@ class CodeEditor {
|
|
|
3306
3833
|
isKwd |= ( ctxData.tokens[ tokenIndex - 2 ] == '$' );
|
|
3307
3834
|
}
|
|
3308
3835
|
}
|
|
3836
|
+
if( this.highlight == 'Markdown' )
|
|
3837
|
+
{
|
|
3838
|
+
isKwd = ( this._markdownHeader !== undefined );
|
|
3839
|
+
}
|
|
3309
3840
|
else if( lang.tags )
|
|
3310
3841
|
{
|
|
3311
3842
|
isKwd &= ( this._enclosedByTokens( token, tokenIndex, '<', '>' ) != undefined );
|
|
@@ -3315,24 +3846,14 @@ class CodeEditor {
|
|
|
3315
3846
|
return isKwd;
|
|
3316
3847
|
}
|
|
3317
3848
|
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
const token = ctxData.token;
|
|
3321
|
-
const prev = ctxData.prev;
|
|
3322
|
-
const next = ctxData.next;
|
|
3849
|
+
_isNumber( token ) {
|
|
3323
3850
|
|
|
3324
|
-
|
|
3851
|
+
const lang = CodeEditor.languages[ this.highlight ];
|
|
3852
|
+
if( !( lang.numbers ?? true ) )
|
|
3325
3853
|
{
|
|
3326
3854
|
return false;
|
|
3327
3855
|
}
|
|
3328
3856
|
|
|
3329
|
-
return ( prev == '.' || prev == '::'
|
|
3330
|
-
|| ( prev == ':' && next == '{' )
|
|
3331
|
-
|| ( token[ 0 ] == '#' && prev != ':' ) );
|
|
3332
|
-
}
|
|
3333
|
-
|
|
3334
|
-
_isNumber( token ) {
|
|
3335
|
-
|
|
3336
3857
|
const subToken = token.substring( 0, token.length - 1 );
|
|
3337
3858
|
|
|
3338
3859
|
if( this.highlight == 'C++' )
|
|
@@ -3364,56 +3885,27 @@ class CodeEditor {
|
|
|
3364
3885
|
return token.length && token != ' ' && !Number.isNaN( +token );
|
|
3365
3886
|
}
|
|
3366
3887
|
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
const token = ctxData.token;
|
|
3370
|
-
const prev = ctxData.prev;
|
|
3371
|
-
const next = ctxData.next;
|
|
3372
|
-
|
|
3373
|
-
// Common case
|
|
3374
|
-
if( this._mustHightlightWord( token, CodeEditor.types, lang ) )
|
|
3375
|
-
{
|
|
3376
|
-
return true;
|
|
3377
|
-
}
|
|
3888
|
+
_encloseSelectedWordWithKey( key, lidx, cursor ) {
|
|
3378
3889
|
|
|
3379
|
-
if(
|
|
3380
|
-
{
|
|
3381
|
-
return (prev == 'class' && next == '{') || (prev == 'new' && next == '(');
|
|
3382
|
-
}
|
|
3383
|
-
else if( this.highlight == 'C++' )
|
|
3890
|
+
if( !cursor.selection || ( cursor.selection.fromY != cursor.selection.toY ) )
|
|
3384
3891
|
{
|
|
3385
|
-
return
|
|
3386
|
-
}
|
|
3387
|
-
else if ( this.highlight == 'WGSL' )
|
|
3388
|
-
{
|
|
3389
|
-
const not_kwd = !this._mustHightlightWord( token, CodeEditor.keywords, lang );
|
|
3390
|
-
return (prev == 'struct' && next == '{') ||
|
|
3391
|
-
(not_kwd && prev == ':' && next == ';') ||
|
|
3392
|
-
( not_kwd &&
|
|
3393
|
-
( prev == ':' && next == ')' || prev == ':' && next == ',' || prev == '>' && next == '{'
|
|
3394
|
-
|| prev == '<' && next == ',' || prev == '<' && next == '>' || prev == '>' && token != ';' && !next ));
|
|
3892
|
+
return false;
|
|
3395
3893
|
}
|
|
3396
|
-
}
|
|
3397
|
-
|
|
3398
|
-
_encloseSelectedWordWithKey( key, lidx, cursor ) {
|
|
3399
|
-
|
|
3400
|
-
if( !cursor.selection || (cursor.selection.fromY != cursor.selection.toY) )
|
|
3401
|
-
return false;
|
|
3402
3894
|
|
|
3403
3895
|
cursor.selection.invertIfNecessary();
|
|
3404
3896
|
|
|
3405
3897
|
// Insert first..
|
|
3406
3898
|
this.code.lines[ lidx ] = [
|
|
3407
|
-
this.code.lines[ lidx ].slice(0, cursor.selection.fromX),
|
|
3899
|
+
this.code.lines[ lidx ].slice( 0, cursor.selection.fromX ),
|
|
3408
3900
|
key,
|
|
3409
|
-
this.code.lines[ lidx ].slice(cursor.selection.fromX)
|
|
3901
|
+
this.code.lines[ lidx ].slice( cursor.selection.fromX )
|
|
3410
3902
|
].join('');
|
|
3411
3903
|
|
|
3412
3904
|
// Go to the end of the word
|
|
3413
|
-
this.cursorToPosition(cursor, cursor.selection.toX + 1);
|
|
3905
|
+
this.cursorToPosition( cursor, cursor.selection.toX + 1 );
|
|
3414
3906
|
|
|
3415
3907
|
// Change next key?
|
|
3416
|
-
switch(key)
|
|
3908
|
+
switch( key )
|
|
3417
3909
|
{
|
|
3418
3910
|
case "'":
|
|
3419
3911
|
case "\"":
|
|
@@ -3441,6 +3933,46 @@ class CodeEditor {
|
|
|
3441
3933
|
return true;
|
|
3442
3934
|
}
|
|
3443
3935
|
|
|
3936
|
+
_detectLanguage( text ) {
|
|
3937
|
+
|
|
3938
|
+
const tokenSet = new Set( this._getTokensFromLine( text, true ) );
|
|
3939
|
+
const scores = {};
|
|
3940
|
+
|
|
3941
|
+
for( let [ lang, wordList ] of Object.entries( CodeEditor.keywords ) )
|
|
3942
|
+
{
|
|
3943
|
+
scores[ lang ] = 0;
|
|
3944
|
+
for( let kw of wordList )
|
|
3945
|
+
if( tokenSet.has( kw ) ) scores[ lang ]++;
|
|
3946
|
+
}
|
|
3947
|
+
|
|
3948
|
+
for( let [ lang, wordList ] of Object.entries( CodeEditor.statements ) )
|
|
3949
|
+
{
|
|
3950
|
+
for( let kw of wordList )
|
|
3951
|
+
if( tokenSet.has( kw ) ) scores[ lang ]++;
|
|
3952
|
+
}
|
|
3953
|
+
|
|
3954
|
+
for( let [ lang, wordList ] of Object.entries( CodeEditor.utils ) )
|
|
3955
|
+
{
|
|
3956
|
+
for( let kw of wordList )
|
|
3957
|
+
if( tokenSet.has( kw ) ) scores[ lang ]++;
|
|
3958
|
+
}
|
|
3959
|
+
|
|
3960
|
+
for( let [ lang, wordList ] of Object.entries( CodeEditor.types ) )
|
|
3961
|
+
{
|
|
3962
|
+
for( let kw of wordList )
|
|
3963
|
+
if( tokenSet.has( kw ) ) scores[ lang ]++;
|
|
3964
|
+
}
|
|
3965
|
+
|
|
3966
|
+
for( let [ lang, wordList ] of Object.entries( CodeEditor.builtIn ) )
|
|
3967
|
+
{
|
|
3968
|
+
for( let kw of wordList )
|
|
3969
|
+
if( tokenSet.has( kw ) ) scores[ lang ]++;
|
|
3970
|
+
}
|
|
3971
|
+
|
|
3972
|
+
const sorted = Object.entries( scores ).sort( ( a, b ) => b[ 1 ] - a[ 1 ] );
|
|
3973
|
+
return sorted[0][1] > 0 ? sorted[0][0] : undefined;
|
|
3974
|
+
}
|
|
3975
|
+
|
|
3444
3976
|
lineUp( cursor, resetLeft ) {
|
|
3445
3977
|
|
|
3446
3978
|
if( this.code.lines[ cursor.line - 1 ] == undefined )
|
|
@@ -3556,7 +4088,7 @@ class CodeEditor {
|
|
|
3556
4088
|
// Use main cursor
|
|
3557
4089
|
this._removeSecondaryCursors();
|
|
3558
4090
|
|
|
3559
|
-
var cursor = this.
|
|
4091
|
+
var cursor = this.getCurrentCursor();
|
|
3560
4092
|
this.resetCursorPos( CodeEditor.CURSOR_LEFT_TOP, cursor );
|
|
3561
4093
|
|
|
3562
4094
|
this.startSelection( cursor );
|
|
@@ -3578,7 +4110,7 @@ class CodeEditor {
|
|
|
3578
4110
|
if( !key ) return;
|
|
3579
4111
|
|
|
3580
4112
|
cursor._left += this.charWidth;
|
|
3581
|
-
cursor.style.left =
|
|
4113
|
+
cursor.style.left = `calc( ${ cursor._left }px + ${ this.xPadding } )`;
|
|
3582
4114
|
cursor.position++;
|
|
3583
4115
|
|
|
3584
4116
|
this.restartBlink();
|
|
@@ -3598,7 +4130,7 @@ class CodeEditor {
|
|
|
3598
4130
|
|
|
3599
4131
|
cursor._left -= this.charWidth;
|
|
3600
4132
|
cursor._left = Math.max( cursor._left, 0 );
|
|
3601
|
-
cursor.style.left =
|
|
4133
|
+
cursor.style.left = `calc( ${ cursor._left }px + ${ this.xPadding } )`;
|
|
3602
4134
|
cursor.position--;
|
|
3603
4135
|
cursor.position = Math.max( cursor.position, 0 );
|
|
3604
4136
|
this.restartBlink();
|
|
@@ -3616,8 +4148,8 @@ class CodeEditor {
|
|
|
3616
4148
|
cursorToTop( cursor, resetLeft = false ) {
|
|
3617
4149
|
|
|
3618
4150
|
cursor._top -= this.lineHeight;
|
|
3619
|
-
cursor._top = Math.max(cursor._top, 0);
|
|
3620
|
-
cursor.style.top =
|
|
4151
|
+
cursor._top = Math.max( cursor._top, 0 );
|
|
4152
|
+
cursor.style.top = `calc(${ cursor._top }px)`;
|
|
3621
4153
|
this.restartBlink();
|
|
3622
4154
|
|
|
3623
4155
|
if( resetLeft )
|
|
@@ -3646,11 +4178,8 @@ class CodeEditor {
|
|
|
3646
4178
|
}
|
|
3647
4179
|
|
|
3648
4180
|
const currentScrollTop = this.getScrollTop();
|
|
3649
|
-
const
|
|
3650
|
-
const
|
|
3651
|
-
const scrollerHeight = this.codeScroller.offsetHeight;
|
|
3652
|
-
|
|
3653
|
-
var lastLine = ( ( scrollerHeight - tabsHeight - infoPanelHeight + currentScrollTop ) / this.lineHeight )|0;
|
|
4181
|
+
const scrollerHeight = this.codeScroller.offsetHeight - this._fullVerticalOffset;
|
|
4182
|
+
const lastLine = ( ( scrollerHeight + currentScrollTop ) / this.lineHeight )|0;
|
|
3654
4183
|
if( cursor.line >= lastLine )
|
|
3655
4184
|
{
|
|
3656
4185
|
this.setScrollTop( currentScrollTop + this.lineHeight );
|
|
@@ -3660,7 +4189,9 @@ class CodeEditor {
|
|
|
3660
4189
|
cursorToString( cursor, text, reverse ) {
|
|
3661
4190
|
|
|
3662
4191
|
if( !text.length )
|
|
4192
|
+
{
|
|
3663
4193
|
return;
|
|
4194
|
+
}
|
|
3664
4195
|
|
|
3665
4196
|
for( let char of text )
|
|
3666
4197
|
{
|
|
@@ -3672,7 +4203,7 @@ class CodeEditor {
|
|
|
3672
4203
|
|
|
3673
4204
|
cursor.position = position;
|
|
3674
4205
|
cursor._left = position * this.charWidth;
|
|
3675
|
-
cursor.style.left =
|
|
4206
|
+
cursor.style.left = `calc( ${ cursor._left }px + ${ this.xPadding } )`;
|
|
3676
4207
|
}
|
|
3677
4208
|
|
|
3678
4209
|
cursorToLine( cursor, line, resetLeft = false ) {
|
|
@@ -3704,14 +4235,24 @@ class CodeEditor {
|
|
|
3704
4235
|
return cursors;
|
|
3705
4236
|
}
|
|
3706
4237
|
|
|
4238
|
+
getCurrentCursor( removeOthers ) {
|
|
4239
|
+
|
|
4240
|
+
if( removeOthers )
|
|
4241
|
+
{
|
|
4242
|
+
this._removeSecondaryCursors();
|
|
4243
|
+
}
|
|
4244
|
+
|
|
4245
|
+
return this.cursors.children[ 0 ];
|
|
4246
|
+
}
|
|
4247
|
+
|
|
3707
4248
|
relocateCursors() {
|
|
3708
4249
|
|
|
3709
4250
|
for( let cursor of this.cursors.children )
|
|
3710
4251
|
{
|
|
3711
4252
|
cursor._left = cursor.position * this.charWidth;
|
|
3712
|
-
cursor.style.left =
|
|
4253
|
+
cursor.style.left = `calc( ${ cursor._left }px + ${ this.xPadding } )`;
|
|
3713
4254
|
cursor._top = cursor.line * this.lineHeight;
|
|
3714
|
-
cursor.style.top =
|
|
4255
|
+
cursor.style.top = `calc(${ cursor._top }px)`;
|
|
3715
4256
|
}
|
|
3716
4257
|
}
|
|
3717
4258
|
|
|
@@ -3730,9 +4271,9 @@ class CodeEditor {
|
|
|
3730
4271
|
cursor.line = state.line ?? 0;
|
|
3731
4272
|
|
|
3732
4273
|
cursor._left = cursor.position * this.charWidth;
|
|
3733
|
-
cursor.style.left =
|
|
4274
|
+
cursor.style.left = `calc( ${ cursor._left }px + ${ this.xPadding } )`;
|
|
3734
4275
|
cursor._top = cursor.line * this.lineHeight;
|
|
3735
|
-
cursor.style.top =
|
|
4276
|
+
cursor.style.top = `calc(${ cursor._top }px)`;
|
|
3736
4277
|
|
|
3737
4278
|
if( state.selection )
|
|
3738
4279
|
{
|
|
@@ -3753,7 +4294,7 @@ class CodeEditor {
|
|
|
3753
4294
|
|
|
3754
4295
|
resetCursorPos( flag, cursor ) {
|
|
3755
4296
|
|
|
3756
|
-
cursor = cursor ?? this.
|
|
4297
|
+
cursor = cursor ?? this.getCurrentCursor();
|
|
3757
4298
|
|
|
3758
4299
|
if( flag & CodeEditor.CURSOR_LEFT )
|
|
3759
4300
|
{
|
|
@@ -3770,18 +4311,85 @@ class CodeEditor {
|
|
|
3770
4311
|
}
|
|
3771
4312
|
}
|
|
3772
4313
|
|
|
4314
|
+
_addCursor( line = 0, position = 0, force, isMain = false ) {
|
|
4315
|
+
|
|
4316
|
+
// If cursor in that position exists, remove it instead..
|
|
4317
|
+
const exists = Array.from( this.cursors.children ).find( v => v.position == position && v.line == line );
|
|
4318
|
+
if( exists && !force )
|
|
4319
|
+
{
|
|
4320
|
+
if( !exists.isMain )
|
|
4321
|
+
exists.remove();
|
|
4322
|
+
|
|
4323
|
+
return;
|
|
4324
|
+
}
|
|
4325
|
+
|
|
4326
|
+
let cursor = document.createElement( 'div' );
|
|
4327
|
+
cursor.name = "cursor" + this.cursors.childElementCount;
|
|
4328
|
+
cursor.className = "cursor";
|
|
4329
|
+
cursor.innerHTML = " ";
|
|
4330
|
+
cursor.isMain = isMain;
|
|
4331
|
+
cursor._left = position * this.charWidth;
|
|
4332
|
+
cursor.style.left = "calc( " + cursor._left + "px + " + this.xPadding + " )";
|
|
4333
|
+
cursor._top = line * this.lineHeight;
|
|
4334
|
+
cursor.style.top = cursor._top + "px";
|
|
4335
|
+
cursor._position = position;
|
|
4336
|
+
cursor._line = line;
|
|
4337
|
+
cursor.print = (function() { console.log( this._line, this._position ) }).bind( cursor );
|
|
4338
|
+
cursor.isLast = (function() { return this.cursors.lastChild == cursor; }).bind( this );
|
|
4339
|
+
|
|
4340
|
+
Object.defineProperty( cursor, 'line', {
|
|
4341
|
+
get: (v) => { return cursor._line },
|
|
4342
|
+
set: (v) => {
|
|
4343
|
+
cursor._line = v;
|
|
4344
|
+
if( cursor.isMain ) this._setActiveLine( v );
|
|
4345
|
+
}
|
|
4346
|
+
} );
|
|
4347
|
+
|
|
4348
|
+
Object.defineProperty( cursor, 'position', {
|
|
4349
|
+
get: (v) => { return cursor._position },
|
|
4350
|
+
set: (v) => {
|
|
4351
|
+
cursor._position = v;
|
|
4352
|
+
if( cursor.isMain )
|
|
4353
|
+
{
|
|
4354
|
+
const activeLine = this.state.activeLine;
|
|
4355
|
+
this._updateDataInfoPanel( "@cursor-data", `Ln ${ activeLine + 1 }, Col ${ v + 1 }` );
|
|
4356
|
+
}
|
|
4357
|
+
}
|
|
4358
|
+
} );
|
|
4359
|
+
|
|
4360
|
+
this.cursors.appendChild( cursor );
|
|
4361
|
+
|
|
4362
|
+
return cursor;
|
|
4363
|
+
}
|
|
4364
|
+
|
|
4365
|
+
_removeSecondaryCursors() {
|
|
4366
|
+
|
|
4367
|
+
while( this.cursors.childElementCount > 1 )
|
|
4368
|
+
this.cursors.lastChild.remove();
|
|
4369
|
+
}
|
|
4370
|
+
|
|
4371
|
+
_logCursors() {
|
|
4372
|
+
|
|
4373
|
+
for( let cursor of this.cursors.children )
|
|
4374
|
+
{
|
|
4375
|
+
cursor.print();
|
|
4376
|
+
}
|
|
4377
|
+
}
|
|
4378
|
+
|
|
3773
4379
|
_addSpaceTabs( cursor, n ) {
|
|
3774
4380
|
|
|
3775
|
-
for( var i = 0; i < n; ++i )
|
|
4381
|
+
for( var i = 0; i < n; ++i )
|
|
4382
|
+
{
|
|
3776
4383
|
this.actions[ 'Tab' ].callback( cursor.line, cursor, null );
|
|
3777
4384
|
}
|
|
3778
4385
|
}
|
|
3779
4386
|
|
|
3780
4387
|
_addSpaces( n ) {
|
|
3781
4388
|
|
|
3782
|
-
for( var i = 0; i < n; ++i )
|
|
4389
|
+
for( var i = 0; i < n; ++i )
|
|
4390
|
+
{
|
|
3783
4391
|
this.root.dispatchEvent( new CustomEvent( 'keydown', { 'detail': {
|
|
3784
|
-
|
|
4392
|
+
skipUndo: true,
|
|
3785
4393
|
key: ' ',
|
|
3786
4394
|
targetCursor: this._lastProcessedCursorIndex
|
|
3787
4395
|
}}));
|
|
@@ -3789,16 +4397,21 @@ class CodeEditor {
|
|
|
3789
4397
|
}
|
|
3790
4398
|
|
|
3791
4399
|
_removeSpaces( cursor ) {
|
|
4400
|
+
|
|
3792
4401
|
const lidx = cursor.line;
|
|
4402
|
+
|
|
3793
4403
|
// Remove indentation
|
|
3794
4404
|
let lineStart = firstNonspaceIndex( this.code.lines[ lidx ] );
|
|
3795
4405
|
|
|
3796
4406
|
// Nothing to remove... we are at the start of the line
|
|
3797
4407
|
if( lineStart == 0 )
|
|
4408
|
+
{
|
|
3798
4409
|
return;
|
|
4410
|
+
}
|
|
3799
4411
|
|
|
3800
4412
|
// Only tabs/spaces in the line...
|
|
3801
|
-
if( lineStart == -1 )
|
|
4413
|
+
if( lineStart == -1 )
|
|
4414
|
+
{
|
|
3802
4415
|
lineStart = this.code.lines[ lidx ].length;
|
|
3803
4416
|
}
|
|
3804
4417
|
|
|
@@ -3835,7 +4448,7 @@ class CodeEditor {
|
|
|
3835
4448
|
|
|
3836
4449
|
setScrollLeft( value ) {
|
|
3837
4450
|
if( !this.codeScroller ) return;
|
|
3838
|
-
doAsync( () => {
|
|
4451
|
+
LX.doAsync( () => {
|
|
3839
4452
|
this.codeScroller.scrollLeft = value;
|
|
3840
4453
|
this.setScrollBarValue( 'horizontal', 0 );
|
|
3841
4454
|
}, 20 );
|
|
@@ -3843,30 +4456,34 @@ class CodeEditor {
|
|
|
3843
4456
|
|
|
3844
4457
|
setScrollTop( value ) {
|
|
3845
4458
|
if( !this.codeScroller ) return;
|
|
3846
|
-
doAsync( () => {
|
|
4459
|
+
LX.doAsync( () => {
|
|
3847
4460
|
this.codeScroller.scrollTop = value;
|
|
3848
4461
|
this.setScrollBarValue( 'vertical' );
|
|
3849
4462
|
}, 20 );
|
|
3850
4463
|
}
|
|
3851
4464
|
|
|
3852
|
-
resize( pMaxLength, onResize ) {
|
|
4465
|
+
resize( flag = CodeEditor.RESIZE_SCROLLBAR_H_V, pMaxLength, onResize ) {
|
|
3853
4466
|
|
|
3854
4467
|
setTimeout( () => {
|
|
3855
4468
|
|
|
3856
|
-
|
|
3857
|
-
const maxLineLength = pMaxLength ?? this.getMaxLineLength();
|
|
3858
|
-
const scrollWidth = maxLineLength * this.charWidth + CodeEditor.LINE_GUTTER_WIDTH;
|
|
4469
|
+
let scrollWidth, scrollHeight;
|
|
3859
4470
|
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
4471
|
+
if( flag & CodeEditor.RESIZE_SCROLLBAR_H )
|
|
4472
|
+
{
|
|
4473
|
+
// Update max viewport
|
|
4474
|
+
const maxLineLength = pMaxLength ?? this.getMaxLineLength();
|
|
4475
|
+
this._lastMaxLineLength = maxLineLength;
|
|
4476
|
+
scrollWidth = maxLineLength * this.charWidth + CodeEditor.LINE_GUTTER_WIDTH;
|
|
4477
|
+
this.codeSizer.style.minWidth = scrollWidth + "px";
|
|
4478
|
+
}
|
|
3865
4479
|
|
|
3866
|
-
|
|
3867
|
-
|
|
4480
|
+
if( flag & CodeEditor.RESIZE_SCROLLBAR_V )
|
|
4481
|
+
{
|
|
4482
|
+
scrollHeight = this.code.lines.length * this.lineHeight + this._fullVerticalOffset;
|
|
4483
|
+
this.codeSizer.style.minHeight = scrollHeight + "px";
|
|
4484
|
+
}
|
|
3868
4485
|
|
|
3869
|
-
this.resizeScrollBars();
|
|
4486
|
+
this.resizeScrollBars( flag );
|
|
3870
4487
|
|
|
3871
4488
|
if( onResize )
|
|
3872
4489
|
{
|
|
@@ -3876,37 +4493,52 @@ class CodeEditor {
|
|
|
3876
4493
|
}, 10 );
|
|
3877
4494
|
}
|
|
3878
4495
|
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
const totalLinesInViewport = ((this.codeScroller.offsetHeight) / this.lineHeight)|0;
|
|
4496
|
+
resizeIfNecessary( cursor, force ) {
|
|
3882
4497
|
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
this.vScrollbar.root.classList.add( 'scrollbar-unused' );
|
|
3887
|
-
}
|
|
3888
|
-
else
|
|
4498
|
+
const maxLineLength = this.getMaxLineLength();
|
|
4499
|
+
const numViewportChars = Math.floor( ( this.codeScroller.clientWidth - CodeEditor.LINE_GUTTER_WIDTH ) / this.charWidth );
|
|
4500
|
+
if( force || ( maxLineLength >= numViewportChars && maxLineLength != this._lastMaxLineLength ) )
|
|
3889
4501
|
{
|
|
3890
|
-
this.
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
4502
|
+
this.resize( CodeEditor.RESIZE_SCROLLBAR_H, maxLineLength, () => {
|
|
4503
|
+
if( cursor.position > numViewportChars )
|
|
4504
|
+
{
|
|
4505
|
+
this.setScrollLeft( cursor.position * this.charWidth );
|
|
4506
|
+
}
|
|
4507
|
+
} );
|
|
3894
4508
|
}
|
|
4509
|
+
}
|
|
3895
4510
|
|
|
3896
|
-
|
|
3897
|
-
const maxLineLength = this._lastMaxLineLength;
|
|
4511
|
+
resizeScrollBars( flag = CodeEditor.RESIZE_SCROLLBAR_H_V ) {
|
|
3898
4512
|
|
|
3899
|
-
if(
|
|
4513
|
+
if( flag & CodeEditor.RESIZE_SCROLLBAR_V )
|
|
3900
4514
|
{
|
|
3901
|
-
this.codeScroller.
|
|
3902
|
-
this.
|
|
4515
|
+
const totalLinesInViewport = (( this.codeScroller.offsetHeight ) / this.lineHeight)|0;
|
|
4516
|
+
const needsVerticalScrollbar = ( this.code.lines.length >= totalLinesInViewport );
|
|
4517
|
+
if( needsVerticalScrollbar )
|
|
4518
|
+
{
|
|
4519
|
+
this.vScrollbar.thumb.size = ( totalLinesInViewport / this.code.lines.length );
|
|
4520
|
+
this.vScrollbar.thumb.style.height = (this.vScrollbar.thumb.size * 100.0) + "%";
|
|
4521
|
+
}
|
|
4522
|
+
|
|
4523
|
+
this.vScrollbar.root.classList.toggle( 'hidden', !needsVerticalScrollbar );
|
|
4524
|
+
this.hScrollbar.root.style.width = `calc(100% - ${ 48 + ( needsVerticalScrollbar ? ScrollBar.SCROLLBAR_VERTICAL_WIDTH : 0 ) }px)`; // 48 is the line gutter
|
|
4525
|
+
this.codeArea.root.style.width = `calc(100% - ${ needsVerticalScrollbar ? ScrollBar.SCROLLBAR_VERTICAL_WIDTH : 0 }px)`;
|
|
3903
4526
|
}
|
|
3904
|
-
|
|
4527
|
+
|
|
4528
|
+
if( flag & CodeEditor.RESIZE_SCROLLBAR_H )
|
|
3905
4529
|
{
|
|
3906
|
-
this.codeScroller.
|
|
3907
|
-
this.
|
|
3908
|
-
|
|
3909
|
-
|
|
4530
|
+
const numViewportChars = Math.floor( ( this.codeScroller.clientWidth - CodeEditor.LINE_GUTTER_WIDTH ) / this.charWidth );
|
|
4531
|
+
const maxLineLength = this._lastMaxLineLength;
|
|
4532
|
+
const needsHorizontalScrollbar = maxLineLength >= numViewportChars;
|
|
4533
|
+
|
|
4534
|
+
if( needsHorizontalScrollbar )
|
|
4535
|
+
{
|
|
4536
|
+
this.hScrollbar.thumb.size = ( numViewportChars / maxLineLength );
|
|
4537
|
+
this.hScrollbar.thumb.style.width = ( this.hScrollbar.thumb.size * 100.0 ) + "%";
|
|
4538
|
+
}
|
|
4539
|
+
|
|
4540
|
+
this.hScrollbar.root.classList.toggle( 'hidden', !needsHorizontalScrollbar );
|
|
4541
|
+
this.codeArea.root.style.height = `calc(100% - ${ this._verticalBottomOffset + ( needsHorizontalScrollbar ? ScrollBar.SCROLLBAR_HORIZONTAL_HEIGHT : 0 ) }px)`;
|
|
3910
4542
|
}
|
|
3911
4543
|
}
|
|
3912
4544
|
|
|
@@ -3980,7 +4612,6 @@ class CodeEditor {
|
|
|
3980
4612
|
}
|
|
3981
4613
|
|
|
3982
4614
|
getCharAtPos( cursor, offset = 0 ) {
|
|
3983
|
-
|
|
3984
4615
|
return this.code.lines[ cursor.line ][ cursor.position + offset ];
|
|
3985
4616
|
}
|
|
3986
4617
|
|
|
@@ -4034,7 +4665,7 @@ class CodeEditor {
|
|
|
4034
4665
|
return [ word, from, to ];
|
|
4035
4666
|
}
|
|
4036
4667
|
|
|
4037
|
-
_measureChar( char = "a",
|
|
4668
|
+
_measureChar( char = "a", useFloating = false, getBB = false ) {
|
|
4038
4669
|
const parentContainer = LX.makeContainer( null, "lexcodeeditor", "", document.body );
|
|
4039
4670
|
const container = LX.makeContainer( null, "code", "", parentContainer );
|
|
4040
4671
|
const line = document.createElement( "pre" );
|
|
@@ -4044,12 +4675,11 @@ class CodeEditor {
|
|
|
4044
4675
|
text.innerText = char;
|
|
4045
4676
|
var rect = text.getBoundingClientRect();
|
|
4046
4677
|
LX.deleteElement( parentContainer );
|
|
4047
|
-
const bb = [
|
|
4048
|
-
return
|
|
4678
|
+
const bb = [ useFloating ? rect.width : Math.floor( rect.width ), useFloating ? rect.height : Math.floor( rect.height ) ];
|
|
4679
|
+
return getBB ? bb : bb[ 0 ];
|
|
4049
4680
|
}
|
|
4050
4681
|
|
|
4051
4682
|
measureString( str ) {
|
|
4052
|
-
|
|
4053
4683
|
return str.length * this.charWidth;
|
|
4054
4684
|
}
|
|
4055
4685
|
|
|
@@ -4102,46 +4732,64 @@ class CodeEditor {
|
|
|
4102
4732
|
showAutoCompleteBox( key, cursor ) {
|
|
4103
4733
|
|
|
4104
4734
|
if( !cursor.isMain )
|
|
4735
|
+
{
|
|
4105
4736
|
return;
|
|
4737
|
+
}
|
|
4106
4738
|
|
|
4107
|
-
const [word, start, end] = this.getWordAtPos( cursor, -1 );
|
|
4108
|
-
if( key == ' ' || !word.length )
|
|
4739
|
+
const [ word, start, end ] = this.getWordAtPos( cursor, -1 );
|
|
4740
|
+
if( key == ' ' || !word.length )
|
|
4741
|
+
{
|
|
4109
4742
|
this.hideAutoCompleteBox();
|
|
4110
4743
|
return;
|
|
4111
4744
|
}
|
|
4112
4745
|
|
|
4113
4746
|
this.autocomplete.innerHTML = ""; // Clear all suggestions
|
|
4114
4747
|
|
|
4115
|
-
let suggestions = [];
|
|
4116
|
-
|
|
4117
4748
|
// Add language special keys...
|
|
4118
|
-
suggestions =
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4749
|
+
let suggestions = [
|
|
4750
|
+
...Array.from( CodeEditor.keywords[ this.highlight ] ?? [] ),
|
|
4751
|
+
...Array.from( CodeEditor.builtIn[ this.highlight ] ?? [] ),
|
|
4752
|
+
...Array.from( CodeEditor.statements[ this.highlight ] ?? [] ),
|
|
4753
|
+
...Array.from( CodeEditor.types[ this.highlight ] ?? [] ),
|
|
4754
|
+
...Array.from( CodeEditor.utils[ this.highlight ] ?? [] )
|
|
4755
|
+
];
|
|
4756
|
+
|
|
4757
|
+
suggestions = suggestions.concat( Object.keys(this.code.tokens).filter( a => a != word ) );
|
|
4758
|
+
|
|
4759
|
+
const scopeStack = [ ...this.code.lineScopes[ cursor.line ] ];
|
|
4760
|
+
const scope = scopeStack.at( -1 );
|
|
4761
|
+
if( scope.type.startsWith( "enum" ) )
|
|
4762
|
+
{
|
|
4763
|
+
const enumValues = Array.from( this.code.symbolsTable ).filter( s => s[ 1 ][ 0 ].kind === "enum_value" && s[ 1 ][ 0 ].scope === scope.name ).map( s => s[ 0 ] );
|
|
4764
|
+
suggestions = suggestions.concat( enumValues.slice( 0, -1 ) );
|
|
4765
|
+
}
|
|
4766
|
+
else
|
|
4767
|
+
{
|
|
4768
|
+
const otherValues = Array.from( this.code.symbolsTable ).map( s => s[ 0 ] );
|
|
4769
|
+
suggestions = suggestions.concat( otherValues.slice( 0, -1 ) );
|
|
4770
|
+
}
|
|
4125
4771
|
|
|
4126
|
-
|
|
4127
|
-
// suggestions = suggestions.concat( Object.keys(this.code.tokens).filter( a => a != word ) );
|
|
4772
|
+
const prefix = word.toLowerCase();
|
|
4128
4773
|
|
|
4129
4774
|
// Remove 1/2 char words and duplicates...
|
|
4130
|
-
suggestions =
|
|
4775
|
+
suggestions = Array.from( new Set( suggestions )).filter( s => s.length > 2 && s.toLowerCase().includes( prefix ) );
|
|
4131
4776
|
|
|
4132
4777
|
// Order...
|
|
4133
|
-
|
|
4778
|
+
|
|
4779
|
+
function scoreSuggestion( s, prefix ) {
|
|
4780
|
+
if( s.startsWith( prefix ) ) return 0; // best option
|
|
4781
|
+
if( s.includes( prefix )) return 1;
|
|
4782
|
+
return 2; // worst
|
|
4783
|
+
}
|
|
4784
|
+
|
|
4785
|
+
suggestions = suggestions.sort( ( a, b ) => scoreSuggestion( a, prefix ) - scoreSuggestion( b, prefix ) || a.localeCompare( b ) );
|
|
4134
4786
|
|
|
4135
4787
|
for( let s of suggestions )
|
|
4136
4788
|
{
|
|
4137
|
-
if( !s.toLowerCase().includes( word.toLowerCase() ) )
|
|
4138
|
-
continue;
|
|
4139
|
-
|
|
4140
4789
|
var pre = document.createElement( 'pre' );
|
|
4141
4790
|
this.autocomplete.appendChild( pre );
|
|
4142
4791
|
|
|
4143
4792
|
var icon = "Type";
|
|
4144
|
-
|
|
4145
4793
|
if( this._mustHightlightWord( s, CodeEditor.utils ) )
|
|
4146
4794
|
icon = "Box";
|
|
4147
4795
|
else if( this._mustHightlightWord( s, CodeEditor.types ) )
|
|
@@ -4177,11 +4825,11 @@ class CodeEditor {
|
|
|
4177
4825
|
}
|
|
4178
4826
|
|
|
4179
4827
|
// Select always first option
|
|
4180
|
-
this.autocomplete.firstChild.classList.add('selected');
|
|
4828
|
+
this.autocomplete.firstChild.classList.add( 'selected' );
|
|
4181
4829
|
|
|
4182
4830
|
// Show box
|
|
4183
|
-
this.autocomplete.classList.toggle('show', true);
|
|
4184
|
-
this.autocomplete.classList.toggle('no-scrollbar', !(this.autocomplete.scrollHeight > this.autocomplete.offsetHeight));
|
|
4831
|
+
this.autocomplete.classList.toggle( 'show', true );
|
|
4832
|
+
this.autocomplete.classList.toggle( 'no-scrollbar', !( this.autocomplete.scrollHeight > this.autocomplete.offsetHeight ) );
|
|
4185
4833
|
this.autocomplete.style.left = (cursor._left + CodeEditor.LINE_GUTTER_WIDTH - this.getScrollLeft()) + "px";
|
|
4186
4834
|
this.autocomplete.style.top = (cursor._top + 28 + this.lineHeight - this.getScrollTop()) + "px";
|
|
4187
4835
|
|
|
@@ -4293,7 +4941,7 @@ class CodeEditor {
|
|
|
4293
4941
|
}
|
|
4294
4942
|
else
|
|
4295
4943
|
{
|
|
4296
|
-
const cursor = this.
|
|
4944
|
+
const cursor = this.getCurrentCursor();
|
|
4297
4945
|
|
|
4298
4946
|
if( cursor.selection )
|
|
4299
4947
|
{
|
|
@@ -4333,9 +4981,11 @@ class CodeEditor {
|
|
|
4333
4981
|
text = text ?? this._lastTextFound;
|
|
4334
4982
|
|
|
4335
4983
|
if( !text )
|
|
4984
|
+
{
|
|
4336
4985
|
return;
|
|
4986
|
+
}
|
|
4337
4987
|
|
|
4338
|
-
let cursor = this.
|
|
4988
|
+
let cursor = this.getCurrentCursor();
|
|
4339
4989
|
let cursorData = new LX.vec2( cursor.position, cursor.line );
|
|
4340
4990
|
let line = null;
|
|
4341
4991
|
let char = -1;
|
|
@@ -4344,6 +4994,7 @@ class CodeEditor {
|
|
|
4344
4994
|
{
|
|
4345
4995
|
LX.deleteElement( this._lastResult.dom );
|
|
4346
4996
|
cursorData = this._lastResult.pos;
|
|
4997
|
+
cursorData.x += text.length * ( reverse ? -1 : 1 );
|
|
4347
4998
|
delete this._lastResult;
|
|
4348
4999
|
}
|
|
4349
5000
|
|
|
@@ -4389,10 +5040,12 @@ class CodeEditor {
|
|
|
4389
5040
|
}
|
|
4390
5041
|
}
|
|
4391
5042
|
|
|
4392
|
-
if( line == null)
|
|
5043
|
+
if( line == null )
|
|
4393
5044
|
{
|
|
4394
5045
|
if( !skipAlert )
|
|
5046
|
+
{
|
|
4395
5047
|
alert( "No results!" );
|
|
5048
|
+
}
|
|
4396
5049
|
|
|
4397
5050
|
const lastLine = this.code.lines.length - 1;
|
|
4398
5051
|
|
|
@@ -4400,6 +5053,7 @@ class CodeEditor {
|
|
|
4400
5053
|
'dom': this.searchResultSelections.lastChild,
|
|
4401
5054
|
'pos': reverse ? new LX.vec2( this.code.lines[ lastLine ].length, lastLine ) : new LX.vec2( 0, 0 )
|
|
4402
5055
|
};
|
|
5056
|
+
|
|
4403
5057
|
return;
|
|
4404
5058
|
}
|
|
4405
5059
|
|
|
@@ -4409,9 +5063,10 @@ class CodeEditor {
|
|
|
4409
5063
|
have to add the length of the substring (0, first_ocurrence)
|
|
4410
5064
|
*/
|
|
4411
5065
|
|
|
4412
|
-
|
|
4413
5066
|
if( !reverse )
|
|
5067
|
+
{
|
|
4414
5068
|
char += ( line == cursorData.y ? cursorData.x : 0 );
|
|
5069
|
+
}
|
|
4415
5070
|
|
|
4416
5071
|
|
|
4417
5072
|
// Text found..
|
|
@@ -4439,8 +5094,13 @@ class CodeEditor {
|
|
|
4439
5094
|
|
|
4440
5095
|
this._lastResult = {
|
|
4441
5096
|
'dom': this.searchResultSelections.lastChild,
|
|
4442
|
-
'pos': new LX.vec2( char
|
|
5097
|
+
'pos': new LX.vec2( char , line ),
|
|
5098
|
+
reverse
|
|
4443
5099
|
};
|
|
5100
|
+
|
|
5101
|
+
// Force focus back to search box
|
|
5102
|
+
const input = this.searchbox.querySelector( 'input' );
|
|
5103
|
+
input.focus();
|
|
4444
5104
|
}
|
|
4445
5105
|
|
|
4446
5106
|
showSearchLineBox() {
|
|
@@ -4472,7 +5132,7 @@ class CodeEditor {
|
|
|
4472
5132
|
this.codeScroller.scrollTo( 0, Math.max( line - 15 ) * this.lineHeight );
|
|
4473
5133
|
|
|
4474
5134
|
// Select line ?
|
|
4475
|
-
var cursor = this.
|
|
5135
|
+
var cursor = this.getCurrentCursor( true );
|
|
4476
5136
|
this.cursorToLine( cursor, line - 1, true );
|
|
4477
5137
|
}
|
|
4478
5138
|
|
|
@@ -4527,13 +5187,16 @@ class CodeEditor {
|
|
|
4527
5187
|
|
|
4528
5188
|
number = number ?? this.state.activeLine;
|
|
4529
5189
|
|
|
4530
|
-
|
|
5190
|
+
const cursor = this.getCurrentCursor();
|
|
5191
|
+
this._updateDataInfoPanel( "@cursor-data", `Ln ${ number + 1 }, Col ${ cursor.position + 1 }` );
|
|
4531
5192
|
|
|
4532
|
-
const
|
|
4533
|
-
let line = this.code.childNodes[
|
|
5193
|
+
const oldLocal = this.toLocalLine( this.state.activeLine );
|
|
5194
|
+
let line = this.code.childNodes[ oldLocal ];
|
|
4534
5195
|
|
|
4535
5196
|
if( !line )
|
|
5197
|
+
{
|
|
4536
5198
|
return;
|
|
5199
|
+
}
|
|
4537
5200
|
|
|
4538
5201
|
line.classList.remove( 'active-line' );
|
|
4539
5202
|
|
|
@@ -4541,71 +5204,54 @@ class CodeEditor {
|
|
|
4541
5204
|
{
|
|
4542
5205
|
this.state.activeLine = number;
|
|
4543
5206
|
|
|
4544
|
-
const
|
|
4545
|
-
line = this.code.childNodes[
|
|
5207
|
+
const newLocal = this.toLocalLine( number );
|
|
5208
|
+
line = this.code.childNodes[ newLocal ];
|
|
4546
5209
|
if( line ) line.classList.add( 'active-line' );
|
|
4547
5210
|
}
|
|
4548
5211
|
}
|
|
4549
5212
|
|
|
4550
5213
|
_hideActiveLine() {
|
|
4551
|
-
|
|
4552
5214
|
this.code.querySelectorAll( '.active-line' ).forEach( e => e.classList.remove( 'active-line' ) );
|
|
4553
5215
|
}
|
|
4554
5216
|
|
|
4555
|
-
|
|
4556
|
-
|
|
5217
|
+
_setFontSize( size ) {
|
|
4557
5218
|
// Change font size
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
var pixels = parseInt( s.getPropertyValue( "--code-editor-font-size" ) );
|
|
4562
|
-
pixels = LX.clamp( pixels + 1, CodeEditor.CODE_MIN_FONT_SIZE, CodeEditor.CODE_MAX_FONT_SIZE );
|
|
4563
|
-
r.style.setProperty( "--code-editor-font-size", pixels + "px" );
|
|
5219
|
+
this.fontSize = size;
|
|
5220
|
+
const r = document.querySelector( ':root' );
|
|
5221
|
+
r.style.setProperty( "--code-editor-font-size", `${ this.fontSize }px` );
|
|
4564
5222
|
this.charWidth = this._measureChar( "a", true );
|
|
4565
5223
|
|
|
4566
|
-
|
|
5224
|
+
window.localStorage.setItem( "lexcodeeditor-font-size", this.fontSize );
|
|
4567
5225
|
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
5226
|
+
// Change row size
|
|
5227
|
+
const rowPixels = this.fontSize + 6;
|
|
5228
|
+
r.style.setProperty( "--code-editor-row-height", `${ rowPixels }px` );
|
|
5229
|
+
this.lineHeight = rowPixels;
|
|
4571
5230
|
|
|
4572
5231
|
// Relocate cursors
|
|
4573
|
-
|
|
4574
5232
|
this.relocateCursors();
|
|
4575
5233
|
|
|
4576
5234
|
// Resize the code area
|
|
4577
|
-
|
|
4578
5235
|
this.processLines();
|
|
4579
|
-
}
|
|
4580
|
-
|
|
4581
|
-
_decreaseFontSize() {
|
|
4582
|
-
|
|
4583
|
-
// Change font size
|
|
4584
|
-
|
|
4585
|
-
var r = document.querySelector( ':root' );
|
|
4586
|
-
var s = getComputedStyle( r );
|
|
4587
|
-
var pixels = parseInt( s.getPropertyValue( "--code-editor-font-size" ) );
|
|
4588
|
-
pixels = LX.clamp( pixels - 1, CodeEditor.CODE_MIN_FONT_SIZE, CodeEditor.CODE_MAX_FONT_SIZE );
|
|
4589
|
-
r.style.setProperty( "--code-editor-font-size", pixels + "px" );
|
|
4590
|
-
this.charWidth = this._measureChar( "a", true );
|
|
4591
|
-
|
|
4592
|
-
// Change row size
|
|
4593
|
-
|
|
4594
|
-
var row_pixels = pixels + 6;
|
|
4595
|
-
r.style.setProperty( "--code-editor-row-height", row_pixels + "px" );
|
|
4596
|
-
this.lineHeight = row_pixels;
|
|
4597
5236
|
|
|
4598
|
-
//
|
|
5237
|
+
// Emit event
|
|
5238
|
+
LX.emit( "@font-size", this.fontSize );
|
|
5239
|
+
}
|
|
4599
5240
|
|
|
4600
|
-
|
|
5241
|
+
_applyFontSizeOffset( offset = 0 ) {
|
|
5242
|
+
const newFontSize = LX.clamp( this.fontSize + offset, CodeEditor.CODE_MIN_FONT_SIZE, CodeEditor.CODE_MAX_FONT_SIZE );
|
|
5243
|
+
this._setFontSize( newFontSize );
|
|
5244
|
+
}
|
|
4601
5245
|
|
|
4602
|
-
|
|
5246
|
+
_increaseFontSize() {
|
|
5247
|
+
this._applyFontSizeOffset( 1 );
|
|
5248
|
+
}
|
|
4603
5249
|
|
|
4604
|
-
|
|
5250
|
+
_decreaseFontSize() {
|
|
5251
|
+
this._applyFontSizeOffset( -1 );
|
|
4605
5252
|
}
|
|
4606
5253
|
|
|
4607
5254
|
_clearTmpVariables() {
|
|
4608
|
-
|
|
4609
5255
|
delete this._currentLineString;
|
|
4610
5256
|
delete this._currentLineNumber;
|
|
4611
5257
|
delete this._buildingString;
|
|
@@ -4613,38 +5259,70 @@ class CodeEditor {
|
|
|
4613
5259
|
delete this._buildingBlockComment;
|
|
4614
5260
|
delete this._markdownHeader;
|
|
4615
5261
|
delete this._lastResult;
|
|
5262
|
+
delete this._scopeStack;
|
|
4616
5263
|
}
|
|
4617
5264
|
}
|
|
4618
5265
|
|
|
4619
|
-
CodeEditor.
|
|
5266
|
+
CodeEditor.languages = {
|
|
5267
|
+
'Plain Text': { ext: "txt", blockComments: false, singleLineComments: false, numbers: false, icon: "AlignLeft gray" },
|
|
5268
|
+
'JavaScript': { ext: "js", icon: "Js goldenrod" },
|
|
5269
|
+
'TypeScript': { ext: "ts", icon: "Ts pipelineblue" },
|
|
5270
|
+
'C': { ext: [ 'c', 'h' ], usePreprocessor: true, icon: { 'c': "C pictonblue", 'h': "C heliotrope" } },
|
|
5271
|
+
'C++': { ext: [ "cpp", "hpp" ], usePreprocessor: true, icon: { 'cpp': "CPlusPlus pictonblue", 'hpp': "CPlusPlus heliotrope" } },
|
|
5272
|
+
'CSS': { ext: "css", icon: "Hash dodgerblue" },
|
|
5273
|
+
'CMake': { ext: "cmake", singleLineCommentToken: '#', blockComments: false, ignoreCase: true },
|
|
5274
|
+
'GLSL': { ext: "glsl", usePreprocessor: true },
|
|
5275
|
+
'WGSL': { ext: "wgsl", usePreprocessor: true },
|
|
5276
|
+
'JSON': { ext: "json", blockComments: false, singleLineComments: false, icon: "Braces fg-primary" },
|
|
5277
|
+
'XML': { ext: "xml", tags: true, icon: "Rss orange" },
|
|
5278
|
+
'Rust': { ext: "rs", icon: "Rust fg-primary" },
|
|
5279
|
+
'Python': { ext: "py", singleLineCommentToken: '#', icon: "Python munsellblue" },
|
|
5280
|
+
'HTML': { ext: "html", tags: true, singleLineComments: false, blockCommentsTokens: [ '<!--', '-->' ], numbers: false, icon: "Code orange" },
|
|
5281
|
+
'Batch': { ext: "bat", blockComments: false, singleLineCommentToken: '::', ignoreCase: true, icon: "Windows lightblue" },
|
|
5282
|
+
'Markdown': { ext: "md", blockComments: false, singleLineCommentToken: '::', tags: true, numbers: false, icon: "Markdown fg-primary" },
|
|
5283
|
+
'PHP': { ext: "php", icon: "Php blueviolet" },
|
|
5284
|
+
};
|
|
5285
|
+
|
|
5286
|
+
CodeEditor.nativeTypes = {
|
|
5287
|
+
'C++': ['int', 'float', 'double', 'bool', 'long', 'short', 'char', 'wchar_t', 'void']
|
|
5288
|
+
};
|
|
4620
5289
|
|
|
5290
|
+
CodeEditor.declarationKeywords = {
|
|
5291
|
+
'JavaScript': ['var', 'let', 'const', 'this', 'static', 'class'],
|
|
5292
|
+
'C++': [...CodeEditor.nativeTypes["C++"], 'const', 'auto', 'class', 'struct', 'namespace', 'enum', 'extern']
|
|
5293
|
+
};
|
|
5294
|
+
|
|
5295
|
+
CodeEditor.keywords = {
|
|
4621
5296
|
'JavaScript': ['var', 'let', 'const', 'this', 'in', 'of', 'true', 'false', 'new', 'function', 'NaN', 'static', 'class', 'constructor', 'null', 'typeof', 'debugger', 'abstract',
|
|
4622
5297
|
'arguments', 'extends', 'instanceof', 'Infinity'],
|
|
5298
|
+
'TypeScript': ['var', 'let', 'const', 'this', 'in', 'of', 'true', 'false', 'new', 'function', 'class', 'extends', 'instanceof', 'Infinity', 'private', 'public', 'protected', 'interface',
|
|
5299
|
+
'enum', 'type'],
|
|
4623
5300
|
'C': ['int', 'float', 'double', 'long', 'short', 'char', 'const', 'void', 'true', 'false', 'auto', 'struct', 'typedef', 'signed', 'volatile', 'unsigned', 'static', 'extern', 'enum', 'register',
|
|
4624
5301
|
'union'],
|
|
4625
|
-
'C++': [
|
|
5302
|
+
'C++': [...CodeEditor.nativeTypes["C++"], 'const', 'static_cast', 'dynamic_cast', 'new', 'delete', 'true', 'false', 'auto', 'class', 'struct', 'typedef', 'nullptr',
|
|
4626
5303
|
'NULL', 'signed', 'unsigned', 'namespace', 'enum', 'extern', 'union', 'sizeof', 'static', 'private', 'public'],
|
|
4627
5304
|
'CMake': ['cmake_minimum_required', 'set', 'not', 'if', 'endif', 'exists', 'string', 'strequal', 'add_definitions', 'macro', 'endmacro', 'file', 'list', 'source_group', 'add_executable',
|
|
4628
5305
|
'target_include_directories', 'set_target_properties', 'set_property', 'add_compile_options', 'add_link_options', 'include_directories', 'add_library', 'target_link_libraries',
|
|
4629
5306
|
'target_link_options', 'add_subdirectory', 'add_compile_definitions', 'project', 'cache'],
|
|
4630
5307
|
'JSON': ['true', 'false'],
|
|
4631
5308
|
'GLSL': ['true', 'false', 'function', 'int', 'float', 'vec2', 'vec3', 'vec4', 'mat2x2', 'mat3x3', 'mat4x4', 'struct'],
|
|
4632
|
-
'CSS': ['body', 'html', 'canvas', 'div', 'input', 'span', '.'
|
|
4633
|
-
|
|
5309
|
+
'CSS': ['body', 'html', 'canvas', 'div', 'input', 'span', '.', 'table', 'tr', 'td', 'th', 'label', 'video', 'img', 'code', 'button', 'select', 'option', 'svg', 'media', 'all',
|
|
5310
|
+
'i', 'a', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'last-child', 'tbody', 'pre', 'monospace', 'font-face'],
|
|
5311
|
+
'WGSL': ['var', 'let', 'true', 'false', 'fn', 'bool', 'u32', 'i32', 'f16', 'f32', 'vec2', 'vec3', 'vec4', 'vec2f', 'vec3f', 'vec4f', 'mat2x2f', 'mat3x3f', 'mat4x4f', 'array', 'atomic', 'struct',
|
|
4634
5312
|
'sampler', 'sampler_comparison', 'texture_depth_2d', 'texture_depth_2d_array', 'texture_depth_cube', 'texture_depth_cube_array', 'texture_depth_multisampled_2d',
|
|
4635
5313
|
'texture_external', 'texture_1d', 'texture_2d', 'texture_2d_array', 'texture_3d', 'texture_cube', 'texture_cube_array', 'texture_storage_1d', 'texture_storage_2d',
|
|
4636
|
-
'texture_storage_2d_array', 'texture_storage_3d', 'vec2u', 'vec3u', 'vec4u'],
|
|
5314
|
+
'texture_storage_2d_array', 'texture_storage_3d', 'vec2u', 'vec3u', 'vec4u', 'ptr'],
|
|
4637
5315
|
'Rust': ['as', 'const', 'crate', 'enum', 'extern', 'false', 'fn', 'impl', 'in', 'let', 'mod', 'move', 'mut', 'pub', 'ref', 'self', 'Self', 'static', 'struct', 'super', 'trait', 'true',
|
|
4638
5316
|
'type', 'unsafe', 'use', 'where', 'abstract', 'become', 'box', 'final', 'macro', 'override', 'priv', 'typeof', 'unsized', 'virtual'],
|
|
4639
5317
|
'Python': ['False', 'def', 'None', 'True', 'in', 'is', 'and', 'lambda', 'nonlocal', 'not', 'or'],
|
|
4640
|
-
'Batch': ['set', '
|
|
4641
|
-
'DRIVERQUERY', 'print', 'PRINT'],
|
|
5318
|
+
'Batch': ['set', 'echo', 'off', 'del', 'defined', 'setlocal', 'enabledelayedexpansion', 'driverquery', 'print'],
|
|
4642
5319
|
'HTML': ['html', 'meta', 'title', 'link', 'script', 'body', 'DOCTYPE', 'head', 'br', 'i', 'a', 'li', 'img', 'tr', 'td', 'h1', 'h2', 'h3', 'h4', 'h5'],
|
|
4643
5320
|
'Markdown': ['br', 'i', 'a', 'li', 'img', 'table', 'title', 'tr', 'td', 'h1', 'h2', 'h3', 'h4', 'h5'],
|
|
5321
|
+
'PHP': ['const', 'function', 'array', 'new', 'int', 'string', '$this', 'public', 'null', 'private', 'protected', 'implements', 'class', 'use', 'namespace', 'abstract', 'clone', 'final',
|
|
5322
|
+
'enum'],
|
|
4644
5323
|
};
|
|
4645
5324
|
|
|
4646
5325
|
CodeEditor.utils = { // These ones don't have hightlight, used as suggestions to autocomplete only...
|
|
4647
|
-
|
|
4648
5326
|
'JavaScript': ['querySelector', 'body', 'addEventListener', 'removeEventListener', 'remove', 'sort', 'keys', 'filter', 'isNaN', 'parseFloat', 'parseInt', 'EPSILON', 'isFinite',
|
|
4649
5327
|
'bind', 'prototype', 'length', 'assign', 'entries', 'values', 'concat', 'substring', 'substr', 'splice', 'slice', 'buffer', 'appendChild', 'createElement', 'prompt',
|
|
4650
5328
|
'alert'],
|
|
@@ -4656,43 +5334,47 @@ CodeEditor.utils = { // These ones don't have hightlight, used as suggestions to
|
|
|
4656
5334
|
};
|
|
4657
5335
|
|
|
4658
5336
|
CodeEditor.types = {
|
|
4659
|
-
|
|
4660
5337
|
'JavaScript': ['Object', 'String', 'Function', 'Boolean', 'Symbol', 'Error', 'Number', 'TextEncoder', 'TextDecoder', 'Array', 'ArrayBuffer', 'InputEvent', 'MouseEvent',
|
|
4661
5338
|
'Int8Array', 'Int16Array', 'Int32Array', 'Float32Array', 'Float64Array', 'Element'],
|
|
5339
|
+
'TypeScript': ['arguments', 'constructor', 'null', 'typeof', 'debugger', 'abstract', 'Object', 'string', 'String', 'Function', 'Boolean', 'boolean', 'Error', 'Number', 'number', 'TextEncoder',
|
|
5340
|
+
'TextDecoder', 'Array', 'ArrayBuffer', 'InputEvent', 'MouseEvent', 'Int8Array', 'Int16Array', 'Int32Array', 'Float32Array', 'Float64Array', 'Element'],
|
|
4662
5341
|
'Rust': ['u128'],
|
|
4663
5342
|
'Python': ['int', 'type', 'float', 'map', 'list', 'ArithmeticError', 'AssertionError', 'AttributeError', 'Exception', 'EOFError', 'FloatingPointError', 'GeneratorExit',
|
|
4664
5343
|
'ImportError', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'NotImplementedError', 'OSError',
|
|
4665
5344
|
'OverflowError', 'ReferenceError', 'RuntimeError', 'StopIteration', 'SyntaxError', 'TabError', 'SystemError', 'SystemExit', 'TypeError', 'UnboundLocalError',
|
|
4666
5345
|
'UnicodeError', 'UnicodeEncodeError', 'UnicodeDecodeError', 'UnicodeTranslateError', 'ValueError', 'ZeroDivisionError'],
|
|
4667
|
-
'C++': ['uint8_t', 'uint16_t', 'uint32_t']
|
|
5346
|
+
'C++': ['uint8_t', 'uint16_t', 'uint32_t'],
|
|
5347
|
+
'PHP': ['Exception', 'DateTime', 'JsonSerializable'],
|
|
4668
5348
|
};
|
|
4669
5349
|
|
|
4670
5350
|
CodeEditor.builtIn = {
|
|
4671
|
-
|
|
4672
5351
|
'JavaScript': ['document', 'console', 'window', 'navigator', 'performance'],
|
|
4673
5352
|
'CSS': ['*', '!important'],
|
|
4674
5353
|
'C++': ['vector', 'list', 'map'],
|
|
4675
5354
|
'HTML': ['type', 'xmlns', 'PUBLIC', 'http-equiv', 'src', 'style', 'lang', 'href', 'rel', 'content', 'xml', 'alt'], // attributes
|
|
4676
5355
|
'Markdown': ['type', 'src', 'style', 'lang', 'href', 'rel', 'content', 'valign', 'alt'], // attributes
|
|
5356
|
+
'PHP': ['echo', 'print'],
|
|
4677
5357
|
};
|
|
4678
5358
|
|
|
4679
|
-
CodeEditor.
|
|
4680
|
-
|
|
5359
|
+
CodeEditor.statements = {
|
|
4681
5360
|
'JavaScript': ['for', 'if', 'else', 'case', 'switch', 'return', 'while', 'continue', 'break', 'do', 'import', 'from', 'throw', 'async', 'try', 'catch', 'await'],
|
|
5361
|
+
'TypeScript': ['for', 'if', 'else', 'case', 'switch', 'return', 'while', 'continue', 'break', 'do', 'import', 'from', 'throw', 'async', 'try', 'catch', 'await', 'as'],
|
|
4682
5362
|
'CSS': ['@', 'import'],
|
|
4683
5363
|
'C': ['for', 'if', 'else', 'return', 'continue', 'break', 'case', 'switch', 'while', 'using', 'default', 'goto', 'do'],
|
|
4684
5364
|
'C++': ['std', 'for', 'if', 'else', 'return', 'continue', 'break', 'case', 'switch', 'while', 'using', 'glm', 'spdlog', 'default'],
|
|
4685
5365
|
'GLSL': ['for', 'if', 'else', 'return', 'continue', 'break'],
|
|
4686
|
-
'WGSL': ['const','for', 'if', 'else', 'return', 'continue', 'break', 'storage', 'read', 'read_write', 'uniform', 'function', 'workgroup'],
|
|
5366
|
+
'WGSL': ['const','for', 'if', 'else', 'return', 'continue', 'break', 'storage', 'read', 'read_write', 'uniform', 'function', 'workgroup', 'bitcast'],
|
|
4687
5367
|
'Rust': ['break', 'else', 'continue', 'for', 'if', 'loop', 'match', 'return', 'while', 'do', 'yield'],
|
|
4688
5368
|
'Python': ['if', 'raise', 'del', 'import', 'return', 'elif', 'try', 'else', 'while', 'as', 'except', 'with', 'assert', 'finally', 'yield', 'break', 'for', 'class', 'continue',
|
|
4689
5369
|
'global', 'pass', 'from'],
|
|
4690
|
-
'Batch': ['if', 'IF', 'for', 'FOR', 'in', 'IN', 'do', 'DO', 'call', 'CALL', 'goto', 'GOTO', 'exit', 'EXIT']
|
|
5370
|
+
'Batch': ['if', 'IF', 'for', 'FOR', 'in', 'IN', 'do', 'DO', 'call', 'CALL', 'goto', 'GOTO', 'exit', 'EXIT'],
|
|
5371
|
+
'PHP': ['declare', 'enddeclare', 'foreach', 'endforeach', 'if', 'else', 'elseif', 'endif', 'for', 'endfor', 'while', 'endwhile', 'switch', 'case', 'default', 'endswitch', 'return', 'break', 'continue',
|
|
5372
|
+
'try', 'catch', 'die', 'do', 'exit', 'finally'],
|
|
4691
5373
|
};
|
|
4692
5374
|
|
|
4693
5375
|
CodeEditor.symbols = {
|
|
4694
|
-
|
|
4695
5376
|
'JavaScript': ['<', '>', '[', ']', '{', '}', '(', ')', ';', '=', '|', '||', '&', '&&', '?', '??'],
|
|
5377
|
+
'TypeScript': ['<', '>', '[', ']', '{', '}', '(', ')', ';', '=', '|', '||', '&', '&&', '?', '??'],
|
|
4696
5378
|
'C': ['<', '>', '[', ']', '{', '}', '(', ')', ';', '=', '|', '||', '&', '&&', '?', '*', '-', '+'],
|
|
4697
5379
|
'C++': ['<', '>', '[', ']', '{', '}', '(', ')', ';', '=', '|', '||', '&', '&&', '?', '::', '*', '-', '+'],
|
|
4698
5380
|
'CMake': ['{', '}'],
|
|
@@ -4704,7 +5386,22 @@ CodeEditor.symbols = {
|
|
|
4704
5386
|
'Python': ['<', '>', '[', ']', '(', ')', '='],
|
|
4705
5387
|
'Batch': ['[', ']', '(', ')', '%'],
|
|
4706
5388
|
'HTML': ['<', '>', '/'],
|
|
4707
|
-
'XML': ['<', '>', '/']
|
|
5389
|
+
'XML': ['<', '>', '/'],
|
|
5390
|
+
'PHP': ['[', ']', '{', '}', '(', ')'],
|
|
5391
|
+
};
|
|
5392
|
+
|
|
5393
|
+
CodeEditor.REGISTER_LANGUAGE = function( name, options = {}, def, rules )
|
|
5394
|
+
{
|
|
5395
|
+
CodeEditor.languages[ name ] = options;
|
|
5396
|
+
|
|
5397
|
+
if( def?.keywords ) CodeEditor.keywords[ name ] = new Set( def.keywords );
|
|
5398
|
+
if( def?.utils ) CodeEditor.utils[ name ] = new Set( def.utils );
|
|
5399
|
+
if( def?.types ) CodeEditor.types[ name ] = new Set( def.types );
|
|
5400
|
+
if( def?.builtIn ) CodeEditor.builtIn[ name ] = new Set( def.builtIn );
|
|
5401
|
+
if( def?.statements ) CodeEditor.statements[ name ] = new Set( def.statements );
|
|
5402
|
+
if( def?.symbols ) CodeEditor.symbols[ name ] = new Set( def.symbols );
|
|
5403
|
+
|
|
5404
|
+
if( rules ) HighlightRules[ name ] = rules;
|
|
4708
5405
|
};
|
|
4709
5406
|
|
|
4710
5407
|
LX.CodeEditor = CodeEditor;
|