lexgui 0.7.2 → 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 +1461 -917
- package/build/extensions/videoeditor.js +17 -6
- package/build/lexgui.css +17 -34
- package/build/lexgui.js +3 -3
- package/build/lexgui.min.css +1 -1
- package/build/lexgui.min.js +1 -1
- package/build/lexgui.module.js +3 -3
- package/build/lexgui.module.min.js +1 -1
- package/changelog.md +12 -1
- package/examples/code-editor.html +5 -1
- package/examples/video-editor.html +2 -1
- package/examples/video-editor2.html +5 -4
- package/package.json +1 -1
|
@@ -33,6 +33,15 @@ function indexOfFrom( str, reg, from, reverse ) {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
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;
|
|
43
|
+
}
|
|
44
|
+
|
|
36
45
|
class CodeSelection {
|
|
37
46
|
|
|
38
47
|
constructor( editor, cursor, className = "lexcodeselection" ) {
|
|
@@ -133,6 +142,9 @@ class ScrollBar {
|
|
|
133
142
|
static SCROLLBAR_VERTICAL = 1;
|
|
134
143
|
static SCROLLBAR_HORIZONTAL = 2;
|
|
135
144
|
|
|
145
|
+
static SCROLLBAR_VERTICAL_WIDTH = 10;
|
|
146
|
+
static SCROLLBAR_HORIZONTAL_HEIGHT = 10;
|
|
147
|
+
|
|
136
148
|
constructor( editor, type ) {
|
|
137
149
|
|
|
138
150
|
this.editor = editor;
|
|
@@ -206,7 +218,7 @@ const HighlightRules = {
|
|
|
206
218
|
{ test: ctx => ctx.token.substr( 0, ctx.singleLineCommentToken.length ) == ctx.singleLineCommentToken, className: "cm-com" },
|
|
207
219
|
{ test: (ctx, editor) => editor._isKeyword( ctx ), className: "cm-kwd" },
|
|
208
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" },
|
|
209
|
-
{ test: (ctx, editor) => editor._mustHightlightWord( ctx.token, CodeEditor.
|
|
221
|
+
{ test: (ctx, editor) => editor._mustHightlightWord( ctx.token, CodeEditor.statements, ctx.lang ), className: "cm-std" },
|
|
210
222
|
{ test: (ctx, editor) => editor._mustHightlightWord( ctx.token, CodeEditor.symbols, ctx.lang ), className: "cm-sym" },
|
|
211
223
|
{ test: (ctx, editor) => editor._mustHightlightWord( ctx.token, CodeEditor.types, ctx.lang ), className: "cm-typ" },
|
|
212
224
|
{ test: (ctx, editor) => editor._isNumber( ctx.token ) || editor._isNumber( ctx.token.replace(/[px]|[em]|%/g,'') ), className: "cm-dec" },
|
|
@@ -214,21 +226,23 @@ const HighlightRules = {
|
|
|
214
226
|
],
|
|
215
227
|
|
|
216
228
|
javascript: [
|
|
217
|
-
{ test: ctx => (ctx.prev === 'class' && ctx.next === '{')
|
|
229
|
+
{ test: ctx => (ctx.prev === 'class' && ctx.next === '{') , className: "cm-typ" },
|
|
218
230
|
],
|
|
219
231
|
|
|
220
232
|
typescript: [
|
|
221
|
-
{ test: ctx => ctx.scope && (ctx.token !== ',' && ctx.scope == "enum"), className: "cm-enu" },
|
|
233
|
+
{ test: ctx => ctx.scope && (ctx.token !== ',' && ctx.scope.type == "enum"), className: "cm-enu" },
|
|
222
234
|
{ test: ctx => (ctx.prev === ':' && ctx.next !== undefined && isLetter(ctx.token) ) || (ctx.prev === 'interface' && ctx.next === '{') || (ctx.prev === 'enum' && ctx.next === '{'), className: "cm-typ" },
|
|
223
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" },
|
|
224
236
|
{ test: (ctx, editor) => ctx.token !== ',' && editor._enclosedByTokens( ctx.token, ctx.tokenIndex, '<', '>' ), className: "cm-typ" },
|
|
225
237
|
],
|
|
226
238
|
|
|
227
239
|
cpp: [
|
|
228
|
-
{ test: ctx => ctx.scope && (ctx.token !== ',' && ctx.scope == "enum"), className: "cm-enu" },
|
|
240
|
+
{ test: ctx => ctx.scope && (ctx.token !== ',' && ctx.scope.type == "enum"), className: "cm-enu" },
|
|
241
|
+
{ test: ctx => ctx.isEnumValueSymbol( ctx.token ), className: "cm-enu" },
|
|
229
242
|
{ test: ctx => (ctx.prev === 'class' && ctx.next === '{') || (ctx.prev === 'struct' && ctx.next === '{'), className: "cm-typ" },
|
|
230
243
|
{ test: ctx => ctx.prev === "<" && (ctx.next === ">" || ctx.next === "*"), className: "cm-typ" }, // Defining template type in C++
|
|
231
|
-
{ test: ctx => ctx.next === "::" || (ctx.prev === "::" && ctx.next !== "("), className: "cm-typ" } // C++ Class
|
|
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" },
|
|
232
246
|
],
|
|
233
247
|
|
|
234
248
|
wgsl: [
|
|
@@ -240,7 +254,8 @@ const HighlightRules = {
|
|
|
240
254
|
css: [
|
|
241
255
|
{ test: ctx => ( ctx.prev == '.' || ctx.prev == '::' || ( ctx.prev == ':' && ctx.next == '{' ) || ( ctx.token[ 0 ] == '#' && ctx.prev != ':' ) ), className: "cm-kwd" },
|
|
242
256
|
{ test: ctx => ctx.prev === ':' && (ctx.next === ';' || ctx.next === '!important'), className: "cm-str" }, // CSS value
|
|
243
|
-
{ test: ctx => ctx.prev === undefined && ctx.next === ":", className: "cm-typ" }, // CSS attribute
|
|
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
|
|
244
259
|
],
|
|
245
260
|
|
|
246
261
|
batch: [
|
|
@@ -252,7 +267,8 @@ const HighlightRules = {
|
|
|
252
267
|
],
|
|
253
268
|
|
|
254
269
|
php: [
|
|
255
|
-
{ test: ctx =>
|
|
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" },
|
|
256
272
|
],
|
|
257
273
|
|
|
258
274
|
post_common: [
|
|
@@ -285,8 +301,13 @@ class CodeEditor {
|
|
|
285
301
|
static CODE_MIN_FONT_SIZE = 9;
|
|
286
302
|
static CODE_MAX_FONT_SIZE = 22;
|
|
287
303
|
|
|
304
|
+
static LINE_GUTTER_WIDTH = 48;
|
|
288
305
|
static LINE_GUTTER_WIDTH = 48;
|
|
289
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
|
+
|
|
290
311
|
/**
|
|
291
312
|
* @param {*} options
|
|
292
313
|
* name:
|
|
@@ -302,11 +323,17 @@ class CodeEditor {
|
|
|
302
323
|
|
|
303
324
|
CodeEditor.__instances.push( this );
|
|
304
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
|
+
|
|
305
332
|
// File explorer
|
|
306
|
-
if(
|
|
333
|
+
if( this.useFileExplorer )
|
|
307
334
|
{
|
|
308
|
-
|
|
309
|
-
explorerArea.setLimitBox( 180, 20, 512 );
|
|
335
|
+
let [ explorerArea, editorArea ] = area.split({ sizes:[ "15%","85%" ] });
|
|
336
|
+
// explorerArea.setLimitBox( 180, 20, 512 );
|
|
310
337
|
this.explorerArea = explorerArea;
|
|
311
338
|
|
|
312
339
|
let panel = new LX.Panel();
|
|
@@ -357,35 +384,73 @@ class CodeEditor {
|
|
|
357
384
|
explorerArea.attach( panel );
|
|
358
385
|
|
|
359
386
|
// Update area
|
|
360
|
-
area =
|
|
387
|
+
area = editorArea;
|
|
361
388
|
}
|
|
362
389
|
|
|
363
|
-
this.
|
|
390
|
+
this.baseArea = area;
|
|
364
391
|
this.area = new LX.Area( { className: "lexcodeeditor", height: "100%", skipAppend: true } );
|
|
365
392
|
|
|
366
|
-
this.
|
|
367
|
-
|
|
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
|
+
} } );
|
|
368
403
|
|
|
369
|
-
|
|
370
|
-
this.tabs = this.area.addTabs( { onclose: (name) => {
|
|
371
|
-
delete this.openedTabs[ name ];
|
|
372
|
-
if( Object.keys( this.openedTabs ).length < 2 )
|
|
404
|
+
if( !this.disableEdition )
|
|
373
405
|
{
|
|
374
|
-
|
|
375
|
-
|
|
406
|
+
this.tabs.root.addEventListener( 'dblclick', (e) => {
|
|
407
|
+
if( options.allowAddScripts ?? true )
|
|
408
|
+
{
|
|
409
|
+
e.preventDefault();
|
|
410
|
+
this.addTab( "unnamed.js", true );
|
|
411
|
+
}
|
|
412
|
+
} );
|
|
376
413
|
}
|
|
377
|
-
} } );
|
|
378
414
|
|
|
379
|
-
|
|
415
|
+
this.codeArea = this.tabs.area;
|
|
416
|
+
}
|
|
417
|
+
else
|
|
380
418
|
{
|
|
381
|
-
this.
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
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"
|
|
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
|
+
} );
|
|
385
445
|
}
|
|
446
|
+
|
|
447
|
+
new LX.DropdownMenu( this._loadFileButton, dropdownOptions, { side: "top", align: "center" });
|
|
448
|
+
|
|
386
449
|
} );
|
|
387
450
|
}
|
|
388
451
|
|
|
452
|
+
this.codeArea.root.classList.add( 'lexcodearea' );
|
|
453
|
+
|
|
389
454
|
// Full editor
|
|
390
455
|
area.root.classList.add('codebasearea');
|
|
391
456
|
|
|
@@ -401,9 +466,6 @@ class CodeEditor {
|
|
|
401
466
|
attributeFilter: ['class', 'style'],
|
|
402
467
|
});
|
|
403
468
|
|
|
404
|
-
// Code area
|
|
405
|
-
this.tabs.area.root.classList.add( 'codetabsarea' );
|
|
406
|
-
|
|
407
469
|
this.root = this.area.root;
|
|
408
470
|
this.root.tabIndex = -1;
|
|
409
471
|
area.attach( this.root );
|
|
@@ -432,12 +494,12 @@ class CodeEditor {
|
|
|
432
494
|
|
|
433
495
|
this.cursors = document.createElement( 'div' );
|
|
434
496
|
this.cursors.className = 'cursors';
|
|
435
|
-
this.
|
|
497
|
+
this.codeArea.attach( this.cursors );
|
|
436
498
|
|
|
437
499
|
this.searchResultSelections = document.createElement( 'div' );
|
|
438
500
|
this.searchResultSelections.id = 'search-selections';
|
|
439
501
|
this.searchResultSelections.className = 'selections';
|
|
440
|
-
this.
|
|
502
|
+
this.codeArea.attach( this.searchResultSelections );
|
|
441
503
|
|
|
442
504
|
// Store here selections per cursor
|
|
443
505
|
this.selections = {};
|
|
@@ -450,7 +512,7 @@ class CodeEditor {
|
|
|
450
512
|
|
|
451
513
|
// Scroll stuff
|
|
452
514
|
{
|
|
453
|
-
this.codeScroller = this.
|
|
515
|
+
this.codeScroller = this.codeArea.root;
|
|
454
516
|
this.firstLineInViewport = 0;
|
|
455
517
|
this.lineScrollMargin = new LX.vec2( 20, 20 ); // [ mUp, mDown ]
|
|
456
518
|
|
|
@@ -475,12 +537,14 @@ class CodeEditor {
|
|
|
475
537
|
{
|
|
476
538
|
if( this.visibleLinesViewport.y < (this.code.lines.length - 1) )
|
|
477
539
|
{
|
|
478
|
-
const totalLinesInViewport = ((this.codeScroller.offsetHeight
|
|
540
|
+
const totalLinesInViewport = ( ( this.codeScroller.offsetHeight ) / this.lineHeight )|0;
|
|
479
541
|
const scrollDownBoundary =
|
|
480
542
|
( Math.max( this.visibleLinesViewport.y - totalLinesInViewport, 0 ) - 1 ) * this.lineHeight;
|
|
481
543
|
|
|
482
544
|
if( scrollTop >= scrollDownBoundary )
|
|
545
|
+
{
|
|
483
546
|
this.processLines( CodeEditor.UPDATE_VISIBLE_LINES );
|
|
547
|
+
}
|
|
484
548
|
}
|
|
485
549
|
}
|
|
486
550
|
// Scroll up...
|
|
@@ -488,7 +552,9 @@ class CodeEditor {
|
|
|
488
552
|
{
|
|
489
553
|
const scrollUpBoundary = parseInt( this.code.style.top );
|
|
490
554
|
if( scrollTop < scrollUpBoundary )
|
|
555
|
+
{
|
|
491
556
|
this.processLines( CodeEditor.UPDATE_VISIBLE_LINES );
|
|
557
|
+
}
|
|
492
558
|
}
|
|
493
559
|
|
|
494
560
|
lastScrollTopValue = scrollTop;
|
|
@@ -513,34 +579,31 @@ class CodeEditor {
|
|
|
513
579
|
}
|
|
514
580
|
}
|
|
515
581
|
|
|
516
|
-
//
|
|
582
|
+
// Line numbers and scrollbars
|
|
517
583
|
{
|
|
584
|
+
// This is only the container, line numbers are in the same line div
|
|
518
585
|
this.gutter = document.createElement( 'div' );
|
|
519
586
|
this.gutter.className = "lexcodegutter";
|
|
520
587
|
area.attach( this.gutter );
|
|
521
|
-
}
|
|
522
588
|
|
|
523
|
-
|
|
524
|
-
{
|
|
589
|
+
// Add custom vertical scroll bar
|
|
525
590
|
this.vScrollbar = new ScrollBar( this, ScrollBar.SCROLLBAR_VERTICAL );
|
|
526
591
|
area.attach( this.vScrollbar.root );
|
|
527
|
-
}
|
|
528
592
|
|
|
529
|
-
|
|
530
|
-
{
|
|
593
|
+
// Add custom horizontal scroll bar
|
|
531
594
|
this.hScrollbar = new ScrollBar( this, ScrollBar.SCROLLBAR_HORIZONTAL );
|
|
532
595
|
area.attach( this.hScrollbar.root );
|
|
533
596
|
}
|
|
534
597
|
|
|
598
|
+
// Add autocomplete, search boxes (IF edition enabled)
|
|
535
599
|
if( !this.disableEdition )
|
|
536
600
|
{
|
|
537
601
|
// Add autocomplete box
|
|
538
602
|
{
|
|
539
|
-
|
|
603
|
+
const box = document.createElement( 'div' );
|
|
540
604
|
box.className = "autocomplete";
|
|
541
605
|
this.autocomplete = box;
|
|
542
|
-
this.
|
|
543
|
-
|
|
606
|
+
this.codeArea.attach( box );
|
|
544
607
|
this.isAutoCompleteActive = false;
|
|
545
608
|
}
|
|
546
609
|
|
|
@@ -564,7 +627,7 @@ class CodeEditor {
|
|
|
564
627
|
} );
|
|
565
628
|
|
|
566
629
|
this.searchbox = box;
|
|
567
|
-
this.
|
|
630
|
+
this.codeArea.attach( box );
|
|
568
631
|
}
|
|
569
632
|
|
|
570
633
|
// Add search LINE box
|
|
@@ -586,7 +649,7 @@ class CodeEditor {
|
|
|
586
649
|
} );
|
|
587
650
|
|
|
588
651
|
this.searchlinebox = box;
|
|
589
|
-
this.
|
|
652
|
+
this.codeArea.attach( box );
|
|
590
653
|
}
|
|
591
654
|
}
|
|
592
655
|
|
|
@@ -597,7 +660,9 @@ class CodeEditor {
|
|
|
597
660
|
|
|
598
661
|
// Append all childs
|
|
599
662
|
while( this.codeScroller.firstChild )
|
|
663
|
+
{
|
|
600
664
|
this.codeSizer.appendChild( this.codeScroller.firstChild );
|
|
665
|
+
}
|
|
601
666
|
|
|
602
667
|
this.codeScroller.appendChild( this.codeSizer );
|
|
603
668
|
}
|
|
@@ -613,7 +678,6 @@ class CodeEditor {
|
|
|
613
678
|
|
|
614
679
|
// Code
|
|
615
680
|
|
|
616
|
-
this.useAutoComplete = options.autocomplete ?? true;
|
|
617
681
|
this.highlight = options.highlight ?? 'Plain Text';
|
|
618
682
|
this.onsave = options.onsave ?? ((code) => { console.log( code, "save" ) });
|
|
619
683
|
this.onrun = options.onrun ?? ((code) => { this.runScript(code) });
|
|
@@ -625,8 +689,8 @@ class CodeEditor {
|
|
|
625
689
|
this.charWidth = 7; // To update later depending on size..
|
|
626
690
|
this.defaultSingleLineCommentToken = '//';
|
|
627
691
|
this.defaultBlockCommentTokens = [ '/*', '*/' ];
|
|
628
|
-
this.lineScopes = [];
|
|
629
692
|
this._lastTime = null;
|
|
693
|
+
this._tabStorage = {};
|
|
630
694
|
|
|
631
695
|
this.pairKeys = {
|
|
632
696
|
"\"": "\"",
|
|
@@ -656,476 +720,485 @@ class CodeEditor {
|
|
|
656
720
|
|
|
657
721
|
if( !CodeEditor._staticReady )
|
|
658
722
|
{
|
|
659
|
-
for( let lang in CodeEditor.keywords ) CodeEditor.keywords[lang] = CodeEditor.keywords[lang]
|
|
660
|
-
for( let lang in CodeEditor.utils ) CodeEditor.utils[lang] = CodeEditor.utils[lang]
|
|
661
|
-
for( let lang in CodeEditor.types ) CodeEditor.types[lang] = CodeEditor.types[lang]
|
|
662
|
-
for( let lang in CodeEditor.builtIn ) CodeEditor.builtIn[lang] = CodeEditor.builtIn[lang]
|
|
663
|
-
for( let lang in CodeEditor.
|
|
664
|
-
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] );
|
|
665
729
|
|
|
666
730
|
CodeEditor._staticReady = true;
|
|
667
731
|
}
|
|
668
732
|
|
|
669
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
|
+
});
|
|
670
744
|
|
|
671
|
-
|
|
672
|
-
if( this.hideAutoCompleteBox() )
|
|
673
|
-
return;
|
|
674
|
-
if( this.hideSearchBox() )
|
|
675
|
-
return;
|
|
676
|
-
// Remove selections and cursors
|
|
677
|
-
this.endSelection();
|
|
678
|
-
this._removeSecondaryCursors();
|
|
679
|
-
});
|
|
680
|
-
|
|
681
|
-
this.action( 'Backspace', false, ( ln, cursor, e ) => {
|
|
745
|
+
this.action( 'Backspace', false, ( ln, cursor, e ) => {
|
|
682
746
|
|
|
683
|
-
|
|
747
|
+
this._addUndoStep( cursor );
|
|
684
748
|
|
|
685
|
-
|
|
686
|
-
this.deleteSelection( cursor );
|
|
687
|
-
// Remove entire line when selecting with triple click
|
|
688
|
-
if( this._tripleClickSelection )
|
|
749
|
+
if( cursor.selection )
|
|
689
750
|
{
|
|
690
|
-
this.
|
|
691
|
-
|
|
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
|
+
}
|
|
692
758
|
}
|
|
693
|
-
|
|
694
|
-
else {
|
|
695
|
-
|
|
696
|
-
var letter = this.getCharAtPos( cursor, -1 );
|
|
697
|
-
if( letter ) {
|
|
759
|
+
else {
|
|
698
760
|
|
|
699
|
-
var
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
// Delete full word
|
|
703
|
-
if( e.shiftKey )
|
|
761
|
+
var letter = this.getCharAtPos( cursor, -1 );
|
|
762
|
+
if( letter )
|
|
704
763
|
{
|
|
705
|
-
|
|
764
|
+
var deleteFromPosition = cursor.position - 1;
|
|
765
|
+
var numCharsDeleted = 1;
|
|
706
766
|
|
|
707
|
-
|
|
767
|
+
// Delete full word
|
|
768
|
+
if( e.shiftKey )
|
|
708
769
|
{
|
|
709
|
-
|
|
710
|
-
|
|
770
|
+
const [word, from, to] = this.getWordAtPos( cursor, -1 );
|
|
771
|
+
|
|
772
|
+
if( word.length > 1 )
|
|
773
|
+
{
|
|
774
|
+
deleteFromPosition = from;
|
|
775
|
+
numCharsDeleted = word.length;
|
|
776
|
+
}
|
|
711
777
|
}
|
|
712
|
-
}
|
|
713
778
|
|
|
714
|
-
|
|
715
|
-
|
|
779
|
+
this.code.lines[ ln ] = sliceChars( this.code.lines[ ln ], deleteFromPosition, numCharsDeleted );
|
|
780
|
+
this.processLine( ln );
|
|
716
781
|
|
|
717
|
-
|
|
782
|
+
this.cursorToPosition( cursor, deleteFromPosition );
|
|
718
783
|
|
|
719
|
-
|
|
784
|
+
if( this.useAutoComplete )
|
|
785
|
+
{
|
|
786
|
+
this.showAutoCompleteBox( 'foo', cursor );
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
else if( this.code.lines[ ln - 1 ] != undefined )
|
|
720
790
|
{
|
|
721
|
-
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();
|
|
722
798
|
}
|
|
723
799
|
}
|
|
724
|
-
else if( this.code.lines[ ln - 1 ] != undefined ) {
|
|
725
|
-
|
|
726
|
-
this.lineUp( cursor );
|
|
727
|
-
e.cancelShift = true;
|
|
728
|
-
this.actions[ 'End' ].callback( cursor.line, cursor, e );
|
|
729
|
-
// Move line on top
|
|
730
|
-
this.code.lines[ ln - 1 ] += this.code.lines[ ln ];
|
|
731
|
-
this.code.lines.splice( ln, 1 );
|
|
732
|
-
this.processLines();
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
});
|
|
736
|
-
|
|
737
|
-
this.action( 'Delete', false, ( ln, cursor, e ) => {
|
|
738
|
-
|
|
739
|
-
this._addUndoStep( cursor );
|
|
740
800
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
this.actions['Backspace'].callback( ln, cursor, e );
|
|
744
|
-
}
|
|
745
|
-
else
|
|
746
|
-
{
|
|
747
|
-
var letter = this.getCharAtPos( cursor );
|
|
748
|
-
if( letter ) {
|
|
749
|
-
this.code.lines[ ln ] = sliceChars( this.code.lines[ ln ], cursor.position );
|
|
750
|
-
this.processLine( ln );
|
|
751
|
-
}
|
|
752
|
-
else if( this.code.lines[ ln + 1 ] != undefined ) {
|
|
753
|
-
this.code.lines[ ln ] += this.code.lines[ ln + 1 ];
|
|
754
|
-
this.code.lines.splice( ln + 1, 1 );
|
|
755
|
-
this.processLines();
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
});
|
|
801
|
+
this.resizeIfNecessary( cursor, true );
|
|
802
|
+
});
|
|
759
803
|
|
|
760
|
-
|
|
804
|
+
this.action( 'Delete', false, ( ln, cursor, e ) => {
|
|
761
805
|
|
|
762
|
-
if( this._skipTabs )
|
|
763
|
-
{
|
|
764
|
-
this._skipTabs--;
|
|
765
|
-
if( !this._skipTabs )
|
|
766
|
-
delete this._skipTabs;
|
|
767
|
-
}
|
|
768
|
-
else if( this.isAutoCompleteActive )
|
|
769
|
-
{
|
|
770
|
-
this.autoCompleteWord();
|
|
771
|
-
}
|
|
772
|
-
else
|
|
773
|
-
{
|
|
774
806
|
this._addUndoStep( cursor );
|
|
775
807
|
|
|
776
|
-
if(
|
|
808
|
+
if( cursor.selection )
|
|
777
809
|
{
|
|
778
|
-
|
|
810
|
+
// Use 'Backspace' as it's the same callback...
|
|
811
|
+
this.actions['Backspace'].callback( ln, cursor, e );
|
|
779
812
|
}
|
|
780
813
|
else
|
|
781
814
|
{
|
|
782
|
-
|
|
783
|
-
|
|
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
|
+
}
|
|
784
827
|
}
|
|
785
|
-
}
|
|
786
|
-
}, "shiftKey");
|
|
787
|
-
|
|
788
|
-
this.action( 'Home', false, ( ln, cursor, e ) => {
|
|
789
|
-
|
|
790
|
-
let idx = firstNonspaceIndex( this.code.lines[ ln ] );
|
|
791
|
-
|
|
792
|
-
// We already are in the first non space index...
|
|
793
|
-
if( idx == cursor.position ) idx = 0;
|
|
794
|
-
|
|
795
|
-
const prestring = this.code.lines[ ln ].substring( 0, idx );
|
|
796
|
-
let lastX = cursor.position;
|
|
797
|
-
|
|
798
|
-
this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
|
|
799
|
-
if( idx > 0 )
|
|
800
|
-
{
|
|
801
|
-
this.cursorToString( cursor, prestring );
|
|
802
|
-
}
|
|
803
|
-
else
|
|
804
|
-
{
|
|
805
|
-
// No spaces, start from char 0
|
|
806
|
-
idx = 0;
|
|
807
|
-
}
|
|
808
828
|
|
|
809
|
-
|
|
810
|
-
|
|
829
|
+
this.resizeIfNecessary( cursor, true );
|
|
830
|
+
});
|
|
811
831
|
|
|
812
|
-
|
|
813
|
-
{
|
|
814
|
-
// Get last selection range
|
|
815
|
-
if( cursor.selection )
|
|
816
|
-
{
|
|
817
|
-
lastX += cursor.selection.chars;
|
|
818
|
-
}
|
|
832
|
+
this.action( 'Tab', true, ( ln, cursor, e ) => {
|
|
819
833
|
|
|
820
|
-
if(
|
|
834
|
+
if( this._skipTabs )
|
|
821
835
|
{
|
|
822
|
-
this.
|
|
836
|
+
this._skipTabs--;
|
|
837
|
+
if( !this._skipTabs )
|
|
838
|
+
delete this._skipTabs;
|
|
823
839
|
}
|
|
824
|
-
|
|
825
|
-
var string = this.code.lines[ ln ].substring( idx, lastX );
|
|
826
|
-
if( cursor.selection.sameLine() )
|
|
840
|
+
else if( this.isAutoCompleteActive )
|
|
827
841
|
{
|
|
828
|
-
|
|
842
|
+
this.autoCompleteWord();
|
|
829
843
|
}
|
|
830
844
|
else
|
|
831
845
|
{
|
|
832
|
-
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
|
+
}
|
|
833
857
|
}
|
|
834
|
-
}
|
|
835
|
-
this.endSelection();
|
|
836
|
-
});
|
|
858
|
+
}, "shiftKey");
|
|
837
859
|
|
|
838
|
-
|
|
860
|
+
this.action( 'Home', false, ( ln, cursor, e ) => {
|
|
839
861
|
|
|
840
|
-
|
|
862
|
+
let idx = firstNonspaceIndex( this.code.lines[ ln ] );
|
|
841
863
|
|
|
842
|
-
|
|
843
|
-
if(
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
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
|
+
}
|
|
847
875
|
else
|
|
848
876
|
{
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
this._processSelection( cursor, e );
|
|
877
|
+
// No spaces, start from char 0
|
|
878
|
+
idx = 0;
|
|
852
879
|
}
|
|
853
|
-
} else if( !e.keepSelection )
|
|
854
|
-
this.endSelection();
|
|
855
880
|
|
|
856
|
-
|
|
857
|
-
|
|
881
|
+
this.setScrollLeft( 0 );
|
|
882
|
+
this.mergeCursors( ln );
|
|
858
883
|
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
884
|
+
if( e.shiftKey && !e.cancelShift )
|
|
885
|
+
{
|
|
886
|
+
// Get last selection range
|
|
887
|
+
if( cursor.selection )
|
|
888
|
+
{
|
|
889
|
+
lastX += cursor.selection.chars;
|
|
890
|
+
}
|
|
862
891
|
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
892
|
+
if( !cursor.selection )
|
|
893
|
+
{
|
|
894
|
+
this.startSelection( cursor );
|
|
895
|
+
}
|
|
866
896
|
|
|
867
|
-
|
|
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
|
+
});
|
|
868
909
|
|
|
869
|
-
|
|
870
|
-
if( this.isAutoCompleteActive )
|
|
871
|
-
{
|
|
872
|
-
this.autoCompleteWord();
|
|
873
|
-
return;
|
|
874
|
-
}
|
|
910
|
+
this.action( 'End', false, ( ln, cursor, e ) => {
|
|
875
911
|
|
|
876
|
-
|
|
877
|
-
{
|
|
878
|
-
this.onrun( this.getText() );
|
|
879
|
-
return;
|
|
880
|
-
}
|
|
912
|
+
if( ( e.shiftKey || e._shiftKey ) && !e.cancelShift ) {
|
|
881
913
|
|
|
882
|
-
|
|
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();
|
|
883
927
|
|
|
884
|
-
|
|
885
|
-
|
|
928
|
+
this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
|
|
929
|
+
this.cursorToString( cursor, this.code.lines[ ln ] );
|
|
886
930
|
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
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 );
|
|
890
934
|
|
|
891
|
-
|
|
935
|
+
// Merge cursors
|
|
936
|
+
this.mergeCursors( ln );
|
|
937
|
+
});
|
|
892
938
|
|
|
893
|
-
|
|
894
|
-
var spaces = firstNonspaceIndex( this.code.lines[ ln ]);
|
|
895
|
-
var tabs = Math.floor( spaces / this.tabSpaces );
|
|
939
|
+
this.action( 'Enter', true, ( ln, cursor, e ) => {
|
|
896
940
|
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
}
|
|
941
|
+
// Add word
|
|
942
|
+
if( this.isAutoCompleteActive )
|
|
943
|
+
{
|
|
944
|
+
this.autoCompleteWord();
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
904
947
|
|
|
905
|
-
|
|
906
|
-
|
|
948
|
+
if( e.ctrlKey )
|
|
949
|
+
{
|
|
950
|
+
this.onrun( this.getText() );
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
907
953
|
|
|
908
|
-
|
|
954
|
+
this._addUndoStep( cursor, true );
|
|
909
955
|
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
{
|
|
913
|
-
if( e.shiftKey ) {
|
|
914
|
-
if( !cursor.selection )
|
|
915
|
-
this.startSelection( cursor );
|
|
956
|
+
var _c0 = this.getCharAtPos( cursor, -1 );
|
|
957
|
+
var _c1 = this.getCharAtPos( cursor );
|
|
916
958
|
|
|
917
|
-
|
|
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
|
|
918
962
|
|
|
919
|
-
|
|
920
|
-
if( !letter ) {
|
|
921
|
-
this.cursorToPosition( cursor, this.code.lines[ cursor.line ].length );
|
|
922
|
-
}
|
|
963
|
+
this.lineDown( cursor, true );
|
|
923
964
|
|
|
924
|
-
|
|
965
|
+
// Check indentation
|
|
966
|
+
var spaces = firstNonspaceIndex( this.code.lines[ ln ]);
|
|
967
|
+
var tabs = Math.floor( spaces / this.tabSpaces );
|
|
925
968
|
|
|
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 ];
|
|
926
973
|
} else {
|
|
927
|
-
this.
|
|
928
|
-
this.lineUp( cursor );
|
|
929
|
-
// Go to end of line if out of line
|
|
930
|
-
var letter = this.getCharAtPos( cursor );
|
|
931
|
-
if( !letter ) this.actions['End'].callback( cursor.line, cursor, e );
|
|
974
|
+
this._addSpaceTabs( cursor, tabs );
|
|
932
975
|
}
|
|
933
|
-
}
|
|
934
|
-
// Move up autocomplete selection
|
|
935
|
-
else
|
|
936
|
-
{
|
|
937
|
-
this._moveArrowSelectedAutoComplete('up');
|
|
938
|
-
}
|
|
939
|
-
});
|
|
940
976
|
|
|
941
|
-
|
|
977
|
+
this.processLines();
|
|
978
|
+
});
|
|
942
979
|
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
if(
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
}
|
|
980
|
+
this.action( 'ArrowUp', false, ( ln, cursor, e ) => {
|
|
981
|
+
|
|
982
|
+
// Move cursor..
|
|
983
|
+
if( !this.isAutoCompleteActive )
|
|
984
|
+
{
|
|
985
|
+
if( e.shiftKey ) {
|
|
986
|
+
if( !cursor.selection )
|
|
987
|
+
this.startSelection( cursor );
|
|
952
988
|
|
|
953
|
-
|
|
954
|
-
const letter = this.getCharAtPos( cursor );
|
|
989
|
+
this.lineUp( cursor );
|
|
955
990
|
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
991
|
+
var letter = this.getCharAtPos( cursor );
|
|
992
|
+
if( !letter ) {
|
|
993
|
+
this.cursorToPosition( cursor, this.code.lines[ cursor.line ].length );
|
|
994
|
+
}
|
|
960
995
|
|
|
961
|
-
|
|
962
|
-
|
|
996
|
+
this._processSelection( cursor, e, false );
|
|
997
|
+
|
|
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
|
+
}
|
|
963
1005
|
}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
}
|
|
970
|
-
});
|
|
1006
|
+
// Move up autocomplete selection
|
|
1007
|
+
else
|
|
1008
|
+
{
|
|
1009
|
+
this._moveArrowSelectedAutoComplete('up');
|
|
1010
|
+
}
|
|
1011
|
+
});
|
|
971
1012
|
|
|
972
|
-
|
|
1013
|
+
this.action( 'ArrowDown', false, ( ln, cursor, e ) => {
|
|
973
1014
|
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
1015
|
+
// Move cursor..
|
|
1016
|
+
if( !this.isAutoCompleteActive )
|
|
1017
|
+
{
|
|
1018
|
+
if( e.shiftKey ) {
|
|
1019
|
+
if( !cursor.selection )
|
|
1020
|
+
this.startSelection( cursor );
|
|
1021
|
+
} else {
|
|
1022
|
+
this.endSelection();
|
|
1023
|
+
}
|
|
977
1024
|
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
this.actions[ 'Home' ].callback( ln, cursor, e );
|
|
981
|
-
}
|
|
982
|
-
else if( e.ctrlKey ) {
|
|
983
|
-
// Get next word
|
|
984
|
-
const [word, from, to] = this.getWordAtPos( cursor, -1 );
|
|
985
|
-
// If no length, we change line..
|
|
986
|
-
if( !word.length && this.lineUp( cursor, true ) ) {
|
|
987
|
-
const cS = e.cancelShift, kS = e.keepSelection;
|
|
988
|
-
e.cancelShift = true;
|
|
989
|
-
e.keepSelection = true;
|
|
990
|
-
this.actions[ 'End' ].callback( cursor.line, cursor, e );
|
|
991
|
-
e.cancelShift = cS;
|
|
992
|
-
e.keepSelection = kS;
|
|
993
|
-
}
|
|
994
|
-
var diff = Math.max( cursor.position - from, 1 );
|
|
995
|
-
var substr = word.substr( 0, diff );
|
|
1025
|
+
const canGoDown = this.lineDown( cursor );
|
|
1026
|
+
const letter = this.getCharAtPos( cursor );
|
|
996
1027
|
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
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
|
+
}
|
|
1032
|
+
|
|
1033
|
+
if( e.shiftKey ) {
|
|
1034
|
+
this._processSelection( cursor, e );
|
|
1035
|
+
}
|
|
1001
1036
|
}
|
|
1037
|
+
// Move down autocomplete selection
|
|
1002
1038
|
else
|
|
1003
|
-
|
|
1039
|
+
{
|
|
1040
|
+
this._moveArrowSelectedAutoComplete('down');
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
1004
1043
|
|
|
1005
|
-
|
|
1044
|
+
this.action( 'ArrowLeft', false, ( ln, cursor, e ) => {
|
|
1006
1045
|
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
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 );
|
|
1053
|
+
}
|
|
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 );
|
|
1068
|
+
|
|
1069
|
+
// Selections...
|
|
1013
1070
|
if( e.shiftKey ) {
|
|
1014
|
-
if( !cursor.selection )
|
|
1015
|
-
this.
|
|
1016
|
-
this._processSelection( cursor, e, false, CodeEditor.SELECTION_X );
|
|
1071
|
+
if( !cursor.selection )
|
|
1072
|
+
this.startSelection( cursor );
|
|
1017
1073
|
}
|
|
1018
|
-
else
|
|
1019
|
-
|
|
1074
|
+
else
|
|
1075
|
+
this.endSelection();
|
|
1076
|
+
|
|
1077
|
+
this.cursorToString( cursor, substr, true );
|
|
1078
|
+
|
|
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 );
|
|
1020
1087
|
this.cursorToLeft( letter, cursor );
|
|
1021
|
-
|
|
1022
|
-
this.showAutoCompleteBox( 'foo', cursor );
|
|
1088
|
+
this._processSelection( cursor, e, false, CodeEditor.SELECTION_X );
|
|
1023
1089
|
}
|
|
1024
1090
|
else {
|
|
1025
|
-
cursor.selection
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
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
|
+
}
|
|
1030
1103
|
}
|
|
1031
1104
|
}
|
|
1032
|
-
|
|
1033
|
-
else if( cursor.line > 0 ) {
|
|
1105
|
+
else if( cursor.line > 0 ) {
|
|
1034
1106
|
|
|
1035
|
-
|
|
1107
|
+
if( e.shiftKey && !cursor.selection ) this.startSelection( cursor );
|
|
1036
1108
|
|
|
1037
|
-
|
|
1109
|
+
this.lineUp( cursor );
|
|
1038
1110
|
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1111
|
+
e.cancelShift = e.keepSelection = true;
|
|
1112
|
+
this.actions[ 'End' ].callback( cursor.line, cursor, e );
|
|
1113
|
+
delete e.cancelShift; delete e.keepSelection;
|
|
1042
1114
|
|
|
1043
|
-
|
|
1115
|
+
if( e.shiftKey ) this._processSelection( cursor, e, false );
|
|
1116
|
+
}
|
|
1044
1117
|
}
|
|
1045
|
-
}
|
|
1046
|
-
});
|
|
1047
|
-
|
|
1048
|
-
this.action( 'ArrowRight', false, ( ln, cursor, e ) => {
|
|
1118
|
+
});
|
|
1049
1119
|
|
|
1050
|
-
|
|
1051
|
-
if( cursor.line == this.code.lines.length - 1 &&
|
|
1052
|
-
cursor.position == this.code.lines[ cursor.line ].length )
|
|
1053
|
-
return;
|
|
1120
|
+
this.action( 'ArrowRight', false, ( ln, cursor, e ) => {
|
|
1054
1121
|
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
}
|
|
1060
|
-
else if( e.ctrlKey ) // Next word
|
|
1061
|
-
{
|
|
1062
|
-
// Get next word
|
|
1063
|
-
const [ word, from, to ] = this.getWordAtPos( cursor );
|
|
1064
|
-
|
|
1065
|
-
// If no length, we change line..
|
|
1066
|
-
if( !word.length ) this.lineDown( cursor, true );
|
|
1067
|
-
var diff = cursor.position - from;
|
|
1068
|
-
var substr = word.substr( diff );
|
|
1122
|
+
// Nothing to do..
|
|
1123
|
+
if( cursor.line == this.code.lines.length - 1 &&
|
|
1124
|
+
cursor.position == this.code.lines[ cursor.line ].length )
|
|
1125
|
+
return;
|
|
1069
1126
|
|
|
1070
|
-
//
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1127
|
+
if( e.metaKey ) // Apple devices (Command)
|
|
1128
|
+
{
|
|
1129
|
+
e.preventDefault();
|
|
1130
|
+
this.actions[ 'End' ].callback( ln, cursor );
|
|
1074
1131
|
}
|
|
1075
|
-
else
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1132
|
+
else if( e.ctrlKey ) // Next word
|
|
1133
|
+
{
|
|
1134
|
+
// Get next word
|
|
1135
|
+
const [ word, from, to ] = this.getWordAtPos( cursor );
|
|
1079
1136
|
|
|
1080
|
-
|
|
1081
|
-
this.
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
{
|
|
1085
|
-
var letter = this.getCharAtPos( cursor );
|
|
1086
|
-
if( letter ) {
|
|
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 );
|
|
1087
1141
|
|
|
1088
|
-
//
|
|
1089
|
-
if( e.shiftKey )
|
|
1090
|
-
{
|
|
1142
|
+
// Selections...
|
|
1143
|
+
if( e.shiftKey ) {
|
|
1091
1144
|
if( !cursor.selection )
|
|
1092
1145
|
this.startSelection( cursor );
|
|
1093
|
-
|
|
1094
|
-
this.cursorToRight( letter, cursor );
|
|
1095
|
-
this._processSelection( cursor, e, false, CodeEditor.SELECTION_X );
|
|
1096
1146
|
}
|
|
1097
1147
|
else
|
|
1098
|
-
|
|
1099
|
-
|
|
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
|
+
|
|
1100
1166
|
this.cursorToRight( letter, cursor );
|
|
1101
|
-
|
|
1102
|
-
this.showAutoCompleteBox( 'foo', cursor );
|
|
1167
|
+
this._processSelection( cursor, e, false, CodeEditor.SELECTION_X );
|
|
1103
1168
|
}
|
|
1104
1169
|
else
|
|
1105
1170
|
{
|
|
1106
|
-
cursor.selection
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
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
|
+
}
|
|
1111
1184
|
}
|
|
1112
1185
|
}
|
|
1113
|
-
|
|
1114
|
-
else if( this.code.lines[ cursor.line + 1 ] !== undefined ) {
|
|
1186
|
+
else if( this.code.lines[ cursor.line + 1 ] !== undefined ) {
|
|
1115
1187
|
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1188
|
+
if( e.shiftKey ) {
|
|
1189
|
+
if( !cursor.selection ) this.startSelection( cursor );
|
|
1190
|
+
}
|
|
1191
|
+
else this.endSelection();
|
|
1120
1192
|
|
|
1121
|
-
|
|
1193
|
+
this.lineDown( cursor, true );
|
|
1122
1194
|
|
|
1123
|
-
|
|
1195
|
+
if( e.shiftKey ) this._processSelection( cursor, e, false );
|
|
1124
1196
|
|
|
1125
|
-
|
|
1197
|
+
this.hideAutoCompleteBox();
|
|
1198
|
+
}
|
|
1126
1199
|
}
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1129
1202
|
|
|
1130
1203
|
// Default code tab
|
|
1131
1204
|
|
|
@@ -1158,6 +1231,21 @@ class CodeEditor {
|
|
|
1158
1231
|
}
|
|
1159
1232
|
|
|
1160
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
|
+
|
|
1161
1249
|
});
|
|
1162
1250
|
|
|
1163
1251
|
window.editor = this;
|
|
@@ -1201,39 +1289,38 @@ class CodeEditor {
|
|
|
1201
1289
|
onKeyPressed( e ) {
|
|
1202
1290
|
|
|
1203
1291
|
// Toggle visibility of the file explorer
|
|
1204
|
-
if( e.key == 'b' && e.ctrlKey && this.
|
|
1292
|
+
if( e.key == 'b' && e.ctrlKey && this.useFileExplorer )
|
|
1205
1293
|
{
|
|
1206
1294
|
this.explorerArea.root.classList.toggle( "hidden" );
|
|
1207
1295
|
if( this._lastBaseareaWidth )
|
|
1208
1296
|
{
|
|
1209
|
-
this.
|
|
1297
|
+
this.baseArea.root.style.width = this._lastBaseareaWidth;
|
|
1210
1298
|
delete this._lastBaseareaWidth;
|
|
1211
1299
|
|
|
1212
1300
|
} else
|
|
1213
1301
|
{
|
|
1214
|
-
this._lastBaseareaWidth = this.
|
|
1215
|
-
this.
|
|
1302
|
+
this._lastBaseareaWidth = this.baseArea.root.style.width;
|
|
1303
|
+
this.baseArea.root.style.width = "100%";
|
|
1216
1304
|
}
|
|
1217
1305
|
}
|
|
1218
1306
|
}
|
|
1219
1307
|
|
|
1220
1308
|
getText( min ) {
|
|
1221
|
-
|
|
1222
1309
|
return this.code.lines.join( min ? ' ' : '\n' );
|
|
1223
1310
|
}
|
|
1224
1311
|
|
|
1225
1312
|
// This can be used to empty all text...
|
|
1226
1313
|
setText( text = "", lang ) {
|
|
1227
1314
|
|
|
1228
|
-
let
|
|
1229
|
-
this.code.lines = [].concat(
|
|
1315
|
+
let newLines = text.split( '\n' );
|
|
1316
|
+
this.code.lines = [].concat( newLines );
|
|
1230
1317
|
|
|
1231
1318
|
this._removeSecondaryCursors();
|
|
1232
1319
|
|
|
1233
|
-
let cursor = this.
|
|
1234
|
-
let lastLine =
|
|
1320
|
+
let cursor = this.getCurrentCursor( true );
|
|
1321
|
+
let lastLine = newLines.pop();
|
|
1235
1322
|
|
|
1236
|
-
this.cursorToLine( cursor,
|
|
1323
|
+
this.cursorToLine( cursor, newLines.length ); // Already substracted 1
|
|
1237
1324
|
this.cursorToPosition( cursor, lastLine.length );
|
|
1238
1325
|
this.processLines();
|
|
1239
1326
|
|
|
@@ -1247,21 +1334,22 @@ class CodeEditor {
|
|
|
1247
1334
|
|
|
1248
1335
|
let lidx = cursor.line;
|
|
1249
1336
|
|
|
1250
|
-
if( cursor.selection )
|
|
1337
|
+
if( cursor.selection )
|
|
1338
|
+
{
|
|
1251
1339
|
this.deleteSelection( cursor );
|
|
1252
1340
|
lidx = cursor.line;
|
|
1253
1341
|
}
|
|
1254
1342
|
|
|
1255
1343
|
this.endSelection();
|
|
1256
1344
|
|
|
1257
|
-
const
|
|
1345
|
+
const newLines = text.replaceAll( '\r', '' ).split( '\n' );
|
|
1258
1346
|
|
|
1259
1347
|
// Pasting Multiline...
|
|
1260
|
-
if(
|
|
1348
|
+
if( newLines.length != 1 )
|
|
1261
1349
|
{
|
|
1262
|
-
let num_lines =
|
|
1350
|
+
let num_lines = newLines.length;
|
|
1263
1351
|
console.assert( num_lines > 0 );
|
|
1264
|
-
const first_line =
|
|
1352
|
+
const first_line = newLines.shift();
|
|
1265
1353
|
num_lines--;
|
|
1266
1354
|
|
|
1267
1355
|
const remaining = this.code.lines[ lidx ].slice( cursor.position );
|
|
@@ -1278,11 +1366,11 @@ class CodeEditor {
|
|
|
1278
1366
|
|
|
1279
1367
|
let _text = null;
|
|
1280
1368
|
|
|
1281
|
-
for( var i = 0; i <
|
|
1282
|
-
_text =
|
|
1369
|
+
for( var i = 0; i < newLines.length; ++i ) {
|
|
1370
|
+
_text = newLines[ i ];
|
|
1283
1371
|
this.cursorToLine( cursor, cursor.line++, true );
|
|
1284
1372
|
// Add remaining...
|
|
1285
|
-
if( i == (
|
|
1373
|
+
if( i == (newLines.length - 1) )
|
|
1286
1374
|
_text += remaining;
|
|
1287
1375
|
this.code.lines.splice( 1 + lidx + i, 0, _text );
|
|
1288
1376
|
}
|
|
@@ -1296,24 +1384,26 @@ class CodeEditor {
|
|
|
1296
1384
|
{
|
|
1297
1385
|
this.code.lines[ lidx ] = [
|
|
1298
1386
|
this.code.lines[ lidx ].slice( 0, cursor.position ),
|
|
1299
|
-
|
|
1387
|
+
newLines[ 0 ],
|
|
1300
1388
|
this.code.lines[ lidx ].slice( cursor.position )
|
|
1301
1389
|
].join('');
|
|
1302
1390
|
|
|
1303
|
-
this.cursorToPosition( cursor, ( cursor.position +
|
|
1391
|
+
this.cursorToPosition( cursor, ( cursor.position + newLines[ 0 ].length ) );
|
|
1304
1392
|
this.processLine( lidx );
|
|
1305
1393
|
}
|
|
1306
1394
|
|
|
1307
|
-
this.resize( null, ( scrollWidth, scrollHeight ) => {
|
|
1395
|
+
this.resize( CodeEditor.RESIZE_SCROLLBAR_H_V, null, ( scrollWidth, scrollHeight ) => {
|
|
1308
1396
|
var viewportSizeX = ( this.codeScroller.clientWidth + this.getScrollLeft() ) - CodeEditor.LINE_GUTTER_WIDTH; // Gutter offset
|
|
1309
1397
|
if( ( cursor.position * this.charWidth ) >= viewportSizeX )
|
|
1398
|
+
{
|
|
1310
1399
|
this.setScrollLeft( this.code.lines[ lidx ].length * this.charWidth );
|
|
1400
|
+
}
|
|
1311
1401
|
} );
|
|
1312
1402
|
}
|
|
1313
1403
|
|
|
1314
1404
|
loadFile( file, options = {} ) {
|
|
1315
1405
|
|
|
1316
|
-
const
|
|
1406
|
+
const _innerAddTab = ( text, name, title ) => {
|
|
1317
1407
|
|
|
1318
1408
|
// Remove Carriage Return in some cases and sub tabs using spaces
|
|
1319
1409
|
text = text.replaceAll( '\r', '' );
|
|
@@ -1325,7 +1415,7 @@ class CodeEditor {
|
|
|
1325
1415
|
|
|
1326
1416
|
// Add item in the explorer if used
|
|
1327
1417
|
|
|
1328
|
-
if( this.
|
|
1418
|
+
if( this.useFileExplorer || this.skipTabs )
|
|
1329
1419
|
{
|
|
1330
1420
|
this._tabStorage[ name ] = {
|
|
1331
1421
|
lines: lines,
|
|
@@ -1333,8 +1423,16 @@ class CodeEditor {
|
|
|
1333
1423
|
};
|
|
1334
1424
|
|
|
1335
1425
|
const ext = CodeEditor.languages[ options.language ] ?. ext;
|
|
1336
|
-
|
|
1337
|
-
this.
|
|
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
|
+
}
|
|
1338
1436
|
}
|
|
1339
1437
|
else
|
|
1340
1438
|
{
|
|
@@ -1359,7 +1457,7 @@ class CodeEditor {
|
|
|
1359
1457
|
let filename = file;
|
|
1360
1458
|
LX.request({ url: filename, success: text => {
|
|
1361
1459
|
const name = filename.substring(filename.lastIndexOf( '/' ) + 1);
|
|
1362
|
-
|
|
1460
|
+
_innerAddTab( text, name, filename );
|
|
1363
1461
|
} });
|
|
1364
1462
|
}
|
|
1365
1463
|
else // File Blob
|
|
@@ -1368,86 +1466,11 @@ class CodeEditor {
|
|
|
1368
1466
|
fr.readAsText( file );
|
|
1369
1467
|
fr.onload = e => {
|
|
1370
1468
|
const text = e.currentTarget.result;
|
|
1371
|
-
|
|
1469
|
+
_innerAddTab( text, file.name );
|
|
1372
1470
|
};
|
|
1373
1471
|
}
|
|
1374
1472
|
}
|
|
1375
1473
|
|
|
1376
|
-
_addCursor( line = 0, position = 0, force, isMain = false ) {
|
|
1377
|
-
|
|
1378
|
-
// If cursor in that position exists, remove it instead..
|
|
1379
|
-
const exists = Array.from( this.cursors.children ).find( v => v.position == position && v.line == line );
|
|
1380
|
-
if( exists && !force )
|
|
1381
|
-
{
|
|
1382
|
-
if( !exists.isMain )
|
|
1383
|
-
exists.remove();
|
|
1384
|
-
|
|
1385
|
-
return;
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
|
-
let cursor = document.createElement( 'div' );
|
|
1389
|
-
cursor.name = "cursor" + this.cursors.childElementCount;
|
|
1390
|
-
cursor.className = "cursor";
|
|
1391
|
-
cursor.innerHTML = " ";
|
|
1392
|
-
cursor.isMain = isMain;
|
|
1393
|
-
cursor._left = position * this.charWidth;
|
|
1394
|
-
cursor.style.left = "calc( " + cursor._left + "px + " + this.xPadding + " )";
|
|
1395
|
-
cursor._top = line * this.lineHeight;
|
|
1396
|
-
cursor.style.top = cursor._top + "px";
|
|
1397
|
-
cursor._position = position;
|
|
1398
|
-
cursor._line = line;
|
|
1399
|
-
cursor.print = (function() { console.log( this._line, this._position ) }).bind( cursor );
|
|
1400
|
-
cursor.isLast = (function() { return this.cursors.lastChild == cursor; }).bind( this );
|
|
1401
|
-
|
|
1402
|
-
Object.defineProperty( cursor, 'line', {
|
|
1403
|
-
get: (v) => { return cursor._line },
|
|
1404
|
-
set: (v) => {
|
|
1405
|
-
cursor._line = v;
|
|
1406
|
-
if( cursor.isMain ) this._setActiveLine( v );
|
|
1407
|
-
}
|
|
1408
|
-
} );
|
|
1409
|
-
|
|
1410
|
-
Object.defineProperty( cursor, 'position', {
|
|
1411
|
-
get: (v) => { return cursor._position },
|
|
1412
|
-
set: (v) => {
|
|
1413
|
-
cursor._position = v;
|
|
1414
|
-
if( cursor.isMain )
|
|
1415
|
-
{
|
|
1416
|
-
const activeLine = this.state.activeLine;
|
|
1417
|
-
this._updateDataInfoPanel( "@cursor-data", `Ln ${ activeLine + 1 }, Col ${ v + 1 }` );
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
} );
|
|
1421
|
-
|
|
1422
|
-
this.cursors.appendChild( cursor );
|
|
1423
|
-
|
|
1424
|
-
return cursor;
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1427
|
-
_getCurrentCursor( removeOthers ) {
|
|
1428
|
-
|
|
1429
|
-
if( removeOthers )
|
|
1430
|
-
{
|
|
1431
|
-
this._removeSecondaryCursors();
|
|
1432
|
-
}
|
|
1433
|
-
|
|
1434
|
-
return this.cursors.children[ 0 ];
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
|
-
_removeSecondaryCursors() {
|
|
1438
|
-
|
|
1439
|
-
while( this.cursors.childElementCount > 1 )
|
|
1440
|
-
this.cursors.lastChild.remove();
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
_logCursors() {
|
|
1444
|
-
|
|
1445
|
-
for( let cursor of this.cursors.children )
|
|
1446
|
-
{
|
|
1447
|
-
cursor.print();
|
|
1448
|
-
}
|
|
1449
|
-
}
|
|
1450
|
-
|
|
1451
1474
|
_addUndoStep( cursor, force, deleteRedo = true ) {
|
|
1452
1475
|
|
|
1453
1476
|
// Only the mainc cursor stores undo steps
|
|
@@ -1459,12 +1482,18 @@ class CodeEditor {
|
|
|
1459
1482
|
|
|
1460
1483
|
if( !force )
|
|
1461
1484
|
{
|
|
1462
|
-
if( !this._lastTime )
|
|
1485
|
+
if( !this._lastTime )
|
|
1486
|
+
{
|
|
1463
1487
|
this._lastTime = current;
|
|
1464
|
-
}
|
|
1465
|
-
|
|
1488
|
+
}
|
|
1489
|
+
else
|
|
1490
|
+
{
|
|
1491
|
+
if( ( current - this._lastTime ) > 2000 )
|
|
1492
|
+
{
|
|
1466
1493
|
this._lastTime = null;
|
|
1467
|
-
}
|
|
1494
|
+
}
|
|
1495
|
+
else
|
|
1496
|
+
{
|
|
1468
1497
|
// If time not enough, reset timer
|
|
1469
1498
|
this._lastTime = current;
|
|
1470
1499
|
return;
|
|
@@ -1518,7 +1547,9 @@ class CodeEditor {
|
|
|
1518
1547
|
|
|
1519
1548
|
// Only the mainc cursor stores redo steps
|
|
1520
1549
|
if( !cursor.isMain )
|
|
1550
|
+
{
|
|
1521
1551
|
return;
|
|
1552
|
+
}
|
|
1522
1553
|
|
|
1523
1554
|
this.code.redoSteps.push( {
|
|
1524
1555
|
lines: LX.deepCopy( this.code.lines ),
|
|
@@ -1548,7 +1579,9 @@ class CodeEditor {
|
|
|
1548
1579
|
|
|
1549
1580
|
// Generate new if needed
|
|
1550
1581
|
if( !currentCursor )
|
|
1582
|
+
{
|
|
1551
1583
|
currentCursor = this._addCursor();
|
|
1584
|
+
}
|
|
1552
1585
|
|
|
1553
1586
|
this.restoreCursor( currentCursor, step.cursors[ i ] );
|
|
1554
1587
|
}
|
|
@@ -1571,6 +1604,7 @@ class CodeEditor {
|
|
|
1571
1604
|
const icon = this._getFileIcon( null, ext );
|
|
1572
1605
|
|
|
1573
1606
|
// Update tab icon
|
|
1607
|
+
if( !this.skipTabs )
|
|
1574
1608
|
{
|
|
1575
1609
|
const tab = this.tabs.tabDOMs[ this.code.tabName ];
|
|
1576
1610
|
tab.firstChild.remove();
|
|
@@ -1589,7 +1623,7 @@ class CodeEditor {
|
|
|
1589
1623
|
}
|
|
1590
1624
|
|
|
1591
1625
|
// Update explorer icon
|
|
1592
|
-
if( this.
|
|
1626
|
+
if( this.useFileExplorer )
|
|
1593
1627
|
{
|
|
1594
1628
|
const item = this.explorer.innerTree.data.children.filter( (v) => v.id === this.code.tabName )[ 0 ];
|
|
1595
1629
|
console.assert( item != undefined );
|
|
@@ -1631,144 +1665,148 @@ class CodeEditor {
|
|
|
1631
1665
|
|
|
1632
1666
|
_createStatusPanel() {
|
|
1633
1667
|
|
|
1634
|
-
if(
|
|
1668
|
+
if( this.skipInfo )
|
|
1635
1669
|
{
|
|
1636
|
-
|
|
1670
|
+
return;
|
|
1671
|
+
}
|
|
1637
1672
|
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
leftStatusPanel.addButton( null, "ZoomInButton", this._increaseFontSize.bind( this ), { icon: "ZoomIn", width: "32px", title: "Zoom In", tooltip: true } );
|
|
1643
|
-
leftStatusPanel.endLine( "justify-start" );
|
|
1644
|
-
panel.attach( leftStatusPanel.root );
|
|
1645
|
-
|
|
1646
|
-
let rightStatusPanel = new LX.Panel( { height: "auto" } );
|
|
1647
|
-
rightStatusPanel.sameLine();
|
|
1648
|
-
rightStatusPanel.addLabel( this.code.title, { id: "EditorFilenameStatusComponent", fit: true, signal: "@tab-name" });
|
|
1649
|
-
rightStatusPanel.addButton( null, "Ln 1, Col 1", this.showSearchLineBox.bind( this ), { id: "EditorSelectionStatusComponent", fit: true, signal: "@cursor-data" });
|
|
1650
|
-
rightStatusPanel.addButton( null, "Spaces: " + this.tabSpaces, ( value, event ) => {
|
|
1651
|
-
LX.addContextMenu( "Spaces", event, m => {
|
|
1652
|
-
const options = [ 2, 4, 8 ];
|
|
1653
|
-
for( const n of options )
|
|
1654
|
-
m.add( n, (v) => {
|
|
1655
|
-
this.tabSpaces = v;
|
|
1656
|
-
this.processLines();
|
|
1657
|
-
this._updateDataInfoPanel( "@tab-spaces", "Spaces: " + this.tabSpaces );
|
|
1658
|
-
} );
|
|
1659
|
-
});
|
|
1660
|
-
}, { id: "EditorIndentationStatusComponent", nameWidth: "15%", signal: "@tab-spaces" });
|
|
1661
|
-
rightStatusPanel.addButton( "<b>{ }</b>", this.highlight, ( value, event ) => {
|
|
1662
|
-
LX.addContextMenu( "Language", event, m => {
|
|
1663
|
-
for( const lang of Object.keys( CodeEditor.languages ) )
|
|
1664
|
-
{
|
|
1665
|
-
m.add( lang, v => {
|
|
1666
|
-
this._changeLanguage( v, null, true )
|
|
1667
|
-
} );
|
|
1668
|
-
}
|
|
1669
|
-
});
|
|
1670
|
-
}, { id: "EditorLanguageStatusComponent", nameWidth: "15%", signal: "@highlight" });
|
|
1671
|
-
rightStatusPanel.endLine( "justify-end" );
|
|
1672
|
-
panel.attach( rightStatusPanel.root );
|
|
1673
|
-
|
|
1674
|
-
const itemVisibilityMap = {
|
|
1675
|
-
"Font Size Zoom": true,
|
|
1676
|
-
"Editor Filename": true,
|
|
1677
|
-
"Editor Selection": true,
|
|
1678
|
-
"Editor Indentation": true,
|
|
1679
|
-
"Editor Language": true,
|
|
1680
|
-
};
|
|
1673
|
+
let panel = new LX.Panel({ className: "lexcodetabinfo flex flex-row", height: "auto" });
|
|
1674
|
+
|
|
1675
|
+
let leftStatusPanel = new LX.Panel( { id: "FontSizeZoomStatusComponent", height: "auto" } );
|
|
1676
|
+
leftStatusPanel.sameLine();
|
|
1681
1677
|
|
|
1682
|
-
|
|
1678
|
+
if( this.skipTabs )
|
|
1679
|
+
{
|
|
1680
|
+
leftStatusPanel.addButton( null, "ZoomOutButton", this._decreaseFontSize.bind( this ), { icon: "ZoomOut", width: "32px", title: "Zoom Out", tooltip: true } );
|
|
1681
|
+
}
|
|
1683
1682
|
|
|
1684
|
-
|
|
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 ) )
|
|
1685
1707
|
{
|
|
1686
|
-
|
|
1708
|
+
m.add( lang, v => {
|
|
1709
|
+
this._changeLanguage( v, null, true )
|
|
1710
|
+
} );
|
|
1687
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
|
+
};
|
|
1688
1724
|
|
|
1689
|
-
|
|
1690
|
-
const item = {
|
|
1691
|
-
name: itemName,
|
|
1692
|
-
icon: "Check",
|
|
1693
|
-
callback: () => {
|
|
1694
|
-
itemVisibilityMap[ itemName ] = !itemVisibilityMap[ itemName ];
|
|
1695
|
-
const b = panel.root.querySelector( `#${ itemName.replaceAll( " ", "" ) }StatusComponent` );
|
|
1696
|
-
console.assert( b, `${ itemName } has no status button!` );
|
|
1697
|
-
b.classList.toggle( "hidden", !itemVisibilityMap[ itemName ] );
|
|
1698
|
-
}
|
|
1699
|
-
}
|
|
1700
|
-
if( !itemVisibilityMap[ itemName ] ) delete item.icon;
|
|
1701
|
-
return item;
|
|
1702
|
-
} );
|
|
1703
|
-
new LX.DropdownMenu( e.target, menuOptions, { side: "top", align: "start" });
|
|
1704
|
-
} );
|
|
1725
|
+
panel.root.addEventListener( "contextmenu", (e) => {
|
|
1705
1726
|
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
LX.doAsync( () => {
|
|
1727
|
+
if( e.target && ( e.target.classList.contains( "lexpanel" ) || e.target.classList.contains( "lexinlinecomponents" ) ) )
|
|
1728
|
+
{
|
|
1729
|
+
return;
|
|
1730
|
+
}
|
|
1711
1731
|
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
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
|
+
} );
|
|
1718
1748
|
|
|
1719
|
-
|
|
1720
|
-
}
|
|
1749
|
+
return panel;
|
|
1721
1750
|
}
|
|
1722
1751
|
|
|
1723
|
-
_getFileIcon( name, extension ) {
|
|
1752
|
+
_getFileIcon( name, extension, lang ) {
|
|
1724
1753
|
|
|
1725
1754
|
const isNewTabButton = name ? ( name === '+' ) : false;
|
|
1726
|
-
|
|
1727
|
-
if( !extension )
|
|
1755
|
+
if( isNewTabButton )
|
|
1728
1756
|
{
|
|
1729
|
-
|
|
1757
|
+
return;
|
|
1730
1758
|
}
|
|
1731
|
-
else
|
|
1732
|
-
{
|
|
1733
|
-
const possibleExtensions = [].concat( extension );
|
|
1734
1759
|
|
|
1735
|
-
|
|
1760
|
+
if( !lang )
|
|
1761
|
+
{
|
|
1762
|
+
if( !extension )
|
|
1736
1763
|
{
|
|
1737
|
-
|
|
1738
|
-
|
|
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 );
|
|
1739
1774
|
|
|
1740
|
-
|
|
1775
|
+
if( idx > -1)
|
|
1776
|
+
{
|
|
1777
|
+
extension = possibleExtensions[ idx ];
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
else
|
|
1741
1781
|
{
|
|
1742
|
-
extension = possibleExtensions[
|
|
1782
|
+
extension = possibleExtensions[ 0 ];
|
|
1743
1783
|
}
|
|
1744
1784
|
}
|
|
1745
|
-
|
|
1785
|
+
|
|
1786
|
+
for( const [ l, lData ] of Object.entries( CodeEditor.languages ) )
|
|
1746
1787
|
{
|
|
1747
|
-
|
|
1788
|
+
const extensions = [].concat( lData.ext );
|
|
1789
|
+
if( extensions.includes( extension ) )
|
|
1790
|
+
{
|
|
1791
|
+
lang = l;
|
|
1792
|
+
break;
|
|
1793
|
+
}
|
|
1748
1794
|
}
|
|
1795
|
+
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
const iconPlusClasses = CodeEditor.languages[ lang ]?.icon;
|
|
1799
|
+
if( iconPlusClasses )
|
|
1800
|
+
{
|
|
1801
|
+
return iconPlusClasses[ extension ] ?? iconPlusClasses;
|
|
1749
1802
|
}
|
|
1750
1803
|
|
|
1751
|
-
return
|
|
1752
|
-
extension == "css" ? "Hash dodgerblue" :
|
|
1753
|
-
extension == "xml" ? "Rss orange" :
|
|
1754
|
-
extension == "bat" ? "Windows lightblue" :
|
|
1755
|
-
extension == "json" ? "Braces fg-primary" :
|
|
1756
|
-
extension == "js" ? "Js goldenrod" :
|
|
1757
|
-
extension == "ts" ? "Ts pipelineblue" :
|
|
1758
|
-
extension == "py" ? "Python munsellblue" :
|
|
1759
|
-
extension == "rs" ? "Rust fg-primary" :
|
|
1760
|
-
extension == "md" ? "Markdown fg-primary" :
|
|
1761
|
-
extension == "cpp" ? "CPlusPlus pictonblue" :
|
|
1762
|
-
extension == "hpp" ? "CPlusPlus heliotrope" :
|
|
1763
|
-
extension == "c" ? "C pictonblue" :
|
|
1764
|
-
extension == "h" ? "C heliotrope" :
|
|
1765
|
-
extension == "php" ? "Php blueviolet" :
|
|
1766
|
-
!isNewTabButton ? "AlignLeft gray" : undefined;
|
|
1804
|
+
return "AlignLeft gray";
|
|
1767
1805
|
}
|
|
1768
1806
|
|
|
1769
1807
|
_onNewTab( e ) {
|
|
1770
1808
|
|
|
1771
|
-
this.processFocus(false);
|
|
1809
|
+
this.processFocus( false );
|
|
1772
1810
|
|
|
1773
1811
|
LX.addContextMenu( null, e, m => {
|
|
1774
1812
|
m.add( "Create", this.addTab.bind( this, "unnamed.js", true, "", { language: "JavaScript" } ) );
|
|
@@ -1791,7 +1829,7 @@ class CodeEditor {
|
|
|
1791
1829
|
|
|
1792
1830
|
this._removeSecondaryCursors();
|
|
1793
1831
|
|
|
1794
|
-
var cursor = this.
|
|
1832
|
+
var cursor = this.getCurrentCursor( true );
|
|
1795
1833
|
this.saveCursor( cursor, this.code.cursorState );
|
|
1796
1834
|
this.code = this.loadedTabs[ name ];
|
|
1797
1835
|
this.restoreCursor( cursor, this.code.cursorState );
|
|
@@ -1815,12 +1853,12 @@ class CodeEditor {
|
|
|
1815
1853
|
_onContextMenuTab( isNewTabButton, event, name, ) {
|
|
1816
1854
|
|
|
1817
1855
|
if( isNewTabButton )
|
|
1818
|
-
|
|
1856
|
+
return;
|
|
1819
1857
|
|
|
1820
1858
|
LX.addContextMenu( null, event, m => {
|
|
1821
1859
|
m.add( "Close", () => { this.tabs.delete( name ) } );
|
|
1822
|
-
m.add( "" );
|
|
1823
|
-
m.add( "Rename", () => { console.warn( "TODO" )} );
|
|
1860
|
+
// m.add( "" );
|
|
1861
|
+
// m.add( "Rename", () => { console.warn( "TODO" )} );
|
|
1824
1862
|
});
|
|
1825
1863
|
}
|
|
1826
1864
|
|
|
@@ -1847,6 +1885,9 @@ class CodeEditor {
|
|
|
1847
1885
|
code.cursorState = {};
|
|
1848
1886
|
code.undoSteps = [];
|
|
1849
1887
|
code.redoSteps = [];
|
|
1888
|
+
code.lineScopes = [];
|
|
1889
|
+
code.lineSymbols = [];
|
|
1890
|
+
code.symbolsTable = new Map();
|
|
1850
1891
|
code.tabName = name;
|
|
1851
1892
|
code.title = title ?? name;
|
|
1852
1893
|
code.tokens = {};
|
|
@@ -1873,44 +1914,101 @@ class CodeEditor {
|
|
|
1873
1914
|
|
|
1874
1915
|
const tabIcon = this._getFileIcon( name );
|
|
1875
1916
|
|
|
1876
|
-
if( this.
|
|
1877
|
-
{
|
|
1878
|
-
this.addExplorerItem( { id: name, skipVisibility: true, icon: tabIcon } );
|
|
1879
|
-
this.explorer.innerTree.frefresh( name );
|
|
1917
|
+
if( this.useFileExplorer && !isNewTabButton )
|
|
1918
|
+
{
|
|
1919
|
+
this.addExplorerItem( { id: name, skipVisibility: true, icon: tabIcon } );
|
|
1920
|
+
this.explorer.innerTree.frefresh( name );
|
|
1921
|
+
}
|
|
1922
|
+
|
|
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
|
+
}
|
|
1935
|
+
|
|
1936
|
+
// Move into the sizer..
|
|
1937
|
+
this.codeSizer.appendChild( code );
|
|
1938
|
+
|
|
1939
|
+
this.endSelection();
|
|
1940
|
+
|
|
1941
|
+
if( selected )
|
|
1942
|
+
{
|
|
1943
|
+
this.code = code;
|
|
1944
|
+
this.resetCursorPos( CodeEditor.CURSOR_LEFT_TOP );
|
|
1945
|
+
this.processLines();
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
if( options.language )
|
|
1949
|
+
{
|
|
1950
|
+
code.languageOverride = options.language;
|
|
1951
|
+
this._changeLanguage( code.languageOverride );
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
this._updateDataInfoPanel( "@tab-name", name );
|
|
1955
|
+
|
|
1956
|
+
// Bc it could be overrided..
|
|
1957
|
+
return name;
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
loadCode( name ) {
|
|
1961
|
+
|
|
1962
|
+
// Hide all others
|
|
1963
|
+
this.codeSizer.querySelectorAll( ".code" ).forEach( c => c.classList.add( "hidden" ) );
|
|
1964
|
+
|
|
1965
|
+
// Already open...
|
|
1966
|
+
if( this.openedTabs[ name ] )
|
|
1967
|
+
{
|
|
1968
|
+
let code = this.openedTabs[ name ]
|
|
1969
|
+
code.classList.remove( "hidden" );
|
|
1970
|
+
return;
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
let code = this.loadedTabs[ name ]
|
|
1974
|
+
if( !code )
|
|
1975
|
+
{
|
|
1976
|
+
this.addTab( name, true );
|
|
1977
|
+
|
|
1978
|
+
// Unload lines from storage...
|
|
1979
|
+
const tabData = this._tabStorage[ name ];
|
|
1980
|
+
if( tabData )
|
|
1981
|
+
{
|
|
1982
|
+
this.code.lines = tabData.lines;
|
|
1983
|
+
|
|
1984
|
+
if( tabData.options.language )
|
|
1985
|
+
{
|
|
1986
|
+
this._changeLanguage( tabData.options.language, null, true );
|
|
1987
|
+
}
|
|
1988
|
+
else
|
|
1989
|
+
{
|
|
1990
|
+
this._changeLanguageFromExtension( LX.getExtension( name ) );
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1993
|
+
delete this._tabStorage[ name ];
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
return;
|
|
1880
1997
|
}
|
|
1881
1998
|
|
|
1882
|
-
this.
|
|
1883
|
-
selected: selected,
|
|
1884
|
-
fixed: isNewTabButton,
|
|
1885
|
-
title: code.title,
|
|
1886
|
-
icon: tabIcon,
|
|
1887
|
-
onSelect: this._onSelectTab.bind( this, isNewTabButton ),
|
|
1888
|
-
onContextMenu: this._onContextMenuTab.bind( this, isNewTabButton ),
|
|
1889
|
-
allowDelete: true
|
|
1890
|
-
} );
|
|
1999
|
+
this.openedTabs[ name ] = code;
|
|
1891
2000
|
|
|
1892
2001
|
// Move into the sizer..
|
|
1893
2002
|
this.codeSizer.appendChild( code );
|
|
1894
2003
|
|
|
1895
2004
|
this.endSelection();
|
|
1896
2005
|
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
if( options.language )
|
|
1905
|
-
{
|
|
1906
|
-
code.languageOverride = options.language;
|
|
1907
|
-
this._changeLanguage( code.languageOverride );
|
|
1908
|
-
}
|
|
1909
|
-
|
|
1910
|
-
this._updateDataInfoPanel( "@tab-name", name );
|
|
1911
|
-
|
|
1912
|
-
// Bc it could be overrided..
|
|
1913
|
-
return name;
|
|
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 );
|
|
1914
2012
|
}
|
|
1915
2013
|
|
|
1916
2014
|
loadTab( name ) {
|
|
@@ -1994,12 +2092,14 @@ class CodeEditor {
|
|
|
1994
2092
|
}
|
|
1995
2093
|
|
|
1996
2094
|
loadTabFromFile() {
|
|
2095
|
+
|
|
1997
2096
|
const input = document.createElement( 'input' );
|
|
1998
2097
|
input.type = 'file';
|
|
1999
2098
|
document.body.appendChild( input );
|
|
2000
2099
|
input.click();
|
|
2001
2100
|
input.addEventListener('change', e => {
|
|
2002
|
-
if (e.target.files[ 0 ])
|
|
2101
|
+
if (e.target.files[ 0 ])
|
|
2102
|
+
{
|
|
2003
2103
|
this.loadFile( e.target.files[ 0 ] );
|
|
2004
2104
|
}
|
|
2005
2105
|
input.remove();
|
|
@@ -2009,8 +2109,11 @@ class CodeEditor {
|
|
|
2009
2109
|
processFocus( active ) {
|
|
2010
2110
|
|
|
2011
2111
|
if( active )
|
|
2112
|
+
{
|
|
2012
2113
|
this.restartBlink();
|
|
2013
|
-
|
|
2114
|
+
}
|
|
2115
|
+
else
|
|
2116
|
+
{
|
|
2014
2117
|
clearInterval( this.blinker );
|
|
2015
2118
|
this.cursors.classList.remove( 'show' );
|
|
2016
2119
|
}
|
|
@@ -2018,10 +2121,10 @@ class CodeEditor {
|
|
|
2018
2121
|
|
|
2019
2122
|
processMouse( e ) {
|
|
2020
2123
|
|
|
2021
|
-
if( !e.target.classList.contains('code') && !e.target.classList.contains('
|
|
2124
|
+
if( !e.target.classList.contains('code') && !e.target.classList.contains('lexcodearea') ) return;
|
|
2022
2125
|
if( !this.code ) return;
|
|
2023
2126
|
|
|
2024
|
-
var cursor = this.
|
|
2127
|
+
var cursor = this.getCurrentCursor();
|
|
2025
2128
|
var code_rect = this.code.getBoundingClientRect();
|
|
2026
2129
|
var mouse_pos = [(e.clientX - code_rect.x), (e.clientY - code_rect.y)];
|
|
2027
2130
|
|
|
@@ -2116,14 +2219,17 @@ class CodeEditor {
|
|
|
2116
2219
|
|
|
2117
2220
|
_onMouseUp( e ) {
|
|
2118
2221
|
|
|
2119
|
-
if( (LX.getTime() - this.lastMouseDown) < 120 )
|
|
2222
|
+
if( ( LX.getTime() - this.lastMouseDown ) < 120 )
|
|
2223
|
+
{
|
|
2120
2224
|
this.state.selectingText = false;
|
|
2121
2225
|
this.endSelection();
|
|
2122
2226
|
}
|
|
2123
2227
|
|
|
2124
|
-
const cursor = this.
|
|
2228
|
+
const cursor = this.getCurrentCursor();
|
|
2125
2229
|
if( cursor.selection )
|
|
2230
|
+
{
|
|
2126
2231
|
cursor.selection.invertIfNecessary();
|
|
2232
|
+
}
|
|
2127
2233
|
|
|
2128
2234
|
this.state.selectingText = false;
|
|
2129
2235
|
delete this._lastSelectionKeyDir;
|
|
@@ -2131,7 +2237,7 @@ class CodeEditor {
|
|
|
2131
2237
|
|
|
2132
2238
|
processClick( e ) {
|
|
2133
2239
|
|
|
2134
|
-
var cursor = this.
|
|
2240
|
+
var cursor = this.getCurrentCursor();
|
|
2135
2241
|
var code_rect = this.codeScroller.getBoundingClientRect();
|
|
2136
2242
|
var position = [( e.clientX - code_rect.x ) + this.getScrollLeft(), (e.clientY - code_rect.y) + this.getScrollTop()];
|
|
2137
2243
|
var ln = (position[ 1 ] / this.lineHeight)|0;
|
|
@@ -2148,7 +2254,6 @@ class CodeEditor {
|
|
|
2148
2254
|
{
|
|
2149
2255
|
// Make sure we only keep the main cursor..
|
|
2150
2256
|
this._removeSecondaryCursors();
|
|
2151
|
-
|
|
2152
2257
|
this.cursorToLine( cursor, ln, true );
|
|
2153
2258
|
this.cursorToPosition( cursor, string.length );
|
|
2154
2259
|
}
|
|
@@ -2162,38 +2267,45 @@ class CodeEditor {
|
|
|
2162
2267
|
this.hideAutoCompleteBox();
|
|
2163
2268
|
}
|
|
2164
2269
|
|
|
2165
|
-
updateSelections( e,
|
|
2270
|
+
updateSelections( e, keepRange, flags = CodeEditor.SELECTION_X_Y ) {
|
|
2166
2271
|
|
|
2167
2272
|
for( let cursor of this.cursors.children )
|
|
2168
2273
|
{
|
|
2169
2274
|
if( !cursor.selection )
|
|
2275
|
+
{
|
|
2170
2276
|
continue;
|
|
2277
|
+
}
|
|
2171
2278
|
|
|
2172
|
-
this._processSelection( cursor, e,
|
|
2279
|
+
this._processSelection( cursor, e, keepRange, flags );
|
|
2173
2280
|
}
|
|
2174
2281
|
}
|
|
2175
2282
|
|
|
2176
|
-
processSelections( e,
|
|
2283
|
+
processSelections( e, keepRange, flags = CodeEditor.SELECTION_X_Y ) {
|
|
2177
2284
|
|
|
2178
2285
|
for( let cursor of this.cursors.children )
|
|
2179
2286
|
{
|
|
2180
|
-
this._processSelection( cursor, e,
|
|
2287
|
+
this._processSelection( cursor, e, keepRange, flags );
|
|
2181
2288
|
}
|
|
2182
2289
|
}
|
|
2183
2290
|
|
|
2184
|
-
_processSelection( cursor, e,
|
|
2291
|
+
_processSelection( cursor, e, keepRange, flags = CodeEditor.SELECTION_X_Y ) {
|
|
2185
2292
|
|
|
2186
2293
|
const isMouseEvent = e && ( e.constructor == MouseEvent );
|
|
2187
2294
|
|
|
2188
|
-
if( isMouseEvent )
|
|
2295
|
+
if( isMouseEvent )
|
|
2296
|
+
{
|
|
2297
|
+
this.processClick( e );
|
|
2298
|
+
}
|
|
2189
2299
|
|
|
2190
2300
|
if( !cursor.selection )
|
|
2301
|
+
{
|
|
2191
2302
|
this.startSelection( cursor );
|
|
2303
|
+
}
|
|
2192
2304
|
|
|
2193
2305
|
this._hideActiveLine();
|
|
2194
2306
|
|
|
2195
2307
|
// Update selection
|
|
2196
|
-
if( !
|
|
2308
|
+
if( !keepRange )
|
|
2197
2309
|
{
|
|
2198
2310
|
let ccw = true;
|
|
2199
2311
|
|
|
@@ -2260,19 +2372,19 @@ class CodeEditor {
|
|
|
2260
2372
|
}
|
|
2261
2373
|
|
|
2262
2374
|
// Compute new width and selection margins
|
|
2263
|
-
let string;
|
|
2375
|
+
let string = "";
|
|
2264
2376
|
|
|
2265
|
-
if(sId == 0) // First line 2 cases (single line, multiline)
|
|
2377
|
+
if( sId == 0 ) // First line 2 cases (single line, multiline)
|
|
2266
2378
|
{
|
|
2267
2379
|
const reverse = fromX > toX;
|
|
2268
2380
|
if(deltaY == 0) string = !reverse ? this.code.lines[ i ].substring( fromX, toX ) : this.code.lines[ i ].substring(toX, fromX);
|
|
2269
2381
|
else string = this.code.lines[ i ].substr( fromX );
|
|
2270
|
-
const pixels = (reverse && deltaY == 0 ? toX : fromX) * this.charWidth;
|
|
2271
|
-
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 })`;
|
|
2272
2384
|
}
|
|
2273
2385
|
else
|
|
2274
2386
|
{
|
|
2275
|
-
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...
|
|
2276
2388
|
if( isVisible ) domEl.style.left = this.xPadding;
|
|
2277
2389
|
}
|
|
2278
2390
|
|
|
@@ -2281,7 +2393,7 @@ class CodeEditor {
|
|
|
2281
2393
|
|
|
2282
2394
|
if( isVisible )
|
|
2283
2395
|
{
|
|
2284
|
-
domEl.style.width = (stringWidth || 8) + "px";
|
|
2396
|
+
domEl.style.width = ( stringWidth || 8 ) + "px";
|
|
2285
2397
|
domEl._top = i * this.lineHeight;
|
|
2286
2398
|
domEl.style.top = domEl._top + "px";
|
|
2287
2399
|
}
|
|
@@ -2302,7 +2414,7 @@ class CodeEditor {
|
|
|
2302
2414
|
{
|
|
2303
2415
|
// Make sure that the line selection is generated...
|
|
2304
2416
|
domEl = cursorSelections.childNodes[ sId ];
|
|
2305
|
-
if(!domEl)
|
|
2417
|
+
if( !domEl )
|
|
2306
2418
|
{
|
|
2307
2419
|
domEl = document.createElement( 'div' );
|
|
2308
2420
|
domEl.className = "lexcodeselection";
|
|
@@ -2421,7 +2533,7 @@ class CodeEditor {
|
|
|
2421
2533
|
|
|
2422
2534
|
_processGlobalKeys( e, key ) {
|
|
2423
2535
|
|
|
2424
|
-
let cursor = this.
|
|
2536
|
+
let cursor = this.getCurrentCursor();
|
|
2425
2537
|
|
|
2426
2538
|
if( e.ctrlKey || e.metaKey )
|
|
2427
2539
|
{
|
|
@@ -2489,7 +2601,7 @@ class CodeEditor {
|
|
|
2489
2601
|
|
|
2490
2602
|
async _processKeyAtCursor( e, key, cursor ) {
|
|
2491
2603
|
|
|
2492
|
-
const
|
|
2604
|
+
const skipUndo = e.detail.skipUndo ?? false;
|
|
2493
2605
|
|
|
2494
2606
|
// keys with length > 1 are probably special keys
|
|
2495
2607
|
if( key.length > 1 && this.specialKeys.indexOf( key ) == -1 )
|
|
@@ -2579,7 +2691,7 @@ class CodeEditor {
|
|
|
2579
2691
|
|
|
2580
2692
|
// Add undo steps
|
|
2581
2693
|
|
|
2582
|
-
if( !
|
|
2694
|
+
if( !skipUndo && this.code.lines.length )
|
|
2583
2695
|
{
|
|
2584
2696
|
this._addUndoStep( cursor );
|
|
2585
2697
|
}
|
|
@@ -2640,17 +2752,7 @@ class CodeEditor {
|
|
|
2640
2752
|
this.processLine( lidx );
|
|
2641
2753
|
|
|
2642
2754
|
// We are out of the viewport and max length is different? Resize scrollbars...
|
|
2643
|
-
|
|
2644
|
-
const numViewportChars = Math.floor( ( this.codeScroller.clientWidth - CodeEditor.LINE_GUTTER_WIDTH ) / this.charWidth );
|
|
2645
|
-
if( maxLineLength >= numViewportChars && maxLineLength != this._lastMaxLineLength )
|
|
2646
|
-
{
|
|
2647
|
-
this.resize( maxLineLength, () => {
|
|
2648
|
-
if( cursor.position > numViewportChars )
|
|
2649
|
-
{
|
|
2650
|
-
this.setScrollLeft( cursor.position * this.charWidth );
|
|
2651
|
-
}
|
|
2652
|
-
} );
|
|
2653
|
-
}
|
|
2755
|
+
this.resizeIfNecessary( cursor );
|
|
2654
2756
|
|
|
2655
2757
|
// Manage autocomplete
|
|
2656
2758
|
|
|
@@ -2662,6 +2764,8 @@ class CodeEditor {
|
|
|
2662
2764
|
|
|
2663
2765
|
async _pasteContent( cursor ) {
|
|
2664
2766
|
|
|
2767
|
+
const mustDetectLanguage = ( !this.getText().length );
|
|
2768
|
+
|
|
2665
2769
|
let text = await navigator.clipboard.readText();
|
|
2666
2770
|
|
|
2667
2771
|
// Remove any possible tabs (\t) and add spaces
|
|
@@ -2674,9 +2778,19 @@ class CodeEditor {
|
|
|
2674
2778
|
const currentScroll = this.getScrollTop();
|
|
2675
2779
|
const scroll = Math.max( cursor.line * this.lineHeight - this.codeScroller.offsetWidth, 0 );
|
|
2676
2780
|
|
|
2677
|
-
if( currentScroll < scroll )
|
|
2781
|
+
if( currentScroll < scroll )
|
|
2782
|
+
{
|
|
2678
2783
|
this.codeScroller.scrollTo( 0, cursor.line * this.lineHeight );
|
|
2679
2784
|
}
|
|
2785
|
+
|
|
2786
|
+
if( mustDetectLanguage )
|
|
2787
|
+
{
|
|
2788
|
+
const detectedLang = this._detectLanguage( text );
|
|
2789
|
+
if( detectedLang )
|
|
2790
|
+
{
|
|
2791
|
+
this._changeLanguage( detectedLang );
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2680
2794
|
}
|
|
2681
2795
|
|
|
2682
2796
|
async _copyContent( cursor ) {
|
|
@@ -2699,7 +2813,9 @@ class CodeEditor {
|
|
|
2699
2813
|
let index = 0;
|
|
2700
2814
|
|
|
2701
2815
|
for( let i = 0; i <= cursor.selection.fromY; i++ )
|
|
2816
|
+
{
|
|
2702
2817
|
index += ( i == cursor.selection.fromY ? cursor.selection.fromX : this.code.lines[ i ].length );
|
|
2818
|
+
}
|
|
2703
2819
|
|
|
2704
2820
|
index += cursor.selection.fromY * separator.length;
|
|
2705
2821
|
const num_chars = cursor.selection.chars + ( cursor.selection.toY - cursor.selection.fromY ) * separator.length;
|
|
@@ -2775,7 +2891,7 @@ class CodeEditor {
|
|
|
2775
2891
|
|
|
2776
2892
|
if( cursor.selection )
|
|
2777
2893
|
{
|
|
2778
|
-
var cursor = this.
|
|
2894
|
+
var cursor = this.getCurrentCursor();
|
|
2779
2895
|
this._addUndoStep( cursor, true );
|
|
2780
2896
|
|
|
2781
2897
|
const selectedLines = this.code.lines.slice( cursor.selection.fromY, cursor.selection.toY );
|
|
@@ -2834,7 +2950,7 @@ class CodeEditor {
|
|
|
2834
2950
|
|
|
2835
2951
|
if( cursor.selection )
|
|
2836
2952
|
{
|
|
2837
|
-
var cursor = this.
|
|
2953
|
+
var cursor = this.getCurrentCursor();
|
|
2838
2954
|
this._addUndoStep( cursor, true );
|
|
2839
2955
|
|
|
2840
2956
|
for( var i = cursor.selection.fromY; i <= cursor.selection.toY; ++i )
|
|
@@ -2888,7 +3004,6 @@ class CodeEditor {
|
|
|
2888
3004
|
}
|
|
2889
3005
|
|
|
2890
3006
|
_actionMustDelete( cursor, action, e ) {
|
|
2891
|
-
|
|
2892
3007
|
return cursor.selection && action.deleteSelection &&
|
|
2893
3008
|
( action.eventSkipDelete ? !e[ action.eventSkipDelete ] : true );
|
|
2894
3009
|
}
|
|
@@ -2906,13 +3021,11 @@ class CodeEditor {
|
|
|
2906
3021
|
}
|
|
2907
3022
|
|
|
2908
3023
|
toLocalLine( line ) {
|
|
2909
|
-
|
|
2910
3024
|
const d = Math.max( this.firstLineInViewport - this.lineScrollMargin.x, 0 );
|
|
2911
3025
|
return Math.min( Math.max( line - d, 0 ), this.code.lines.length - 1 );
|
|
2912
3026
|
}
|
|
2913
3027
|
|
|
2914
3028
|
getMaxLineLength() {
|
|
2915
|
-
|
|
2916
3029
|
return Math.max(...this.code.lines.map( v => v.length ));
|
|
2917
3030
|
}
|
|
2918
3031
|
|
|
@@ -2928,7 +3041,7 @@ class CodeEditor {
|
|
|
2928
3041
|
const lastScrollTop = this.getScrollTop();
|
|
2929
3042
|
this.firstLineInViewport = ( mode ?? CodeEditor.KEEP_VISIBLE_LINES ) & CodeEditor.UPDATE_VISIBLE_LINES ?
|
|
2930
3043
|
( (lastScrollTop / this.lineHeight)|0 ) : this.firstLineInViewport;
|
|
2931
|
-
const totalLinesInViewport = ((this.codeScroller.offsetHeight
|
|
3044
|
+
const totalLinesInViewport = ( ( this.codeScroller.offsetHeight ) / this.lineHeight )|0;
|
|
2932
3045
|
this.visibleLinesViewport = new LX.vec2(
|
|
2933
3046
|
Math.max( this.firstLineInViewport - this.lineScrollMargin.x, 0 ),
|
|
2934
3047
|
Math.min( this.firstLineInViewport + totalLinesInViewport + this.lineScrollMargin.y, this.code.lines.length )
|
|
@@ -2943,7 +3056,7 @@ class CodeEditor {
|
|
|
2943
3056
|
}
|
|
2944
3057
|
}
|
|
2945
3058
|
|
|
2946
|
-
this._scopeStack = [];
|
|
3059
|
+
this._scopeStack = [ { name: "", type: "global" } ];
|
|
2947
3060
|
|
|
2948
3061
|
// Process visible lines
|
|
2949
3062
|
for( let i = this.visibleLinesViewport.x; i < this.visibleLinesViewport.y; ++i )
|
|
@@ -2965,7 +3078,7 @@ class CodeEditor {
|
|
|
2965
3078
|
this.resize();
|
|
2966
3079
|
}
|
|
2967
3080
|
|
|
2968
|
-
processLine( lineNumber, force ) {
|
|
3081
|
+
processLine( lineNumber, force, skipPropagation ) {
|
|
2969
3082
|
|
|
2970
3083
|
// Check if we are in block comment sections..
|
|
2971
3084
|
if( !force && this._inBlockCommentSection( lineNumber ) )
|
|
@@ -2976,151 +3089,400 @@ class CodeEditor {
|
|
|
2976
3089
|
|
|
2977
3090
|
if( this._scopeStack )
|
|
2978
3091
|
{
|
|
2979
|
-
this.lineScopes[ lineNumber ] = [ ...this._scopeStack ];
|
|
3092
|
+
this.code.lineScopes[ lineNumber ] = [ ...this._scopeStack ];
|
|
2980
3093
|
}
|
|
2981
3094
|
else
|
|
2982
3095
|
{
|
|
2983
|
-
this.
|
|
3096
|
+
this.code.lineScopes[ lineNumber ] = this.code.lineScopes[ lineNumber ] ?? [];
|
|
3097
|
+
this._scopeStack = [ ...this.code.lineScopes[ lineNumber ] ];
|
|
3098
|
+
}
|
|
3099
|
+
|
|
3100
|
+
const lang = CodeEditor.languages[ this.highlight ];
|
|
3101
|
+
const localLineNum = this.toLocalLine( lineNumber );
|
|
3102
|
+
const lineString = this.code.lines[ lineNumber ];
|
|
3103
|
+
|
|
3104
|
+
// multi-line strings not supported by now
|
|
3105
|
+
delete this._buildingString;
|
|
3106
|
+
delete this._pendingString;
|
|
3107
|
+
delete this._markdownHeader;
|
|
3108
|
+
|
|
3109
|
+
// Single line
|
|
3110
|
+
if( !force )
|
|
3111
|
+
{
|
|
3112
|
+
LX.deleteElement( this.code.childNodes[ localLineNum ] );
|
|
3113
|
+
this.code.insertChildAtIndex( document.createElement( 'pre' ), localLineNum );
|
|
3114
|
+
}
|
|
3115
|
+
|
|
3116
|
+
// Early out check for no highlighting languages
|
|
3117
|
+
if( this.highlight == 'Plain Text' )
|
|
3118
|
+
{
|
|
3119
|
+
const plainTextHtml = lineString.replaceAll('<', '<').replaceAll('>', '>');
|
|
3120
|
+
return this._updateLine( force, lineNumber, plainTextHtml, skipPropagation );
|
|
3121
|
+
}
|
|
3122
|
+
|
|
3123
|
+
this._currentLineNumber = lineNumber;
|
|
3124
|
+
this._currentLineString = lineString;
|
|
3125
|
+
|
|
3126
|
+
const tokensToEvaluate = this._getTokensFromLine( lineString );
|
|
3127
|
+
if( !tokensToEvaluate.length )
|
|
3128
|
+
{
|
|
3129
|
+
return this._updateLine( force, lineNumber, "", skipPropagation );
|
|
3130
|
+
}
|
|
3131
|
+
|
|
3132
|
+
let lineInnerHtml = "";
|
|
3133
|
+
let pushedScope = false;
|
|
3134
|
+
|
|
3135
|
+
// Process all tokens
|
|
3136
|
+
for( let i = 0; i < tokensToEvaluate.length; ++i )
|
|
3137
|
+
{
|
|
3138
|
+
let it = i - 1;
|
|
3139
|
+
let prev = tokensToEvaluate[ it ];
|
|
3140
|
+
while( prev == ' ' )
|
|
3141
|
+
{
|
|
3142
|
+
it--;
|
|
3143
|
+
prev = tokensToEvaluate[ it ];
|
|
3144
|
+
}
|
|
3145
|
+
|
|
3146
|
+
it = i + 1;
|
|
3147
|
+
let next = tokensToEvaluate[ it ];
|
|
3148
|
+
while( next == ' ' || next == '"' )
|
|
3149
|
+
{
|
|
3150
|
+
it++;
|
|
3151
|
+
next = tokensToEvaluate[ it ];
|
|
3152
|
+
}
|
|
3153
|
+
|
|
3154
|
+
const token = tokensToEvaluate[ i ];
|
|
3155
|
+
|
|
3156
|
+
if( lang.blockComments ?? true )
|
|
3157
|
+
{
|
|
3158
|
+
const blockCommentsToken = ( lang.blockCommentsTokens ?? this.defaultBlockCommentTokens )[ 0 ];
|
|
3159
|
+
if( token.substr( 0, blockCommentsToken.length ) == blockCommentsToken )
|
|
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();
|
|
3169
|
+
}
|
|
3170
|
+
|
|
3171
|
+
lineInnerHtml += this._evaluateToken( {
|
|
3172
|
+
token: token,
|
|
3173
|
+
prev: prev,
|
|
3174
|
+
prevWithSpaces: tokensToEvaluate[ i - 1 ],
|
|
3175
|
+
next: next,
|
|
3176
|
+
nextWithSpaces: tokensToEvaluate[ i + 1 ],
|
|
3177
|
+
tokenIndex: i,
|
|
3178
|
+
isFirstToken: (i == 0),
|
|
3179
|
+
isLastToken: (i == tokensToEvaluate.length - 1),
|
|
3180
|
+
tokens: tokensToEvaluate
|
|
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 [];
|
|
2984
3360
|
}
|
|
2985
3361
|
|
|
2986
|
-
const
|
|
2987
|
-
|
|
2988
|
-
|
|
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
|
+
];
|
|
2989
3371
|
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
_processNextLineIfNecessary();
|
|
2994
|
-
this.code.childNodes[ localLineNum ].innerHTML = gutterLineHtml + html;
|
|
2995
|
-
this._setActiveLine( lineNumber );
|
|
2996
|
-
this._clearTmpVariables();
|
|
2997
|
-
}
|
|
2998
|
-
else // Update all lines at once
|
|
3372
|
+
{
|
|
3373
|
+
const nativeTypes = CodeEditor.nativeTypes[ this.highlight ];
|
|
3374
|
+
if( nativeTypes )
|
|
2999
3375
|
{
|
|
3000
|
-
|
|
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' ] );
|
|
3001
3379
|
}
|
|
3002
|
-
}
|
|
3003
|
-
|
|
3004
|
-
const _processNextLineIfNecessary = () => {
|
|
3005
|
-
// Only update scope stack if something changed when editing a single line
|
|
3006
|
-
if( this._scopeStack.toString() == this.lineScopes[ lineNumber + 1 ]?.toString() )
|
|
3007
|
-
return;
|
|
3008
3380
|
|
|
3009
|
-
|
|
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
|
+
}
|
|
3010
3385
|
|
|
3011
|
-
|
|
3386
|
+
for( let [ regex, kind ] of topLevelRegexes )
|
|
3387
|
+
{
|
|
3388
|
+
const m = text.match( regex );
|
|
3389
|
+
if( m )
|
|
3012
3390
|
{
|
|
3013
|
-
|
|
3391
|
+
symbols.push( { name: m[ 1 ], kind, scope: scopeName, line: lineNumber } );
|
|
3392
|
+
break;
|
|
3014
3393
|
}
|
|
3015
3394
|
}
|
|
3016
3395
|
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
let lineString = this.code.lines[ lineNumber ];
|
|
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
|
+
];
|
|
3023
3400
|
|
|
3024
|
-
|
|
3025
|
-
if( !force )
|
|
3401
|
+
for( let [ regex, kind ] of usageRegexes )
|
|
3026
3402
|
{
|
|
3027
|
-
|
|
3028
|
-
|
|
3403
|
+
const m = text.match( regex );
|
|
3404
|
+
if( m )
|
|
3405
|
+
{
|
|
3406
|
+
symbols.push( { name: m[ 1 ], kind, scope: scopeName, line: lineNumber } );
|
|
3407
|
+
}
|
|
3029
3408
|
}
|
|
3030
3409
|
|
|
3031
|
-
//
|
|
3032
|
-
if(
|
|
3410
|
+
// Stop after matches for top-level declarations and usage symbols
|
|
3411
|
+
if( symbols.length )
|
|
3033
3412
|
{
|
|
3034
|
-
|
|
3035
|
-
return _updateLine( plainTextHtml );
|
|
3413
|
+
return symbols;
|
|
3036
3414
|
}
|
|
3037
3415
|
|
|
3038
|
-
|
|
3039
|
-
this._currentLineString = lineString;
|
|
3416
|
+
const nonWhiteSpaceTokens = tokens.filter( t => t.trim().length );
|
|
3040
3417
|
|
|
3041
|
-
|
|
3042
|
-
if( !tokensToEvaluate.length )
|
|
3418
|
+
for( let i = 0; i < nonWhiteSpaceTokens.length; i++ )
|
|
3043
3419
|
{
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
let lineInnerHtml = "";
|
|
3420
|
+
const prev = nonWhiteSpaceTokens[ i - 1 ];
|
|
3421
|
+
const token = nonWhiteSpaceTokens[ i ];
|
|
3422
|
+
const next = nonWhiteSpaceTokens[ i + 1 ];
|
|
3048
3423
|
|
|
3049
|
-
|
|
3050
|
-
for( let i = 0; i < tokensToEvaluate.length; ++i )
|
|
3051
|
-
{
|
|
3052
|
-
let it = i - 1;
|
|
3053
|
-
let prev = tokensToEvaluate[ it ];
|
|
3054
|
-
while( prev == ' ' )
|
|
3424
|
+
if( scopeType.startsWith("class") )
|
|
3055
3425
|
{
|
|
3056
|
-
|
|
3057
|
-
|
|
3426
|
+
if( next === "(" && /^[a-zA-Z_]\w*$/.test( token ) && prev === undefined )
|
|
3427
|
+
{
|
|
3428
|
+
symbols.push( { name: token, kind: "method", scope: scopeName, line: lineNumber } );
|
|
3429
|
+
}
|
|
3058
3430
|
}
|
|
3059
|
-
|
|
3060
|
-
it = i + 1;
|
|
3061
|
-
let next = tokensToEvaluate[ it ];
|
|
3062
|
-
while( next == ' ' || next == '"' )
|
|
3431
|
+
else if( scopeType.startsWith("enum") )
|
|
3063
3432
|
{
|
|
3064
|
-
|
|
3065
|
-
|
|
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
|
+
}
|
|
3066
3437
|
}
|
|
3438
|
+
}
|
|
3067
3439
|
|
|
3068
|
-
|
|
3440
|
+
return symbols;
|
|
3441
|
+
}
|
|
3069
3442
|
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
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 ) {
|
|
3076
3449
|
|
|
3077
|
-
|
|
3078
|
-
|
|
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 )
|
|
3079
3458
|
{
|
|
3080
|
-
|
|
3459
|
+
continue;
|
|
3081
3460
|
}
|
|
3082
3461
|
|
|
3083
|
-
|
|
3084
|
-
token: token,
|
|
3085
|
-
prev: prev,
|
|
3086
|
-
prevWithSpaces: tokensToEvaluate[ i - 1 ],
|
|
3087
|
-
next: next,
|
|
3088
|
-
nextWithSpaces: tokensToEvaluate[ i + 1 ],
|
|
3089
|
-
tokenIndex: i,
|
|
3090
|
-
isFirstToken: (i == 0),
|
|
3091
|
-
isLastToken: (i == tokensToEvaluate.length - 1),
|
|
3092
|
-
tokens: tokensToEvaluate
|
|
3093
|
-
} );
|
|
3462
|
+
array = array.filter( s => s.line !== lineNumber );
|
|
3094
3463
|
|
|
3095
|
-
|
|
3096
|
-
if( token === "{" )
|
|
3464
|
+
if( array.length )
|
|
3097
3465
|
{
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
].reverse().filter( v => v.length && v != ' ' );
|
|
3104
|
-
|
|
3105
|
-
let scopeType = contextTokens[ 1 ]; // This is the type of scope (function, class, enum, etc)
|
|
3106
|
-
|
|
3107
|
-
// Special cases
|
|
3108
|
-
if( scopeType === ":" ) // enum type specification
|
|
3109
|
-
{
|
|
3110
|
-
scopeType = contextTokens[ 3 ];
|
|
3111
|
-
}
|
|
3112
|
-
|
|
3113
|
-
if( !this._mustHightlightWord( scopeType, CodeEditor.keywords, lang ) )
|
|
3114
|
-
{
|
|
3115
|
-
// If the scope type is not a keyword, use an empty string
|
|
3116
|
-
scopeType = "";
|
|
3117
|
-
}
|
|
3118
|
-
|
|
3119
|
-
this._scopeStack.push( scopeType );
|
|
3466
|
+
this.code.symbolsTable.set( sym.name, array );
|
|
3467
|
+
}
|
|
3468
|
+
else
|
|
3469
|
+
{
|
|
3470
|
+
this.code.symbolsTable.delete( sym.name );
|
|
3120
3471
|
}
|
|
3121
3472
|
}
|
|
3122
3473
|
|
|
3123
|
-
|
|
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;
|
|
3124
3486
|
}
|
|
3125
3487
|
|
|
3126
3488
|
_lineHasComment( lineString ) {
|
|
@@ -3236,18 +3598,33 @@ class CodeEditor {
|
|
|
3236
3598
|
}
|
|
3237
3599
|
else if( this.highlight == 'PHP' )
|
|
3238
3600
|
{
|
|
3239
|
-
|
|
3240
|
-
|
|
3601
|
+
let offset = 0;
|
|
3602
|
+
let dollarIdx = tokens.indexOf( '$' );
|
|
3603
|
+
|
|
3604
|
+
while( dollarIdx > -1 )
|
|
3241
3605
|
{
|
|
3242
|
-
|
|
3243
|
-
|
|
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;
|
|
3244
3621
|
}
|
|
3245
3622
|
}
|
|
3246
3623
|
|
|
3247
3624
|
return tokens;
|
|
3248
3625
|
}
|
|
3249
3626
|
|
|
3250
|
-
_mustHightlightWord( token,
|
|
3627
|
+
_mustHightlightWord( token, wordCategory, lang ) {
|
|
3251
3628
|
|
|
3252
3629
|
if( !lang )
|
|
3253
3630
|
{
|
|
@@ -3261,12 +3638,12 @@ class CodeEditor {
|
|
|
3261
3638
|
t = t.toLowerCase();
|
|
3262
3639
|
}
|
|
3263
3640
|
|
|
3264
|
-
return
|
|
3641
|
+
return wordCategory[ this.highlight ] && wordCategory[ this.highlight ].has( t );
|
|
3265
3642
|
}
|
|
3266
3643
|
|
|
3267
3644
|
_getTokenHighlighting( ctx, highlight ) {
|
|
3268
3645
|
|
|
3269
|
-
const rules = [ ...HighlightRules.common, ...( HighlightRules[highlight] || [] ), ...HighlightRules.post_common ];
|
|
3646
|
+
const rules = [ ...HighlightRules.common, ...( HighlightRules[ highlight ] || [] ), ...HighlightRules.post_common ];
|
|
3270
3647
|
|
|
3271
3648
|
for( const rule of rules )
|
|
3272
3649
|
{
|
|
@@ -3289,7 +3666,8 @@ class CodeEditor {
|
|
|
3289
3666
|
|
|
3290
3667
|
const lang = CodeEditor.languages[ this.highlight ],
|
|
3291
3668
|
highlight = this.highlight.replace( /\s/g, '' ).replaceAll( "+", "p" ).toLowerCase(),
|
|
3292
|
-
customStringKeys = Object.assign( {}, this.stringKeys )
|
|
3669
|
+
customStringKeys = Object.assign( {}, this.stringKeys ),
|
|
3670
|
+
lineNumber = this._currentLineNumber;
|
|
3293
3671
|
|
|
3294
3672
|
var usePreviousTokenToCheckString = false;
|
|
3295
3673
|
|
|
@@ -3338,7 +3716,14 @@ class CodeEditor {
|
|
|
3338
3716
|
ctxData.inString = this._buildingString;
|
|
3339
3717
|
ctxData.singleLineCommentToken = lang.singleLineCommentToken ?? this.defaultSingleLineCommentToken;
|
|
3340
3718
|
ctxData.lang = lang;
|
|
3341
|
-
ctxData.scope = this._scopeStack
|
|
3719
|
+
ctxData.scope = this._scopeStack.at( -1 );
|
|
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";
|
|
3342
3727
|
|
|
3343
3728
|
// Get highlighting class based on language common and specific rules
|
|
3344
3729
|
let tokenClass = this._getTokenHighlighting( ctxData, highlight );
|
|
@@ -3347,7 +3732,7 @@ class CodeEditor {
|
|
|
3347
3732
|
if( ( lang.blockComments ?? true ) && this._buildingBlockComment != undefined
|
|
3348
3733
|
&& token.substr( 0, blockCommentsTokens[ 1 ].length ) == blockCommentsTokens[ 1 ] )
|
|
3349
3734
|
{
|
|
3350
|
-
this._blockCommentCache.push( new LX.vec2( this._buildingBlockComment,
|
|
3735
|
+
this._blockCommentCache.push( new LX.vec2( this._buildingBlockComment, lineNumber ) );
|
|
3351
3736
|
delete this._buildingBlockComment;
|
|
3352
3737
|
}
|
|
3353
3738
|
|
|
@@ -3392,7 +3777,6 @@ class CodeEditor {
|
|
|
3392
3777
|
}
|
|
3393
3778
|
|
|
3394
3779
|
_getCurrentString() {
|
|
3395
|
-
|
|
3396
3780
|
const chars = this._pendingString;
|
|
3397
3781
|
delete this._pendingString;
|
|
3398
3782
|
return chars;
|
|
@@ -3521,7 +3905,7 @@ class CodeEditor {
|
|
|
3521
3905
|
this.cursorToPosition( cursor, cursor.selection.toX + 1 );
|
|
3522
3906
|
|
|
3523
3907
|
// Change next key?
|
|
3524
|
-
switch(key)
|
|
3908
|
+
switch( key )
|
|
3525
3909
|
{
|
|
3526
3910
|
case "'":
|
|
3527
3911
|
case "\"":
|
|
@@ -3549,6 +3933,46 @@ class CodeEditor {
|
|
|
3549
3933
|
return true;
|
|
3550
3934
|
}
|
|
3551
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
|
+
|
|
3552
3976
|
lineUp( cursor, resetLeft ) {
|
|
3553
3977
|
|
|
3554
3978
|
if( this.code.lines[ cursor.line - 1 ] == undefined )
|
|
@@ -3664,7 +4088,7 @@ class CodeEditor {
|
|
|
3664
4088
|
// Use main cursor
|
|
3665
4089
|
this._removeSecondaryCursors();
|
|
3666
4090
|
|
|
3667
|
-
var cursor = this.
|
|
4091
|
+
var cursor = this.getCurrentCursor();
|
|
3668
4092
|
this.resetCursorPos( CodeEditor.CURSOR_LEFT_TOP, cursor );
|
|
3669
4093
|
|
|
3670
4094
|
this.startSelection( cursor );
|
|
@@ -3754,11 +4178,8 @@ class CodeEditor {
|
|
|
3754
4178
|
}
|
|
3755
4179
|
|
|
3756
4180
|
const currentScrollTop = this.getScrollTop();
|
|
3757
|
-
const
|
|
3758
|
-
const
|
|
3759
|
-
const scrollerHeight = this.codeScroller.offsetHeight;
|
|
3760
|
-
|
|
3761
|
-
var lastLine = ( ( scrollerHeight - tabsHeight - statusPanelHeight + currentScrollTop ) / this.lineHeight )|0;
|
|
4181
|
+
const scrollerHeight = this.codeScroller.offsetHeight - this._fullVerticalOffset;
|
|
4182
|
+
const lastLine = ( ( scrollerHeight + currentScrollTop ) / this.lineHeight )|0;
|
|
3762
4183
|
if( cursor.line >= lastLine )
|
|
3763
4184
|
{
|
|
3764
4185
|
this.setScrollTop( currentScrollTop + this.lineHeight );
|
|
@@ -3814,6 +4235,16 @@ class CodeEditor {
|
|
|
3814
4235
|
return cursors;
|
|
3815
4236
|
}
|
|
3816
4237
|
|
|
4238
|
+
getCurrentCursor( removeOthers ) {
|
|
4239
|
+
|
|
4240
|
+
if( removeOthers )
|
|
4241
|
+
{
|
|
4242
|
+
this._removeSecondaryCursors();
|
|
4243
|
+
}
|
|
4244
|
+
|
|
4245
|
+
return this.cursors.children[ 0 ];
|
|
4246
|
+
}
|
|
4247
|
+
|
|
3817
4248
|
relocateCursors() {
|
|
3818
4249
|
|
|
3819
4250
|
for( let cursor of this.cursors.children )
|
|
@@ -3863,7 +4294,7 @@ class CodeEditor {
|
|
|
3863
4294
|
|
|
3864
4295
|
resetCursorPos( flag, cursor ) {
|
|
3865
4296
|
|
|
3866
|
-
cursor = cursor ?? this.
|
|
4297
|
+
cursor = cursor ?? this.getCurrentCursor();
|
|
3867
4298
|
|
|
3868
4299
|
if( flag & CodeEditor.CURSOR_LEFT )
|
|
3869
4300
|
{
|
|
@@ -3880,6 +4311,71 @@ class CodeEditor {
|
|
|
3880
4311
|
}
|
|
3881
4312
|
}
|
|
3882
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
|
+
|
|
3883
4379
|
_addSpaceTabs( cursor, n ) {
|
|
3884
4380
|
|
|
3885
4381
|
for( var i = 0; i < n; ++i )
|
|
@@ -3890,9 +4386,10 @@ class CodeEditor {
|
|
|
3890
4386
|
|
|
3891
4387
|
_addSpaces( n ) {
|
|
3892
4388
|
|
|
3893
|
-
for( var i = 0; i < n; ++i )
|
|
4389
|
+
for( var i = 0; i < n; ++i )
|
|
4390
|
+
{
|
|
3894
4391
|
this.root.dispatchEvent( new CustomEvent( 'keydown', { 'detail': {
|
|
3895
|
-
|
|
4392
|
+
skipUndo: true,
|
|
3896
4393
|
key: ' ',
|
|
3897
4394
|
targetCursor: this._lastProcessedCursorIndex
|
|
3898
4395
|
}}));
|
|
@@ -3913,7 +4410,8 @@ class CodeEditor {
|
|
|
3913
4410
|
}
|
|
3914
4411
|
|
|
3915
4412
|
// Only tabs/spaces in the line...
|
|
3916
|
-
if( lineStart == -1 )
|
|
4413
|
+
if( lineStart == -1 )
|
|
4414
|
+
{
|
|
3917
4415
|
lineStart = this.code.lines[ lidx ].length;
|
|
3918
4416
|
}
|
|
3919
4417
|
|
|
@@ -3964,24 +4462,28 @@ class CodeEditor {
|
|
|
3964
4462
|
}, 20 );
|
|
3965
4463
|
}
|
|
3966
4464
|
|
|
3967
|
-
resize( pMaxLength, onResize ) {
|
|
4465
|
+
resize( flag = CodeEditor.RESIZE_SCROLLBAR_H_V, pMaxLength, onResize ) {
|
|
3968
4466
|
|
|
3969
4467
|
setTimeout( () => {
|
|
3970
4468
|
|
|
3971
|
-
|
|
3972
|
-
const maxLineLength = pMaxLength ?? this.getMaxLineLength();
|
|
3973
|
-
const scrollWidth = maxLineLength * this.charWidth + CodeEditor.LINE_GUTTER_WIDTH;
|
|
3974
|
-
|
|
3975
|
-
const tabsHeight = this.tabs.root.getBoundingClientRect().height;
|
|
3976
|
-
const statusPanelHeight = this.skipInfo ? 0 : this.statusPanel.root.getBoundingClientRect().height;
|
|
3977
|
-
const scrollHeight = this.code.lines.length * this.lineHeight;
|
|
4469
|
+
let scrollWidth, scrollHeight;
|
|
3978
4470
|
|
|
3979
|
-
|
|
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
|
+
}
|
|
3980
4479
|
|
|
3981
|
-
|
|
3982
|
-
|
|
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
|
+
}
|
|
3983
4485
|
|
|
3984
|
-
this.resizeScrollBars();
|
|
4486
|
+
this.resizeScrollBars( flag );
|
|
3985
4487
|
|
|
3986
4488
|
if( onResize )
|
|
3987
4489
|
{
|
|
@@ -3991,37 +4493,52 @@ class CodeEditor {
|
|
|
3991
4493
|
}, 10 );
|
|
3992
4494
|
}
|
|
3993
4495
|
|
|
3994
|
-
|
|
4496
|
+
resizeIfNecessary( cursor, force ) {
|
|
3995
4497
|
|
|
3996
|
-
const
|
|
3997
|
-
|
|
3998
|
-
if(
|
|
3999
|
-
{
|
|
4000
|
-
this.codeScroller.classList.remove( 'with-vscrollbar' );
|
|
4001
|
-
this.vScrollbar.root.classList.add( 'scrollbar-unused' );
|
|
4002
|
-
}
|
|
4003
|
-
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 ) )
|
|
4004
4501
|
{
|
|
4005
|
-
this.
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4502
|
+
this.resize( CodeEditor.RESIZE_SCROLLBAR_H, maxLineLength, () => {
|
|
4503
|
+
if( cursor.position > numViewportChars )
|
|
4504
|
+
{
|
|
4505
|
+
this.setScrollLeft( cursor.position * this.charWidth );
|
|
4506
|
+
}
|
|
4507
|
+
} );
|
|
4009
4508
|
}
|
|
4509
|
+
}
|
|
4010
4510
|
|
|
4011
|
-
|
|
4012
|
-
const maxLineLength = this._lastMaxLineLength;
|
|
4511
|
+
resizeScrollBars( flag = CodeEditor.RESIZE_SCROLLBAR_H_V ) {
|
|
4013
4512
|
|
|
4014
|
-
if(
|
|
4513
|
+
if( flag & CodeEditor.RESIZE_SCROLLBAR_V )
|
|
4015
4514
|
{
|
|
4016
|
-
this.codeScroller.
|
|
4017
|
-
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)`;
|
|
4018
4526
|
}
|
|
4019
|
-
|
|
4527
|
+
|
|
4528
|
+
if( flag & CodeEditor.RESIZE_SCROLLBAR_H )
|
|
4020
4529
|
{
|
|
4021
|
-
this.codeScroller.
|
|
4022
|
-
this.
|
|
4023
|
-
|
|
4024
|
-
|
|
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)`;
|
|
4025
4542
|
}
|
|
4026
4543
|
}
|
|
4027
4544
|
|
|
@@ -4095,7 +4612,6 @@ class CodeEditor {
|
|
|
4095
4612
|
}
|
|
4096
4613
|
|
|
4097
4614
|
getCharAtPos( cursor, offset = 0 ) {
|
|
4098
|
-
|
|
4099
4615
|
return this.code.lines[ cursor.line ][ cursor.position + offset ];
|
|
4100
4616
|
}
|
|
4101
4617
|
|
|
@@ -4164,7 +4680,6 @@ class CodeEditor {
|
|
|
4164
4680
|
}
|
|
4165
4681
|
|
|
4166
4682
|
measureString( str ) {
|
|
4167
|
-
|
|
4168
4683
|
return str.length * this.charWidth;
|
|
4169
4684
|
}
|
|
4170
4685
|
|
|
@@ -4217,46 +4732,64 @@ class CodeEditor {
|
|
|
4217
4732
|
showAutoCompleteBox( key, cursor ) {
|
|
4218
4733
|
|
|
4219
4734
|
if( !cursor.isMain )
|
|
4735
|
+
{
|
|
4220
4736
|
return;
|
|
4737
|
+
}
|
|
4221
4738
|
|
|
4222
|
-
const [word, start, end] = this.getWordAtPos( cursor, -1 );
|
|
4223
|
-
if( key == ' ' || !word.length )
|
|
4739
|
+
const [ word, start, end ] = this.getWordAtPos( cursor, -1 );
|
|
4740
|
+
if( key == ' ' || !word.length )
|
|
4741
|
+
{
|
|
4224
4742
|
this.hideAutoCompleteBox();
|
|
4225
4743
|
return;
|
|
4226
4744
|
}
|
|
4227
4745
|
|
|
4228
4746
|
this.autocomplete.innerHTML = ""; // Clear all suggestions
|
|
4229
4747
|
|
|
4230
|
-
let suggestions = [];
|
|
4231
|
-
|
|
4232
4748
|
// Add language special keys...
|
|
4233
|
-
suggestions =
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
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
|
+
];
|
|
4240
4756
|
|
|
4241
|
-
|
|
4242
|
-
|
|
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
|
+
}
|
|
4771
|
+
|
|
4772
|
+
const prefix = word.toLowerCase();
|
|
4243
4773
|
|
|
4244
4774
|
// Remove 1/2 char words and duplicates...
|
|
4245
|
-
suggestions =
|
|
4775
|
+
suggestions = Array.from( new Set( suggestions )).filter( s => s.length > 2 && s.toLowerCase().includes( prefix ) );
|
|
4246
4776
|
|
|
4247
4777
|
// Order...
|
|
4248
|
-
|
|
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 ) );
|
|
4249
4786
|
|
|
4250
4787
|
for( let s of suggestions )
|
|
4251
4788
|
{
|
|
4252
|
-
if( !s.toLowerCase().includes( word.toLowerCase() ) )
|
|
4253
|
-
continue;
|
|
4254
|
-
|
|
4255
4789
|
var pre = document.createElement( 'pre' );
|
|
4256
4790
|
this.autocomplete.appendChild( pre );
|
|
4257
4791
|
|
|
4258
4792
|
var icon = "Type";
|
|
4259
|
-
|
|
4260
4793
|
if( this._mustHightlightWord( s, CodeEditor.utils ) )
|
|
4261
4794
|
icon = "Box";
|
|
4262
4795
|
else if( this._mustHightlightWord( s, CodeEditor.types ) )
|
|
@@ -4292,11 +4825,11 @@ class CodeEditor {
|
|
|
4292
4825
|
}
|
|
4293
4826
|
|
|
4294
4827
|
// Select always first option
|
|
4295
|
-
this.autocomplete.firstChild.classList.add('selected');
|
|
4828
|
+
this.autocomplete.firstChild.classList.add( 'selected' );
|
|
4296
4829
|
|
|
4297
4830
|
// Show box
|
|
4298
|
-
this.autocomplete.classList.toggle('show', true);
|
|
4299
|
-
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 ) );
|
|
4300
4833
|
this.autocomplete.style.left = (cursor._left + CodeEditor.LINE_GUTTER_WIDTH - this.getScrollLeft()) + "px";
|
|
4301
4834
|
this.autocomplete.style.top = (cursor._top + 28 + this.lineHeight - this.getScrollTop()) + "px";
|
|
4302
4835
|
|
|
@@ -4408,7 +4941,7 @@ class CodeEditor {
|
|
|
4408
4941
|
}
|
|
4409
4942
|
else
|
|
4410
4943
|
{
|
|
4411
|
-
const cursor = this.
|
|
4944
|
+
const cursor = this.getCurrentCursor();
|
|
4412
4945
|
|
|
4413
4946
|
if( cursor.selection )
|
|
4414
4947
|
{
|
|
@@ -4452,7 +4985,7 @@ class CodeEditor {
|
|
|
4452
4985
|
return;
|
|
4453
4986
|
}
|
|
4454
4987
|
|
|
4455
|
-
let cursor = this.
|
|
4988
|
+
let cursor = this.getCurrentCursor();
|
|
4456
4989
|
let cursorData = new LX.vec2( cursor.position, cursor.line );
|
|
4457
4990
|
let line = null;
|
|
4458
4991
|
let char = -1;
|
|
@@ -4599,7 +5132,7 @@ class CodeEditor {
|
|
|
4599
5132
|
this.codeScroller.scrollTo( 0, Math.max( line - 15 ) * this.lineHeight );
|
|
4600
5133
|
|
|
4601
5134
|
// Select line ?
|
|
4602
|
-
var cursor = this.
|
|
5135
|
+
var cursor = this.getCurrentCursor( true );
|
|
4603
5136
|
this.cursorToLine( cursor, line - 1, true );
|
|
4604
5137
|
}
|
|
4605
5138
|
|
|
@@ -4654,7 +5187,7 @@ class CodeEditor {
|
|
|
4654
5187
|
|
|
4655
5188
|
number = number ?? this.state.activeLine;
|
|
4656
5189
|
|
|
4657
|
-
const cursor = this.
|
|
5190
|
+
const cursor = this.getCurrentCursor();
|
|
4658
5191
|
this._updateDataInfoPanel( "@cursor-data", `Ln ${ number + 1 }, Col ${ cursor.position + 1 }` );
|
|
4659
5192
|
|
|
4660
5193
|
const oldLocal = this.toLocalLine( this.state.activeLine );
|
|
@@ -4731,23 +5264,32 @@ class CodeEditor {
|
|
|
4731
5264
|
}
|
|
4732
5265
|
|
|
4733
5266
|
CodeEditor.languages = {
|
|
4734
|
-
'Plain Text': { ext:
|
|
4735
|
-
'JavaScript': { ext:
|
|
4736
|
-
'TypeScript': { ext:
|
|
4737
|
-
'C': { ext: [ 'c', 'h' ], usePreprocessor: true },
|
|
4738
|
-
'C++': { ext: [
|
|
4739
|
-
'CSS': { ext:
|
|
4740
|
-
'CMake': { ext:
|
|
4741
|
-
'GLSL': { ext:
|
|
4742
|
-
'WGSL': { ext:
|
|
4743
|
-
'JSON': { ext:
|
|
4744
|
-
'XML': { ext:
|
|
4745
|
-
'Rust': { ext:
|
|
4746
|
-
'Python': { ext:
|
|
4747
|
-
'HTML': { ext:
|
|
4748
|
-
'Batch': { ext:
|
|
4749
|
-
'Markdown': { ext:
|
|
4750
|
-
'PHP': { ext:
|
|
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
|
+
};
|
|
5289
|
+
|
|
5290
|
+
CodeEditor.declarationKeywords = {
|
|
5291
|
+
'JavaScript': ['var', 'let', 'const', 'this', 'static', 'class'],
|
|
5292
|
+
'C++': [...CodeEditor.nativeTypes["C++"], 'const', 'auto', 'class', 'struct', 'namespace', 'enum', 'extern']
|
|
4751
5293
|
};
|
|
4752
5294
|
|
|
4753
5295
|
CodeEditor.keywords = {
|
|
@@ -4757,14 +5299,15 @@ CodeEditor.keywords = {
|
|
|
4757
5299
|
'enum', 'type'],
|
|
4758
5300
|
'C': ['int', 'float', 'double', 'long', 'short', 'char', 'const', 'void', 'true', 'false', 'auto', 'struct', 'typedef', 'signed', 'volatile', 'unsigned', 'static', 'extern', 'enum', 'register',
|
|
4759
5301
|
'union'],
|
|
4760
|
-
'C++': [
|
|
5302
|
+
'C++': [...CodeEditor.nativeTypes["C++"], 'const', 'static_cast', 'dynamic_cast', 'new', 'delete', 'true', 'false', 'auto', 'class', 'struct', 'typedef', 'nullptr',
|
|
4761
5303
|
'NULL', 'signed', 'unsigned', 'namespace', 'enum', 'extern', 'union', 'sizeof', 'static', 'private', 'public'],
|
|
4762
5304
|
'CMake': ['cmake_minimum_required', 'set', 'not', 'if', 'endif', 'exists', 'string', 'strequal', 'add_definitions', 'macro', 'endmacro', 'file', 'list', 'source_group', 'add_executable',
|
|
4763
5305
|
'target_include_directories', 'set_target_properties', 'set_property', 'add_compile_options', 'add_link_options', 'include_directories', 'add_library', 'target_link_libraries',
|
|
4764
5306
|
'target_link_options', 'add_subdirectory', 'add_compile_definitions', 'project', 'cache'],
|
|
4765
5307
|
'JSON': ['true', 'false'],
|
|
4766
5308
|
'GLSL': ['true', 'false', 'function', 'int', 'float', 'vec2', 'vec3', 'vec4', 'mat2x2', 'mat3x3', 'mat4x4', 'struct'],
|
|
4767
|
-
'CSS': ['body', 'html', 'canvas', 'div', 'input', 'span', '.'
|
|
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'],
|
|
4768
5311
|
'WGSL': ['var', 'let', 'true', 'false', 'fn', 'bool', 'u32', 'i32', 'f16', 'f32', 'vec2', 'vec3', 'vec4', 'vec2f', 'vec3f', 'vec4f', 'mat2x2f', 'mat3x3f', 'mat4x4f', 'array', 'atomic', 'struct',
|
|
4769
5312
|
'sampler', 'sampler_comparison', 'texture_depth_2d', 'texture_depth_2d_array', 'texture_depth_cube', 'texture_depth_cube_array', 'texture_depth_multisampled_2d',
|
|
4770
5313
|
'texture_external', 'texture_1d', 'texture_2d', 'texture_2d_array', 'texture_3d', 'texture_cube', 'texture_cube_array', 'texture_storage_1d', 'texture_storage_2d',
|
|
@@ -4772,11 +5315,11 @@ CodeEditor.keywords = {
|
|
|
4772
5315
|
'Rust': ['as', 'const', 'crate', 'enum', 'extern', 'false', 'fn', 'impl', 'in', 'let', 'mod', 'move', 'mut', 'pub', 'ref', 'self', 'Self', 'static', 'struct', 'super', 'trait', 'true',
|
|
4773
5316
|
'type', 'unsafe', 'use', 'where', 'abstract', 'become', 'box', 'final', 'macro', 'override', 'priv', 'typeof', 'unsized', 'virtual'],
|
|
4774
5317
|
'Python': ['False', 'def', 'None', 'True', 'in', 'is', 'and', 'lambda', 'nonlocal', 'not', 'or'],
|
|
4775
|
-
'Batch': ['set', '
|
|
4776
|
-
'DRIVERQUERY', 'print', 'PRINT'],
|
|
5318
|
+
'Batch': ['set', 'echo', 'off', 'del', 'defined', 'setlocal', 'enabledelayedexpansion', 'driverquery', 'print'],
|
|
4777
5319
|
'HTML': ['html', 'meta', 'title', 'link', 'script', 'body', 'DOCTYPE', 'head', 'br', 'i', 'a', 'li', 'img', 'tr', 'td', 'h1', 'h2', 'h3', 'h4', 'h5'],
|
|
4778
5320
|
'Markdown': ['br', 'i', 'a', 'li', 'img', 'table', 'title', 'tr', 'td', 'h1', 'h2', 'h3', 'h4', 'h5'],
|
|
4779
|
-
'PHP': ['const', 'function', 'array', 'new', 'int', 'string', '$this', 'public', 'null', 'private', 'protected', 'implements', 'class', 'use', 'namespace', 'abstract', 'clone', 'final'
|
|
5321
|
+
'PHP': ['const', 'function', 'array', 'new', 'int', 'string', '$this', 'public', 'null', 'private', 'protected', 'implements', 'class', 'use', 'namespace', 'abstract', 'clone', 'final',
|
|
5322
|
+
'enum'],
|
|
4780
5323
|
};
|
|
4781
5324
|
|
|
4782
5325
|
CodeEditor.utils = { // These ones don't have hightlight, used as suggestions to autocomplete only...
|
|
@@ -4813,7 +5356,7 @@ CodeEditor.builtIn = {
|
|
|
4813
5356
|
'PHP': ['echo', 'print'],
|
|
4814
5357
|
};
|
|
4815
5358
|
|
|
4816
|
-
CodeEditor.
|
|
5359
|
+
CodeEditor.statements = {
|
|
4817
5360
|
'JavaScript': ['for', 'if', 'else', 'case', 'switch', 'return', 'while', 'continue', 'break', 'do', 'import', 'from', 'throw', 'async', 'try', 'catch', 'await'],
|
|
4818
5361
|
'TypeScript': ['for', 'if', 'else', 'case', 'switch', 'return', 'while', 'continue', 'break', 'do', 'import', 'from', 'throw', 'async', 'try', 'catch', 'await', 'as'],
|
|
4819
5362
|
'CSS': ['@', 'import'],
|
|
@@ -4831,6 +5374,7 @@ CodeEditor.statementsAndDeclarations = {
|
|
|
4831
5374
|
|
|
4832
5375
|
CodeEditor.symbols = {
|
|
4833
5376
|
'JavaScript': ['<', '>', '[', ']', '{', '}', '(', ')', ';', '=', '|', '||', '&', '&&', '?', '??'],
|
|
5377
|
+
'TypeScript': ['<', '>', '[', ']', '{', '}', '(', ')', ';', '=', '|', '||', '&', '&&', '?', '??'],
|
|
4834
5378
|
'C': ['<', '>', '[', ']', '{', '}', '(', ')', ';', '=', '|', '||', '&', '&&', '?', '*', '-', '+'],
|
|
4835
5379
|
'C++': ['<', '>', '[', ']', '{', '}', '(', ')', ';', '=', '|', '||', '&', '&&', '?', '::', '*', '-', '+'],
|
|
4836
5380
|
'CMake': ['{', '}'],
|
|
@@ -4843,19 +5387,19 @@ CodeEditor.symbols = {
|
|
|
4843
5387
|
'Batch': ['[', ']', '(', ')', '%'],
|
|
4844
5388
|
'HTML': ['<', '>', '/'],
|
|
4845
5389
|
'XML': ['<', '>', '/'],
|
|
4846
|
-
'PHP': ['{', '}', '(', ')'],
|
|
5390
|
+
'PHP': ['[', ']', '{', '}', '(', ')'],
|
|
4847
5391
|
};
|
|
4848
5392
|
|
|
4849
5393
|
CodeEditor.REGISTER_LANGUAGE = function( name, options = {}, def, rules )
|
|
4850
5394
|
{
|
|
4851
5395
|
CodeEditor.languages[ name ] = options;
|
|
4852
5396
|
|
|
4853
|
-
if( def?.keywords ) CodeEditor.keywords[ name ] = def.keywords
|
|
4854
|
-
if( def?.utils ) CodeEditor.utils[ name ] = def.utils
|
|
4855
|
-
if( def?.types ) CodeEditor.types[ name ] = def.types
|
|
4856
|
-
if( def?.builtIn ) CodeEditor.builtIn[ name ] = def.builtIn
|
|
4857
|
-
if( def?.
|
|
4858
|
-
if( def?.symbols ) CodeEditor.symbols[ name ] = def.symbols
|
|
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 );
|
|
4859
5403
|
|
|
4860
5404
|
if( rules ) HighlightRules[ name ] = rules;
|
|
4861
5405
|
};
|