lexgui 0.1.18 → 0.1.20

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.
@@ -30,7 +30,7 @@ function firstNonspaceIndex( str ) {
30
30
  }
31
31
 
32
32
  function deleteElement( el ) {
33
- if(el) el.remove();
33
+ if( el ) el.remove();
34
34
  }
35
35
 
36
36
  let ASYNC_ENABLED = true;
@@ -59,11 +59,25 @@ class CodeSelection {
59
59
  return this.fromY === this.toY;
60
60
  }
61
61
 
62
+ samePosition() {
63
+ return this.fromX === this.toX;
64
+ }
65
+
66
+ isEmpty() {
67
+ return this.sameLine() && this.samePosition();
68
+ }
69
+
62
70
  invertIfNecessary() {
63
- if(this.fromX > this.toX)
64
- swapElements(this, 'fromX', 'toX');
65
- if(this.fromY > this.toY)
66
- swapElements(this, 'fromY', 'toY');
71
+ // if( this.fromX > this.toX )
72
+ if( this.fromY > this.toY )
73
+ {
74
+ swapElements( this, 'fromX', 'toX' );
75
+ swapElements( this, 'fromY', 'toY' );
76
+ }
77
+ else if( this.sameLine() && this.fromX > this.toX )
78
+ {
79
+ swapElements( this, 'fromX', 'toX' );
80
+ }
67
81
  }
68
82
 
69
83
  selectInline( x, y, width ) {
@@ -82,6 +96,9 @@ class CodeSelection {
82
96
  domEl.style.left = "calc(" + domEl._left + "px + " + this.editor.xPadding + ")";
83
97
  domEl.style.width = width + "px";
84
98
  this.editor.selections.appendChild(domEl);
99
+
100
+ // Hide active line background
101
+ this.editor.code.childNodes.forEach( e => e.classList.remove( 'active-line' ) );
85
102
  }
86
103
  };
87
104
 
@@ -154,6 +171,10 @@ class CodeEditor {
154
171
  static CURSOR_LEFT = 1;
155
172
  static CURSOR_TOP = 2;
156
173
 
174
+ static SELECTION_X = 1;
175
+ static SELECTION_Y = 2
176
+ static SELECTION_X_Y = CodeEditor.SELECTION_X | CodeEditor.SELECTION_Y;
177
+
157
178
  static KEEP_VISIBLE_LINES = 1;
158
179
  static UPDATE_VISIBLE_LINES = 2;
159
180
 
@@ -170,11 +191,74 @@ class CodeEditor {
170
191
  window.editor = this;
171
192
 
172
193
  CodeEditor.__instances.push( this );
194
+
195
+ // File explorer
196
+ if( options.file_explorer ?? false )
197
+ {
198
+ var [explorerArea, codeArea] = area.split({ sizes:["15%","85%"] });
199
+ explorerArea.setLimitBox( 180, 20, 512 );
200
+ this.explorerArea = explorerArea;
201
+
202
+ let panel = new LX.Panel();
203
+
204
+ panel.addTitle( "EXPLORER" );
205
+
206
+ let sceneData = {
207
+ 'id': 'WORKSPACE',
208
+ 'skipVisibility': true,
209
+ 'children': []
210
+ };
211
+
212
+ this.explorer = panel.addTree( null, sceneData, {
213
+ filter: false,
214
+ rename: false,
215
+ skip_default_icon: true,
216
+ onevent: (event) => {
217
+ switch(event.type) {
218
+ // case LX.TreeEvent.NODE_SELECTED:
219
+ // if( !this.tabs.tabDOMs[ event.node.id ] ) break;
220
+ case LX.TreeEvent.NODE_DBLCLICKED:
221
+ this.loadTab( event.node.id );
222
+ break;
223
+ case LX.TreeEvent.NODE_DELETED:
224
+ this.tabs.delete( event.node.id );
225
+ delete this.loadedTabs[ event.node.id ];
226
+ break;
227
+ // case LX.TreeEvent.NODE_CONTEXTMENU:
228
+ // LX.addContextMenu( event.multiple ? "Selected Nodes" : event.node.id, event.value, m => {
229
+ //
230
+ // });
231
+ // break;
232
+ // case LX.TreeEvent.NODE_DRAGGED:
233
+ // console.log(event.node.id + " is now child of " + event.value.id);
234
+ // break;
235
+ }
236
+ }
237
+ });
238
+
239
+ this.addExplorerItem = function( item )
240
+ {
241
+ if( !this.explorer.data.children.find( (value, index) => value.id === item.id ) )
242
+ this.explorer.data.children.push( item );
243
+ };
244
+
245
+ explorerArea.attach( panel );
246
+
247
+ // Update area
248
+ area = codeArea;
249
+ }
173
250
 
174
251
  this.base_area = area;
175
252
  this.area = new LX.Area( { className: "lexcodeeditor", height: "auto", no_append: true } );
176
253
 
177
- this.tabs = this.area.addTabs( { onclose: (name) => delete this.openedTabs[ name ] } );
254
+ this.tabs = this.area.addTabs( { onclose: (name) => {
255
+ delete this.openedTabs[ name ];
256
+ if( Object.keys( this.openedTabs ).length < 2 )
257
+ {
258
+ clearInterval( this.blinker );
259
+ this.cursors.classList.remove( 'show' );
260
+ }
261
+ } } );
178
262
  this.tabs.root.addEventListener( 'dblclick', (e) => {
179
263
  if( options.allow_add_scripts ?? true ) {
180
264
  e.preventDefault();
@@ -208,6 +292,9 @@ class CodeEditor {
208
292
  this.root.addEventListener( 'click', this.processMouse.bind(this) );
209
293
  this.root.addEventListener( 'contextmenu', this.processMouse.bind(this) );
210
294
 
295
+ // Add mouseup event to document as well to detect when selections end
296
+ document.body.addEventListener( 'mouseup', this._onMouseUp.bind(this) );
297
+
211
298
  // Cursors and selection
212
299
 
213
300
  this.cursors = document.createElement( 'div' );
@@ -230,13 +317,32 @@ class CodeEditor {
230
317
  cursor.style.left = this.xPadding;
231
318
  cursor._top = 0;
232
319
  cursor.style.top = cursor._top + "px";
233
- cursor.position = 0;
320
+ cursor._position = 0;
234
321
  cursor._line = 0;
235
322
  cursor.print = (function() { console.log( this.line, this.position ) }).bind( cursor );
236
323
 
324
+ Object.defineProperty( this, 'line', {
325
+ get: (v) => { return cursor.line }
326
+ } );
327
+
328
+ Object.defineProperty( this, 'position', {
329
+ get: (v) => { return cursor.position }
330
+ } );
331
+
237
332
  Object.defineProperty( cursor, 'line', {
238
333
  get: (v) => { return this._line },
239
- set: (v) => { this._line = v; }
334
+ set: (v) => {
335
+ this._line = v;
336
+ this._setActiveLine( v );
337
+ }
338
+ } );
339
+
340
+ Object.defineProperty( cursor, 'position', {
341
+ get: (v) => { return this._position },
342
+ set: (v) => {
343
+ this._position = v;
344
+ this._updateDataInfoPanel( "@cursor-pos", "Col " + v );
345
+ }
240
346
  } );
241
347
 
242
348
  this.cursors.appendChild( cursor );
@@ -337,14 +443,15 @@ class CodeEditor {
337
443
 
338
444
  this.state = {
339
445
  focused: false,
340
- selectingText: false
446
+ selectingText: false,
447
+ activeLine: null
341
448
  }
342
449
 
343
450
  // Code
344
451
 
345
452
  this.useAutoComplete = options.autocomplete ?? true;
346
453
  this.highlight = options.highlight ?? 'Plain Text';
347
- this.onsave = options.onsave ?? ((code) => { });
454
+ this.onsave = options.onsave ?? ((code) => { console.log( code, "save" ) });
348
455
  this.onrun = options.onrun ?? ((code) => { this.runScript(code) });
349
456
  this.actions = {};
350
457
  this.cursorBlinkRate = 550;
@@ -352,7 +459,7 @@ class CodeEditor {
352
459
  this.maxUndoSteps = 16;
353
460
  this.lineHeight = 20;
354
461
  this.defaultSingleLineCommentToken = "//";
355
- this.charWidth = 8; //this.measureChar();
462
+ this.charWidth = 7; //this._measureChar();
356
463
  this._lastTime = null;
357
464
 
358
465
  this.pairKeys = {
@@ -372,17 +479,18 @@ class CodeEditor {
372
479
  // setInterval( this.scanWordSuggestions.bind( this ), 2000 );
373
480
 
374
481
  this.languages = {
375
- 'Plain Text': { },
376
- 'JavaScript': { },
377
- 'C++': { },
378
- 'CSS': { },
379
- 'GLSL': { },
380
- 'WGSL': { },
381
- 'JSON': { },
382
- 'XML': { },
383
- 'Python': { singleLineCommentToken: '#' },
384
- 'HTML': { },
385
- 'Batch': { blockComments: false, singleLineCommentToken: '::' }
482
+ 'Plain Text': { ext: 'txt' },
483
+ 'JavaScript': { ext: 'js' },
484
+ 'C++': { ext: 'cpp' },
485
+ 'CSS': { ext: 'css' },
486
+ 'GLSL': { ext: 'glsl' },
487
+ 'WGSL': { ext: 'wgsl' },
488
+ 'JSON': { ext: 'json' },
489
+ 'XML': { ext: 'xml' },
490
+ 'Rust': { ext: 'rs' },
491
+ 'Python': { ext: 'py', singleLineCommentToken: '#' },
492
+ 'HTML': { ext: 'html' },
493
+ 'Batch': { ext: 'bat', blockComments: false, singleLineCommentToken: '::' }
386
494
  };
387
495
 
388
496
  this.specialKeys = [
@@ -395,7 +503,7 @@ class CodeEditor {
395
503
  'JavaScript': ['var', 'let', 'const', 'this', 'in', 'of', 'true', 'false', 'new', 'function', 'NaN', 'static', 'class', 'constructor', 'null', 'typeof', 'debugger', 'abstract',
396
504
  'arguments', 'extends', 'instanceof'],
397
505
  'C++': ['int', 'float', 'double', 'bool', 'char', 'wchar_t', 'const', 'static_cast', 'dynamic_cast', 'new', 'delete', 'void', 'true', 'false', 'auto', 'struct', 'typedef', 'nullptr',
398
- 'NULL', 'unsigned'],
506
+ 'NULL', 'unsigned', 'namespace'],
399
507
  'JSON': ['true', 'false'],
400
508
  'GLSL': ['true', 'false', 'function', 'int', 'float', 'vec2', 'vec3', 'vec4', 'mat2x2', 'mat3x3', 'mat4x4', 'struct'],
401
509
  'CSS': ['body', 'html', 'canvas', 'div', 'input', 'span', '.'],
@@ -403,6 +511,8 @@ class CodeEditor {
403
511
  'sampler', 'sampler_comparison', 'texture_depth_2d', 'texture_depth_2d_array', 'texture_depth_cube', 'texture_depth_cube_array', 'texture_depth_multisampled_2d',
404
512
  'texture_external', 'texture_1d', 'texture_2d', 'texture_2d_array', 'texture_3d', 'texture_cube', 'texture_cube_array', 'texture_storage_1d', 'texture_storage_2d',
405
513
  'texture_storage_2d_array', 'texture_storage_3d'],
514
+ 'Rust': ['as', 'const', 'crate', 'enum', 'extern', 'false', 'fn', 'impl', 'in', 'let', 'mod', 'move', 'mut', 'pub', 'ref', 'self', 'Self', 'static', 'struct', 'super', 'trait', 'true',
515
+ 'type', 'unsafe', 'use', 'where', 'abstract', 'become', 'box', 'final', 'macro', 'override', 'priv', 'typeof', 'unsized', 'virtual'],
406
516
  'Python': ['False', 'def', 'None', 'True', 'in', 'is', 'and', 'lambda', 'nonlocal', 'not', 'or'],
407
517
  'Batch': ['set', 'SET', 'echo', 'ECHO', 'off', 'OFF', 'del', 'DEL', 'defined', 'DEFINED', 'setlocal', 'SETLOCAL', 'enabledelayedexpansion', 'ENABLEDELAYEDEXPANSION', 'driverquery',
408
518
  'DRIVERQUERY', 'print', 'PRINT'],
@@ -420,6 +530,7 @@ class CodeEditor {
420
530
  };
421
531
  this.types = {
422
532
  'JavaScript': ['Object', 'String', 'Function', 'Boolean', 'Symbol', 'Error', 'Number', 'TextEncoder', 'TextDecoder'],
533
+ 'Rust': ['u128'],
423
534
  'Python': ['int', 'type', 'float', 'map', 'list', 'ArithmeticError', 'AssertionError', 'AttributeError', 'Exception', 'EOFError', 'FloatingPointError', 'GeneratorExit',
424
535
  'ImportError', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'NotImplementedError', 'OSError',
425
536
  'OverflowError', 'ReferenceError', 'RuntimeError', 'StopIteration', 'SyntaxError', 'TabError', 'SystemError', 'SystemExit', 'TypeError', 'UnboundLocalError',
@@ -434,9 +545,10 @@ class CodeEditor {
434
545
  };
435
546
  this.statementsAndDeclarations = {
436
547
  'JavaScript': ['for', 'if', 'else', 'case', 'switch', 'return', 'while', 'continue', 'break', 'do', 'import', 'from', 'throw', 'async', 'try', 'catch', 'await'],
437
- 'C++': ['std', 'for', 'if', 'else', 'return', 'continue', 'break', 'case', 'switch', 'while', 'glm', 'spdlog'],
548
+ 'C++': ['std', 'for', 'if', 'else', 'return', 'continue', 'break', 'case', 'switch', 'while', 'using', 'glm', 'spdlog'],
438
549
  'GLSL': ['for', 'if', 'else', 'return', 'continue', 'break'],
439
550
  'WGSL': ['const','for', 'if', 'else', 'return', 'continue', 'break', 'storage', 'read', 'uniform'],
551
+ 'Rust': ['break', 'else', 'continue', 'for', 'if', 'loop', 'match', 'return', 'while', 'do', 'yield'],
440
552
  'Python': ['if', 'raise', 'del', 'import', 'return', 'elif', 'try', 'else', 'while', 'as', 'except', 'with', 'assert', 'finally', 'yield', 'break', 'for', 'class', 'continue',
441
553
  'global', 'pass'],
442
554
  'Batch': ['if', 'IF', 'for', 'FOR', 'in', 'IN', 'do', 'DO', 'call', 'CALL', 'goto', 'GOTO', 'exit', 'EXIT']
@@ -448,6 +560,7 @@ class CodeEditor {
448
560
  'GLSL': ['[', ']', '{', '}', '(', ')'],
449
561
  'WGSL': ['[', ']', '{', '}', '(', ')', '->'],
450
562
  'CSS': ['{', '}', '(', ')', '*'],
563
+ 'Rust': ['<', '>', '[', ']', '(', ')', '='],
451
564
  'Python': ['<', '>', '[', ']', '(', ')', '='],
452
565
  'Batch': ['[', ']', '(', ')', '%'],
453
566
  'HTML': ['<', '>', '/']
@@ -475,7 +588,7 @@ class CodeEditor {
475
588
  if( this.selection ) {
476
589
  this.deleteSelection( cursor );
477
590
  // Remove entire line when selecting with triple click
478
- if(this.code.lines[ ln ] != undefined && !this.code.lines[ ln ].length)
591
+ if( this._tripleClickSelection )
479
592
  {
480
593
  this.actions['Backspace'].callback( ln, cursor, e );
481
594
  this.lineDown( cursor, true );
@@ -547,7 +660,6 @@ class CodeEditor {
547
660
 
548
661
  this.resetCursorPos( CodeEditor.CURSOR_LEFT );
549
662
  if(idx > 0) this.cursorToString( cursor, prestring );
550
- this._refreshCodeInfo( cursor.line, cursor.position );
551
663
  this.setScrollLeft( 0 );
552
664
 
553
665
  if( e.shiftKey && !e.cancelShift )
@@ -563,7 +675,7 @@ class CodeEditor {
563
675
  this.selection.selectInline( idx, cursor.line, this.measureString( string ) );
564
676
  else
565
677
  {
566
- this.processSelection();
678
+ this.processSelection( e );
567
679
  }
568
680
  } else if( !e.keepSelection )
569
681
  this.endSelection();
@@ -571,7 +683,7 @@ class CodeEditor {
571
683
 
572
684
  this.action( 'End', false, ( ln, cursor, e ) => {
573
685
 
574
- if( e.shiftKey || e._shiftKey ) {
686
+ if( ( e.shiftKey || e._shiftKey ) && !e.cancelShift ) {
575
687
 
576
688
  var string = this.code.lines[ ln ].substring( cursor.position );
577
689
  if( !this.selection )
@@ -582,9 +694,9 @@ class CodeEditor {
582
694
  {
583
695
  this.resetCursorPos( CodeEditor.CURSOR_LEFT );
584
696
  this.cursorToString( cursor, this.code.lines[ ln ] );
585
- this.processSelection();
697
+ this.processSelection( e );
586
698
  }
587
- } else
699
+ } else if( !e.keepSelection )
588
700
  this.endSelection();
589
701
 
590
702
  this.resetCursorPos( CodeEditor.CURSOR_LEFT );
@@ -643,16 +755,14 @@ class CodeEditor {
643
755
  if( !this.selection )
644
756
  this.startSelection( cursor );
645
757
 
646
- this.selection.toY = ( this.selection.toY > 0 ) ? ( this.selection.toY - 1 ) : 0;
647
- this.cursorToLine( cursor, this.selection.toY );
758
+ this.lineUp();
648
759
 
649
760
  var letter = this.getCharAtPos( cursor );
650
761
  if( !letter ) {
651
- this.selection.toX = this.code.lines[ cursor.line ].length;
652
- this.cursorToPosition( cursor, this.selection.toX );
762
+ this.cursorToPosition( cursor, this.code.lines[ cursor.line ].length );
653
763
  }
654
764
 
655
- this.processSelection( null, true );
765
+ this.processSelection( e, false );
656
766
 
657
767
  } else {
658
768
  this.endSelection();
@@ -665,7 +775,7 @@ class CodeEditor {
665
775
  // Move up autocomplete selection
666
776
  else
667
777
  {
668
- this.moveArrowSelectedAutoComplete('up');
778
+ this._moveArrowSelectedAutoComplete('up');
669
779
  }
670
780
  });
671
781
 
@@ -678,16 +788,14 @@ class CodeEditor {
678
788
  if( !this.selection )
679
789
  this.startSelection( cursor );
680
790
 
681
- this.selection.toY = this.selection.toY < this.code.lines.length - 1 ? this.selection.toY + 1 : this.code.lines.length - 1;
682
- this.cursorToLine( cursor, this.selection.toY );
791
+ this.lineDown( cursor );
683
792
 
684
793
  var letter = this.getCharAtPos( cursor );
685
794
  if( !letter ) {
686
- this.selection.toX = Math.max(this.code.lines[ cursor.line ].length - 1, 0);
687
- this.cursorToPosition(cursor, this.selection.toX);
795
+ this.cursorToPosition( cursor, Math.max(this.code.lines[ cursor.line ].length - 1, 0) );
688
796
  }
689
797
 
690
- this.processSelection( null, true );
798
+ this.processSelection( e );
691
799
  } else {
692
800
 
693
801
  if( this.code.lines[ ln + 1 ] == undefined )
@@ -702,12 +810,16 @@ class CodeEditor {
702
810
  // Move down autocomplete selection
703
811
  else
704
812
  {
705
- this.moveArrowSelectedAutoComplete('down');
813
+ this._moveArrowSelectedAutoComplete('down');
706
814
  }
707
815
  });
708
816
 
709
817
  this.action( 'ArrowLeft', false, ( ln, cursor, e ) => {
710
818
 
819
+ // Nothing to do..
820
+ if( cursor.line == 0 && cursor.position == 0 )
821
+ return;
822
+
711
823
  if( e.metaKey ) { // Apple devices (Command)
712
824
  e.preventDefault();
713
825
  this.actions[ 'Home' ].callback( ln, cursor, e );
@@ -715,29 +827,29 @@ class CodeEditor {
715
827
  else if( e.ctrlKey ) {
716
828
  // Get next word
717
829
  const [word, from, to] = this.getWordAtPos( cursor, -1 );
718
- var diff = Math.max(cursor.position - from, 1);
719
- var substr = word.substr(0, diff);
830
+ // If no length, we change line..
831
+ if( !word.length && this.lineUp( cursor, true ) ) {
832
+ e.cancelShift = true;
833
+ e.keepSelection = true;
834
+ this.actions[ 'End' ].callback( cursor.line, cursor, e );
835
+ delete e.cancelShift;
836
+ delete e.keepSelection;
837
+ }
838
+ var diff = Math.max( cursor.position - from, 1 );
839
+ var substr = word.substr( 0, diff );
720
840
  // Selections...
721
841
  if( e.shiftKey ) { if( !this.selection ) this.startSelection( cursor ); }
722
842
  else this.endSelection();
723
- this.cursorToString(cursor, substr, true);
724
- if( e.shiftKey ) this.processSelection();
843
+ this.cursorToString( cursor, substr, true );
844
+ if( e.shiftKey ) this.processSelection( e, false, true );
725
845
  }
726
846
  else {
727
847
  var letter = this.getCharAtPos( cursor, -1 );
728
848
  if( letter ) {
729
849
  if( e.shiftKey ) {
730
850
  if( !this.selection ) this.startSelection( cursor );
731
- if( ( ( cursor.position - 1 ) < this.selection.fromX ) && this.selection.sameLine() )
732
- this.selection.fromX--;
733
- else if( ( cursor.position - 1 ) == this.selection.fromX && this.selection.sameLine() ) {
734
- this.cursorToLeft( letter, cursor );
735
- this.endSelection();
736
- return;
737
- }
738
- else this.selection.toX--;
739
851
  this.cursorToLeft( letter, cursor );
740
- this.processSelection( null, true );
852
+ this.processSelection( e, false, CodeEditor.SELECTION_X );
741
853
  }
742
854
  else {
743
855
  if( !this.selection ) {
@@ -756,55 +868,48 @@ class CodeEditor {
756
868
  }
757
869
  else if( cursor.line > 0 ) {
758
870
 
759
- if( e.shiftKey ) {
760
- if( !this.selection ) this.startSelection( cursor );
761
- }
871
+ if( e.shiftKey && !this.selection ) this.startSelection( cursor );
762
872
 
763
873
  this.lineUp( cursor );
874
+
875
+ e.cancelShift = e.keepSelection = true;
764
876
  this.actions[ 'End' ].callback( cursor.line, cursor, e );
877
+ delete e.cancelShift; delete e.keepSelection;
765
878
 
766
- if( e.shiftKey ) {
767
- this.selection.toX = cursor.position;
768
- this.selection.toY--;
769
- this.processSelection( null, true );
770
- }
879
+ if( e.shiftKey ) this.processSelection( e, false );
771
880
  }
772
881
  }
773
882
  });
774
883
 
775
884
  this.action( 'ArrowRight', false, ( ln, cursor, e ) => {
776
885
 
886
+ // Nothing to do..
887
+ if( cursor.line == this.code.lines.length - 1 &&
888
+ cursor.position == this.code.lines[ cursor.line - 1 ].length )
889
+ return;
890
+
777
891
  if( e.metaKey ) { // Apple devices (Command)
778
892
  e.preventDefault();
779
893
  this.actions[ 'End' ].callback( ln, cursor );
780
894
  } else if( e.ctrlKey ) {
781
895
  // Get next word
782
896
  const [ word, from, to ] = this.getWordAtPos( cursor );
897
+ // If no length, we change line..
898
+ if( !word.length ) this.lineDown( cursor, true );
783
899
  var diff = cursor.position - from;
784
900
  var substr = word.substr( diff );
785
901
  // Selections...
786
902
  if( e.shiftKey ) { if( !this.selection ) this.startSelection( cursor ); }
787
903
  else this.endSelection();
788
904
  this.cursorToString( cursor, substr);
789
- if( e.shiftKey ) this.processSelection();
905
+ if( e.shiftKey ) this.processSelection( e );
790
906
  } else {
791
907
  var letter = this.getCharAtPos( cursor );
792
908
  if( letter ) {
793
909
  if( e.shiftKey ) {
794
910
  if( !this.selection ) this.startSelection( cursor );
795
- var keep_range = false;
796
- if( cursor.position == this.selection.fromX ) {
797
- if( ( cursor.position + 1 ) == this.selection.toX && this.selection.sameLine() ) {
798
- this.cursorToRight( letter, cursor );
799
- this.endSelection();
800
- return;
801
- } else if( cursor.position < this.selection.toX ) {
802
- this.selection.fromX++;
803
- keep_range = true;
804
- } else this.selection.toX++;
805
- }
806
911
  this.cursorToRight( letter, cursor );
807
- this.processSelection( null, keep_range );
912
+ this.processSelection( e, false, CodeEditor.SELECTION_X );
808
913
  }else{
809
914
  if( !this.selection ) {
810
915
  this.cursorToRight( letter, cursor );
@@ -825,18 +930,11 @@ class CodeEditor {
825
930
 
826
931
  if( e.shiftKey ) {
827
932
  if( !this.selection ) this.startSelection( cursor );
828
- e.cancelShift = true;
829
- e.keepSelection = true;
830
933
  }
831
934
 
832
- this.lineDown( cursor );
833
- this.actions['Home'].callback( cursor.line, cursor, e );
935
+ this.lineDown( cursor, true );
834
936
 
835
- if( e.shiftKey ) {
836
- this.selection.toX = cursor.position;
837
- this.selection.toY++;
838
- this.processSelection( null, true );
839
- }
937
+ if( e.shiftKey ) this.processSelection( e, false );
840
938
 
841
939
  this.hideAutoCompleteBox();
842
940
  }
@@ -845,6 +943,7 @@ class CodeEditor {
845
943
 
846
944
  // Default code tab
847
945
 
946
+ this.loadedTabs = { };
848
947
  this.openedTabs = { };
849
948
 
850
949
  if( options.allow_add_scripts ?? true )
@@ -855,6 +954,28 @@ class CodeEditor {
855
954
  // Create inspector panel
856
955
  let panel = this._createPanelInfo();
857
956
  if( panel ) area.attach( panel );
957
+
958
+ const fontUrl = "https://raw.githubusercontent.com/jxarco/lexgui.js/master/" + "/data/CommitMono-400-Regular.otf";
959
+ const commitMono = new FontFace(
960
+ "CommitMono",
961
+ `url(${ fontUrl })`,
962
+ {
963
+ style: "normal",
964
+ weight: "400",
965
+ display: "swap"
966
+ }
967
+ );
968
+
969
+ // Add to the document.fonts (FontFaceSet)
970
+ document.fonts.add(commitMono);
971
+
972
+ // Load the font
973
+ commitMono.load();
974
+
975
+ // Wait until the fonts are all loaded
976
+ document.fonts.ready.then(() => {
977
+ console.log("commitMono loaded")
978
+ });
858
979
  }
859
980
 
860
981
  static getInstances()
@@ -862,6 +983,26 @@ class CodeEditor {
862
983
  return CodeEditor.__instances;
863
984
  }
864
985
 
986
+ // This received key inputs from the entire document...
987
+ onKeyPressed( e ) {
988
+
989
+ // Toggle visibility of the file explorer
990
+ if( e.key == 'b' && e.ctrlKey && this.explorer )
991
+ {
992
+ this.explorerArea.root.classList.toggle( "hidden" );
993
+ if( this._lastBaseareaWidth )
994
+ {
995
+ this.base_area.root.style.width = this._lastBaseareaWidth;
996
+ delete this._lastBaseareaWidth;
997
+
998
+ } else
999
+ {
1000
+ this._lastBaseareaWidth = this.base_area.root.style.width;
1001
+ this.base_area.root.style.width = "100%";
1002
+ }
1003
+ }
1004
+ }
1005
+
865
1006
  getText( min ) {
866
1007
  return this.code.lines.join(min ? ' ' : '\n');
867
1008
  }
@@ -951,11 +1092,22 @@ class CodeEditor {
951
1092
  loadFile( file ) {
952
1093
 
953
1094
  const inner_add_tab = ( text, name, title ) => {
954
- const existing = this.addTab(name, true, title);
955
- if( !existing )
1095
+
1096
+ // Set current text and language
1097
+ const lines = text.replaceAll( '\r', '' ).split( '\n' );
1098
+
1099
+ // Add item in the explorer if used
1100
+ if( this.explorer )
1101
+ {
1102
+ this._storedLines = this._storedLines ?? {};
1103
+ this._storedLines[ name ] = lines;
1104
+ this.addExplorerItem( { 'id': name, 'skipVisibility': true, 'icon': this._getFileIcon( name ) } );
1105
+ this.explorer.frefresh( name );
1106
+ }
1107
+ else
956
1108
  {
957
- text = text.replaceAll( '\r', '' );
958
- this.code.lines = text.split( '\n' );
1109
+ this.addTab(name, true, title);
1110
+ this.code.lines = lines;
959
1111
  this._changeLanguageFromExtension( LX.getExtension( name ) );
960
1112
  }
961
1113
  };
@@ -1011,8 +1163,37 @@ class CodeEditor {
1011
1163
 
1012
1164
  this.code.language = lang;
1013
1165
  this.highlight = lang;
1014
- this._refreshCodeInfo();
1166
+ this._updateDataInfoPanel( "@highlight", lang );
1015
1167
  this.processLines();
1168
+
1169
+ const ext = this.languages[ lang ].ext;
1170
+ const icon = this._getFileIcon( null, ext );
1171
+
1172
+ // Update tab icon
1173
+ {
1174
+ const tab = this.tabs.tabDOMs[ this.code.tabName ];
1175
+ tab.firstChild.remove();
1176
+ console.assert( tab != undefined );
1177
+ var iconEl;
1178
+ if( icon.includes( 'fa-' ) )
1179
+ {
1180
+ iconEl = document.createElement( 'i' );
1181
+ iconEl.className = icon;
1182
+ } else {
1183
+ iconEl = document.createElement( 'img' );
1184
+ iconEl.src = "https://raw.githubusercontent.com/jxarco/lexgui.js/master/" + icon;
1185
+ }
1186
+ tab.prepend( iconEl );
1187
+ }
1188
+
1189
+ // Update explorer icon
1190
+ if( this.explorer )
1191
+ {
1192
+ const item = this.explorer.data.children.filter( (v) => v.id === this.code.tabName )[ 0 ];
1193
+ console.assert( item != undefined );
1194
+ item.icon = icon;
1195
+ this.explorer.frefresh( this.code.tabName );
1196
+ }
1016
1197
  }
1017
1198
 
1018
1199
  _changeLanguageFromExtension( ext ) {
@@ -1020,23 +1201,13 @@ class CodeEditor {
1020
1201
  if( !ext )
1021
1202
  return this._changeLanguage( this.code.language );
1022
1203
 
1023
- switch( ext.toLowerCase() )
1204
+ for( let l in this.languages )
1024
1205
  {
1025
- case 'js': return this._changeLanguage( 'JavaScript' );
1026
- case 'cpp': return this._changeLanguage( 'C++' );
1027
- case 'h': return this._changeLanguage( 'C++' );
1028
- case 'glsl': return this._changeLanguage( 'GLSL' );
1029
- case 'css': return this._changeLanguage( 'CSS' );
1030
- case 'json': return this._changeLanguage( 'JSON' );
1031
- case 'xml': return this._changeLanguage( 'XML' );
1032
- case 'wgsl': return this._changeLanguage( 'WGSL' );
1033
- case 'py': return this._changeLanguage( 'Python' );
1034
- case 'bat': return this._changeLanguage( 'Batch' );
1035
- case 'html': return this._changeLanguage( 'HTML' );
1036
- case 'txt':
1037
- default:
1038
- this._changeLanguage( 'Plain Text' );
1206
+ if( this.languages[l].ext == ext )
1207
+ return this._changeLanguage( l );
1039
1208
  }
1209
+
1210
+ this._changeLanguage( 'Plain Text' );
1040
1211
  }
1041
1212
 
1042
1213
  _createPanelInfo() {
@@ -1044,34 +1215,23 @@ class CodeEditor {
1044
1215
  if( !this.skipCodeInfo )
1045
1216
  {
1046
1217
  let panel = new LX.Panel({ className: "lexcodetabinfo", width: "calc(100%)", height: "auto" });
1047
- panel.ln = 0;
1048
- panel.col = 0;
1049
-
1050
- this._refreshCodeInfo = ( ln = panel.ln, col = panel.col ) => {
1051
- panel.ln = ln + 1;
1052
- panel.col = col + 1;
1053
- panel.clear();
1054
- panel.sameLine();
1055
- panel.addLabel( this.code.title, { float: 'right' });
1056
- panel.addLabel( "Ln " + panel.ln, { width: "64px" });
1057
- panel.addLabel( "Col " + panel.col, { width: "64px" });
1058
- panel.addButton( "<b>{ }</b>", this.highlight, ( value, event ) => {
1059
- LX.addContextMenu( "Language", event, m => {
1060
- for( const lang of Object.keys(this.languages) )
1061
- m.add( lang, this._changeLanguage.bind(this) );
1062
- });
1063
- }, { width: "25%", nameWidth: "15%" });
1064
- panel.endLine();
1065
- };
1066
1218
 
1067
- this._refreshCodeInfo();
1219
+ panel.sameLine();
1220
+ panel.addLabel( this.code.title, { float: 'right', signal: "@tab-name" });
1221
+ panel.addLabel( "Ln " + 1, { width: "64px", signal: "@cursor-line" });
1222
+ panel.addLabel( "Col " + 1, { width: "64px", signal: "@cursor-pos" });
1223
+ panel.addButton( "<b>{ }</b>", this.highlight, ( value, event ) => {
1224
+ LX.addContextMenu( "Language", event, m => {
1225
+ for( const lang of Object.keys(this.languages) )
1226
+ m.add( lang, this._changeLanguage.bind(this) );
1227
+ });
1228
+ }, { width: "25%", nameWidth: "15%", signal: "@highlight" });
1229
+ panel.endLine();
1068
1230
 
1069
1231
  return panel;
1070
1232
  }
1071
1233
  else
1072
1234
  {
1073
- this._refreshCodeInfo = () => {};
1074
-
1075
1235
  doAsync( () => {
1076
1236
 
1077
1237
  // Change css a little bit...
@@ -1083,24 +1243,72 @@ class CodeEditor {
1083
1243
  }
1084
1244
  }
1085
1245
 
1246
+ _getFileIcon( name, extension ) {
1247
+
1248
+ const isNewTabButton = name ? ( name === '+' ) : false;
1249
+ const ext = extension ?? LX.getExtension( name );
1250
+ return ext == 'html' ? "fa-solid fa-code orange" :
1251
+ ext == 'css' ? "fa-solid fa-hashtag dodgerblue" :
1252
+ ext == 'xml' ? "fa-solid fa-rss orange" :
1253
+ ext == 'bat' ? "fa-brands fa-windows lightblue" :
1254
+ [ 'js', 'py', 'json', 'cpp', 'rs' ].indexOf( ext ) > -1 ? "images/" + ext + ".png" :
1255
+ !isNewTabButton ? "fa-solid fa-align-left gray" : undefined;
1256
+ }
1257
+
1086
1258
  _onNewTab( e ) {
1087
1259
 
1088
1260
  this.processFocus(false);
1089
1261
 
1090
1262
  LX.addContextMenu( null, e, m => {
1091
1263
  m.add( "Create", this.addTab.bind( this, "unnamed.js", true ) );
1092
- m.add( "Load", this.loadTab.bind( this, "unnamed.js", true ) );
1264
+ m.add( "Load", this.loadTabFromFile.bind( this, "unnamed.js", true ) );
1093
1265
  });
1094
1266
  }
1095
1267
 
1096
- addTab( name, selected, title ) {
1097
-
1098
- if(this.openedTabs[ name ])
1268
+ _onSelectTab( isNewTabButton, event, name, ) {
1269
+
1270
+ if( isNewTabButton )
1099
1271
  {
1100
- this.tabs.select( this.code.tabName );
1101
- return true;
1272
+ this._onNewTab( event );
1273
+ return;
1102
1274
  }
1103
1275
 
1276
+ var cursor = cursor ?? this.cursors.children[ 0 ];
1277
+ this.saveCursor( cursor, this.code.cursorState );
1278
+
1279
+ this.code = this.loadedTabs[ name ];
1280
+ this.restoreCursor( cursor, this.code.cursorState );
1281
+
1282
+ this.endSelection();
1283
+ this._changeLanguageFromExtension( LX.getExtension( name ) );
1284
+ this._updateDataInfoPanel( "@tab-name", name );
1285
+ }
1286
+
1287
+ _onContextMenuTab( isNewTabButton, event, name, ) {
1288
+
1289
+ if( isNewTabButton )
1290
+ return;
1291
+
1292
+ LX.addContextMenu( null, event, m => {
1293
+ m.add( "Close", () => { this.tabs.delete( name ) } );
1294
+ m.add( "" );
1295
+ m.add( "Rename", () => { console.warn( "TODO" )} );
1296
+ });
1297
+ }
1298
+
1299
+ addTab( name, selected, title ) {
1300
+
1301
+ // If already loaded, set new name...
1302
+ const repeats = Object.keys( editor.loadedTabs ).slice( 1 ).reduce( ( v, key ) => {
1303
+ const noRepeatName = key.replace( /[_\d+]/g, '');
1304
+ return v + ( noRepeatName == name );
1305
+ }, 0 );
1306
+
1307
+ if( repeats > 0 )
1308
+ name = name.split( '.' ).join( '_' + repeats + '.' );
1309
+
1310
+ const isNewTabButton = ( name === '+' );
1311
+
1104
1312
  // Create code content
1105
1313
  let code = document.createElement( 'div' );
1106
1314
  code.className = 'code';
@@ -1129,34 +1337,25 @@ class CodeEditor {
1129
1337
  this.loadFile( e.dataTransfer.files[ i ] );
1130
1338
  });
1131
1339
 
1340
+ this.loadedTabs[ name ] = code;
1132
1341
  this.openedTabs[ name ] = code;
1342
+
1343
+ const tabIcon = this._getFileIcon( name );
1133
1344
 
1134
- const ext = LX.getExtension( name );
1345
+ if( this.explorer && !isNewTabButton )
1346
+ {
1347
+ this.addExplorerItem( { 'id': name, 'skipVisibility': true, 'icon': tabIcon } );
1348
+ this.explorer.frefresh( name );
1349
+ }
1135
1350
 
1136
- this.tabs.add(name, code, {
1351
+ this.tabs.add( name, code, {
1137
1352
  selected: selected,
1138
- fixed: (name === '+') ,
1353
+ fixed: isNewTabButton,
1139
1354
  title: code.title,
1140
- icon: ext == 'html' ? "fa-solid fa-code orange" :
1141
- ext == 'js' ? "images/js.png" :
1142
- ext == 'py' ? "images/py.png" : undefined,
1143
- onSelect: (e, tabname) => {
1144
-
1145
- if(tabname == '+')
1146
- {
1147
- this._onNewTab( e );
1148
- return;
1149
- }
1150
-
1151
- var cursor = cursor ?? this.cursors.children[ 0 ];
1152
- this.saveCursor( cursor, this.code.cursorState );
1153
- this.code = this.openedTabs[ tabname ];
1154
- this.restoreCursor( cursor, this.code.cursorState );
1155
- this.endSelection();
1156
- this._changeLanguageFromExtension( LX.getExtension( tabname ) );
1157
- this._refreshCodeInfo( cursor.line, cursor.position );
1158
- }
1159
- });
1355
+ icon: tabIcon,
1356
+ onSelect: this._onSelectTab.bind( this, isNewTabButton ),
1357
+ onContextMenu: this._onContextMenuTab.bind( this, isNewTabButton )
1358
+ } );
1160
1359
 
1161
1360
  // Move into the sizer..
1162
1361
  this.codeSizer.appendChild( code );
@@ -1168,11 +1367,66 @@ class CodeEditor {
1168
1367
  this.code = code;
1169
1368
  this.resetCursorPos( CodeEditor.CURSOR_LEFT | CodeEditor.CURSOR_TOP );
1170
1369
  this.processLines();
1171
- doAsync( () => this._refreshCodeInfo( 0, 0 ), 50 );
1172
1370
  }
1371
+
1372
+ this._updateDataInfoPanel( "@tab-name", name );
1373
+
1374
+ // Bc it could be overrided..
1375
+ return name;
1173
1376
  }
1174
1377
 
1175
- loadTab() {
1378
+ loadTab( name ) {
1379
+
1380
+ // Already open...
1381
+ if( this.openedTabs[ name ] )
1382
+ {
1383
+ this.tabs.select( name );
1384
+ return;
1385
+ }
1386
+
1387
+ let code = this.loadedTabs[ name ]
1388
+
1389
+ if( !code )
1390
+ {
1391
+ this.addTab( name, true );
1392
+ // Unload lines from file...
1393
+ if( this._storedLines[ name ] )
1394
+ {
1395
+ this.code.lines = this._storedLines[ name ];
1396
+ delete this._storedLines[ name ];
1397
+ }
1398
+ this._changeLanguageFromExtension( LX.getExtension( name ) );
1399
+ return;
1400
+ }
1401
+
1402
+ this.openedTabs[ name ] = code;
1403
+
1404
+ const isNewTabButton = ( name === '+' );
1405
+ const tabIcon = this._getFileIcon( name );
1406
+
1407
+ this.tabs.add(name, code, {
1408
+ selected: true,
1409
+ fixed: isNewTabButton,
1410
+ title: code.title,
1411
+ icon: tabIcon,
1412
+ onSelect: this._onSelectTab.bind( this, isNewTabButton ),
1413
+ onContextMenu: this._onContextMenuTab.bind( this, isNewTabButton )
1414
+ });
1415
+
1416
+ // Move into the sizer..
1417
+ this.codeSizer.appendChild( code );
1418
+
1419
+ this.endSelection();
1420
+
1421
+ // Select as current...
1422
+ this.code = code;
1423
+ this.resetCursorPos( CodeEditor.CURSOR_LEFT | CodeEditor.CURSOR_TOP );
1424
+ this.processLines();
1425
+ this._changeLanguageFromExtension( LX.getExtension( name ) );
1426
+ this._updateDataInfoPanel( "@tab-name", tabname );
1427
+ }
1428
+
1429
+ loadTabFromFile() {
1176
1430
  const input = document.createElement( 'input' );
1177
1431
  input.type = 'file';
1178
1432
  document.body.appendChild( input );
@@ -1191,7 +1445,7 @@ class CodeEditor {
1191
1445
  this.restartBlink();
1192
1446
  else {
1193
1447
  clearInterval( this.blinker );
1194
- this.cursors.classList.remove('show');
1448
+ this.cursors.classList.remove( 'show' );
1195
1449
  }
1196
1450
  }
1197
1451
 
@@ -1216,7 +1470,7 @@ class CodeEditor {
1216
1470
  // Left click only...
1217
1471
  if( e.button === 2 )
1218
1472
  {
1219
- this.processClick(e);
1473
+ this.processClick( e );
1220
1474
 
1221
1475
  this.canOpenContextMenu = !this.selection;
1222
1476
 
@@ -1236,21 +1490,13 @@ class CodeEditor {
1236
1490
 
1237
1491
  else if( e.type == 'mouseup' )
1238
1492
  {
1239
- if( (LX.getTime() - this.lastMouseDown) < 300 ) {
1240
- this.state.selectingText = false;
1241
- this.processClick(e);
1242
- this.endSelection();
1243
- }
1244
-
1245
- if(this.selection) this.selection.invertIfNecessary();
1246
-
1247
- this.state.selectingText = false;
1493
+ this._onMouseUp( e );
1248
1494
  }
1249
1495
 
1250
1496
  else if( e.type == 'mousemove' )
1251
1497
  {
1252
1498
  if( this.state.selectingText )
1253
- this.processSelection(e);
1499
+ this.processSelection( e );
1254
1500
  }
1255
1501
 
1256
1502
  else if ( e.type == 'click' ) // trip
@@ -1270,6 +1516,7 @@ class CodeEditor {
1270
1516
  this.resetCursorPos( CodeEditor.CURSOR_LEFT );
1271
1517
  e._shiftKey = true;
1272
1518
  this.actions['End'].callback(cursor.line, cursor, e);
1519
+ this._tripleClickSelection = true;
1273
1520
  break;
1274
1521
  }
1275
1522
  }
@@ -1300,7 +1547,24 @@ class CodeEditor {
1300
1547
  }
1301
1548
  }
1302
1549
 
1303
- processClick( e, skip_refresh = false ) {
1550
+ _onMouseUp( e ) {
1551
+
1552
+ if( (LX.getTime() - this.lastMouseDown) < 300 ) {
1553
+ this.state.selectingText = false;
1554
+ this.processClick( e );
1555
+ this.endSelection();
1556
+ }
1557
+
1558
+ if( this.selection )
1559
+ {
1560
+ this.selection.invertIfNecessary();
1561
+ }
1562
+
1563
+ this.state.selectingText = false;
1564
+ delete this._lastSelectionKeyDir;
1565
+ }
1566
+
1567
+ processClick( e ) {
1304
1568
 
1305
1569
  var cursor = this.cursors.children[ 0 ];
1306
1570
  var code_rect = this.codeScroller.getBoundingClientRect();
@@ -1317,24 +1581,51 @@ class CodeEditor {
1317
1581
  this.cursorToPosition( cursor, string.length );
1318
1582
 
1319
1583
  this.hideAutoCompleteBox();
1320
-
1321
- if( !skip_refresh )
1322
- this._refreshCodeInfo( ln, cursor.position );
1323
1584
  }
1324
1585
 
1325
- processSelection( e, keep_range ) {
1586
+ processSelection( e, keep_range, flags = CodeEditor.SELECTION_X_Y ) {
1326
1587
 
1327
1588
  var cursor = this.cursors.children[ 0 ];
1589
+ const isMouseEvent = e && ( e.constructor == MouseEvent );
1328
1590
 
1329
- if( e ) this.processClick( e, true );
1591
+ if( isMouseEvent ) this.processClick( e );
1330
1592
  if( !this.selection )
1331
1593
  this.startSelection( cursor );
1332
1594
 
1595
+ // Hide active line background
1596
+ this.code.childNodes.forEach( e => e.classList.remove( 'active-line' ) );
1597
+
1333
1598
  // Update selection
1334
- if(!keep_range)
1599
+ if( !keep_range )
1600
+ {
1601
+ let ccw = true;
1602
+
1603
+ // Check if we must change ccw or not ... (not with mouse)
1604
+ if( !isMouseEvent && this.line >= this.selection.fromY &&
1605
+ (this.line == this.selection.fromY ? this.position >= this.selection.fromX : true) )
1606
+ {
1607
+ ccw = ( e && this._lastSelectionKeyDir && ( e.key == 'ArrowRight' || e.key == 'ArrowDown' || e.key == 'End' ) );
1608
+ }
1609
+
1610
+ if( ccw )
1611
+ {
1612
+ if( flags & CodeEditor.SELECTION_X ) this.selection.fromX = cursor.position;
1613
+ if( flags & CodeEditor.SELECTION_Y ) this.selection.fromY = cursor.line;
1614
+ }
1615
+ else
1616
+ {
1617
+ if( flags & CodeEditor.SELECTION_X ) this.selection.toX = cursor.position;
1618
+ if( flags & CodeEditor.SELECTION_Y ) this.selection.toY = cursor.line;
1619
+ }
1620
+
1621
+ this._lastSelectionKeyDir = ccw;
1622
+ }
1623
+
1624
+ // Only leave if not a mouse selection...
1625
+ if( !isMouseEvent && this.selection.isEmpty() )
1335
1626
  {
1336
- this.selection.toX = cursor.position;
1337
- this.selection.toY = cursor.line;
1627
+ this.endSelection();
1628
+ return;
1338
1629
  }
1339
1630
 
1340
1631
  this.selection.chars = 0;
@@ -1477,9 +1768,10 @@ class CodeEditor {
1477
1768
  const nlines = this.code.lines.length - 1;
1478
1769
  this.selection.toX = this.code.lines[ nlines ].length;
1479
1770
  this.selection.toY = nlines;
1480
- this.processSelection( null, true );
1481
1771
  this.cursorToPosition( cursor, this.selection.toX );
1482
1772
  this.cursorToLine( cursor, this.selection.toY );
1773
+ this.processSelection( null, true );
1774
+ this.hideAutoCompleteBox();
1483
1775
  break;
1484
1776
  case 'c': // copy
1485
1777
  this._copyContent();
@@ -1489,6 +1781,7 @@ class CodeEditor {
1489
1781
  this.code.lines.splice( lidx, 0, this.code.lines[ lidx ] );
1490
1782
  this.lineDown( cursor );
1491
1783
  this.processLines();
1784
+ this.hideAutoCompleteBox();
1492
1785
  return;
1493
1786
  case 's': // save
1494
1787
  e.preventDefault();
@@ -1499,14 +1792,15 @@ class CodeEditor {
1499
1792
  return;
1500
1793
  case 'x': // cut line
1501
1794
  this._cutContent();
1795
+ this.hideAutoCompleteBox();
1502
1796
  return;
1503
1797
  case 'z': // undo
1504
1798
  if(!this.code.undoSteps.length)
1505
1799
  return;
1506
1800
  const step = this.code.undoSteps.pop();
1507
1801
  this.code.lines = step.lines;
1508
- this.restoreCursor( cursor, step.cursor );
1509
1802
  this.processLines();
1803
+ this.restoreCursor( cursor, step.cursor );
1510
1804
  return;
1511
1805
  }
1512
1806
  }
@@ -1521,6 +1815,7 @@ class CodeEditor {
1521
1815
  this.lineUp();
1522
1816
  this.processLine( lidx - 1 );
1523
1817
  this.processLine( lidx );
1818
+ this.hideAutoCompleteBox();
1524
1819
  return;
1525
1820
  case 'ArrowDown':
1526
1821
  if(this.code.lines[ lidx + 1 ] == undefined)
@@ -1529,6 +1824,7 @@ class CodeEditor {
1529
1824
  this.lineDown();
1530
1825
  this.processLine( lidx );
1531
1826
  this.processLine( lidx + 1 );
1827
+ this.hideAutoCompleteBox();
1532
1828
  return;
1533
1829
  }
1534
1830
  }
@@ -1561,7 +1857,7 @@ class CodeEditor {
1561
1857
  const enclosableKeys = ["\"", "'", "(", "{"];
1562
1858
  if( enclosableKeys.indexOf( key ) > -1 )
1563
1859
  {
1564
- if( this._encloseSelectedWordWithKey(key, lidx, cursor) )
1860
+ if( this._encloseSelectedWordWithKey( key, lidx, cursor ) )
1565
1861
  return;
1566
1862
  }
1567
1863
 
@@ -1570,8 +1866,8 @@ class CodeEditor {
1570
1866
 
1571
1867
  if( this.selection )
1572
1868
  {
1573
- this.actions['Backspace'].callback(lidx, cursor, e);
1574
- lidx = cursor.line; // Update this, since it's from the old code
1869
+ this.actions['Backspace'].callback( lidx, cursor, e );
1870
+ lidx = cursor.line;
1575
1871
  }
1576
1872
 
1577
1873
  // Append key
@@ -1668,11 +1964,15 @@ class CodeEditor {
1668
1964
  let lidx = cursor.line;
1669
1965
  let text_to_cut = "";
1670
1966
 
1967
+ this._addUndoStep( cursor );
1968
+
1671
1969
  if( !this.selection ) {
1672
1970
  text_to_cut = "\n" + this.code.lines[ cursor.line ];
1673
1971
  this.code.lines.splice( lidx, 1 );
1674
1972
  this.processLines();
1675
1973
  this.resetCursorPos( CodeEditor.CURSOR_LEFT );
1974
+ if( this.code.lines[ lidx ] == undefined )
1975
+ this.lineUp();
1676
1976
  }
1677
1977
  else {
1678
1978
 
@@ -1735,7 +2035,6 @@ class CodeEditor {
1735
2035
 
1736
2036
  const start = performance.now();
1737
2037
 
1738
- var gutter_html = "";
1739
2038
  var code_html = "";
1740
2039
 
1741
2040
  // Reset all lines content
@@ -1761,16 +2060,11 @@ class CodeEditor {
1761
2060
  // Process visible lines
1762
2061
  for( let i = this.visibleLinesViewport.x; i < this.visibleLinesViewport.y; ++i )
1763
2062
  {
1764
- gutter_html += "<span>" + (i + 1) + "</span>";
1765
2063
  code_html += this.processLine( i, true );
1766
2064
  }
1767
2065
 
1768
2066
  this.code.innerHTML = code_html;
1769
2067
 
1770
- // console.log("RANGE:", this.visibleLinesViewport);
1771
- // console.log( "Num lines processed:", (this.visibleLinesViewport.y - this.visibleLinesViewport.x), performance.now() - start );
1772
- // console.log("--------------------------------------------");
1773
-
1774
2068
  // Update scroll data
1775
2069
  this.codeScroller.scrollTop = lastScrollTop;
1776
2070
  this.code.style.top = ( this.visibleLinesViewport.x * this.lineHeight ) + "px";
@@ -1779,10 +2073,8 @@ class CodeEditor {
1779
2073
  if( this.selection )
1780
2074
  this.processSelection( null, true );
1781
2075
 
1782
- // Clear tmp vars
1783
- delete this._buildingString;
1784
- delete this._pendingString;
1785
-
2076
+ this._clearTmpVariables();
2077
+ this._setActiveLine();
1786
2078
  this.resize();
1787
2079
  }
1788
2080
 
@@ -1793,7 +2085,10 @@ class CodeEditor {
1793
2085
 
1794
2086
  const UPDATE_LINE = ( html ) => {
1795
2087
  if( !force ) // Single line update
2088
+ {
1796
2089
  this.code.childNodes[ local_line_num ].innerHTML = gutter_line + html;
2090
+ this._clearTmpVariables();
2091
+ }
1797
2092
  else // Update all lines at once
1798
2093
  return "<pre>" + ( gutter_line + html ) + "</pre>";
1799
2094
  }
@@ -1847,8 +2142,6 @@ class CodeEditor {
1847
2142
  {
1848
2143
  if( token.substr( 0, 2 ) == '/*' )
1849
2144
  this._buildingBlockComment = true;
1850
- if( token.substr( token.length - 2 ) == '*/' )
1851
- delete this._buildingBlockComment;
1852
2145
  }
1853
2146
 
1854
2147
  line_inner_html += this._evaluateToken( token, prev, next, (i == tokensToEvaluate.length - 1) );
@@ -1859,7 +2152,7 @@ class CodeEditor {
1859
2152
 
1860
2153
  _processTokens( tokens, offset = 0 ) {
1861
2154
 
1862
- if( this.highlight == 'C++' )
2155
+ if( this.highlight == 'C++' || this.highlight == 'CSS' )
1863
2156
  {
1864
2157
  var idx = tokens.slice( offset ).findIndex( ( value, index ) => this.isNumber( value ) );
1865
2158
  if( idx > -1 )
@@ -1915,8 +2208,6 @@ class CodeEditor {
1915
2208
  linestring = ogLine.substring( 0, hasCommentIdx );
1916
2209
  }
1917
2210
 
1918
- // const usesBlockComments = this.languages[ this.highlight ].blockComments ?? true;
1919
-
1920
2211
  let tokensToEvaluate = []; // store in a temp array so we know prev and next tokens...
1921
2212
 
1922
2213
  const pushToken = function( t ) {
@@ -1925,7 +2216,7 @@ class CodeEditor {
1925
2216
  tokensToEvaluate.push( t );
1926
2217
  };
1927
2218
 
1928
- let iter = linestring.matchAll(/(::|[\[\](){}<>.,;:*"'%@!/= ])/g);
2219
+ let iter = linestring.matchAll(/(\*\/|\/\*|::|[\[\](){}<>.,;:*"'%@!/= ])/g);
1929
2220
  let subtokens = iter.next();
1930
2221
  if( subtokens.value )
1931
2222
  {
@@ -1945,26 +2236,6 @@ class CodeEditor {
1945
2236
  }
1946
2237
  else tokensToEvaluate.push( linestring );
1947
2238
 
1948
- // if( usesBlockComments )
1949
- // {
1950
- // var block = false;
1951
-
1952
- // if( t.includes('/*') )
1953
- // {
1954
- // const idx = t.indexOf( '/*' );
1955
- // tokensToEvaluate.push( t.substring( 0, idx ), '/*', t.substring( idx + 2 ) );
1956
- // block |= true;
1957
- // }
1958
- // else if( t.includes('*/') )
1959
- // {
1960
- // const idx = t.indexOf( '*/' );
1961
- // tokensToEvaluate.push( t.substring( 0, idx ), '*/', t.substring( idx + 2 ) );
1962
- // block |= true;
1963
- // }
1964
-
1965
- // if( block ) continue;
1966
- // }
1967
-
1968
2239
  if( hasCommentIdx != undefined )
1969
2240
  {
1970
2241
  pushToken( ogLine.substring( hasCommentIdx ) );
@@ -2001,6 +2272,8 @@ class CodeEditor {
2001
2272
  this._buildingString = token;
2002
2273
  }
2003
2274
 
2275
+ const usesBlockComments = this.languages[ this.highlight ].blockComments ?? true;
2276
+
2004
2277
  if(token == ' ')
2005
2278
  {
2006
2279
  if( this._buildingString != undefined )
@@ -2013,7 +2286,6 @@ class CodeEditor {
2013
2286
  else
2014
2287
  {
2015
2288
  const singleLineCommentToken = this.languages[ this.highlight ].singleLineCommentToken ?? this.defaultSingleLineCommentToken;
2016
- const usesBlockComments = this.languages[ this.highlight ].blockComments ?? true;
2017
2289
 
2018
2290
  let token_classname = "";
2019
2291
  let discardToken = false;
@@ -2039,12 +2311,6 @@ class CodeEditor {
2039
2311
  else if( token.substr( 0, singleLineCommentToken.length ) == singleLineCommentToken )
2040
2312
  token_classname = "cm-com";
2041
2313
 
2042
- else if( usesBlockComments && token.substr( 0, 2 ) == '/*' )
2043
- token_classname = "cm-com";
2044
-
2045
- else if( usesBlockComments && token.substr( token.length - 2 ) == '*/' )
2046
- token_classname = "cm-com";
2047
-
2048
2314
  else if( this.isNumber( token ) || this.isNumber( token.replace(/[px]|[em]|%/g,'') ) )
2049
2315
  token_classname = "cm-dec";
2050
2316
 
@@ -2076,6 +2342,11 @@ class CodeEditor {
2076
2342
  token_classname = "cm-mtd";
2077
2343
 
2078
2344
 
2345
+ if( usesBlockComments && this._buildingBlockComment && token.substr( 0, 2 ) == '*/' )
2346
+ {
2347
+ delete this._buildingBlockComment;
2348
+ }
2349
+
2079
2350
  // We finished constructing a string
2080
2351
  if( this._buildingString && ( this._stringEnded || isLastToken ) )
2081
2352
  {
@@ -2131,6 +2402,12 @@ class CodeEditor {
2131
2402
  else if( token.lastChar == 'u' )
2132
2403
  return !(token.includes('.')) && this.isNumber( token.substring(0, token.length - 1) );
2133
2404
  }
2405
+
2406
+ else if(this.highlight == 'CSS')
2407
+ {
2408
+ if( token.lastChar == '%' )
2409
+ return this.isNumber( token.substring(0, token.length - 1) )
2410
+ }
2134
2411
 
2135
2412
  return token.length && token != ' ' && !Number.isNaN(+token);
2136
2413
  }
@@ -2208,31 +2485,43 @@ class CodeEditor {
2208
2485
  lineUp( cursor, resetLeft ) {
2209
2486
 
2210
2487
  cursor = cursor ?? this.cursors.children[ 0 ];
2488
+
2489
+ if( this.code.lines[ cursor.line - 1 ] == undefined )
2490
+ return false;
2491
+
2211
2492
  cursor.line--;
2212
2493
  cursor.line = Math.max( 0, cursor.line );
2213
2494
  this.cursorToTop( cursor, resetLeft );
2495
+
2496
+ return true;
2214
2497
  }
2215
2498
 
2216
2499
  lineDown( cursor, resetLeft ) {
2217
2500
 
2218
2501
  cursor = cursor ?? this.cursors.children[ 0 ];
2502
+
2503
+ if( this.code.lines[ cursor.line + 1 ] == undefined )
2504
+ return false;
2505
+
2219
2506
  cursor.line++;
2220
2507
  this.cursorToBottom( cursor, resetLeft );
2508
+
2509
+ return true;
2221
2510
  }
2222
2511
 
2223
2512
  restartBlink() {
2224
2513
 
2225
2514
  if( !this.code ) return;
2226
2515
 
2227
- clearInterval(this.blinker);
2228
- this.cursors.classList.add('show');
2516
+ clearInterval( this.blinker );
2517
+ this.cursors.classList.add( 'show' );
2229
2518
 
2230
- if (this.cursorBlinkRate > 0)
2519
+ if( this.cursorBlinkRate > 0 )
2231
2520
  this.blinker = setInterval(() => {
2232
- this.cursors.classList.toggle('show');
2521
+ this.cursors.classList.toggle( 'show' );
2233
2522
  }, this.cursorBlinkRate);
2234
- else if (this.cursorBlinkRate < 0)
2235
- this.cursors.classList.remove('show');
2523
+ else if( this.cursorBlinkRate < 0 )
2524
+ this.cursors.classList.remove( 'show' );
2236
2525
  }
2237
2526
 
2238
2527
  startSelection( cursor ) {
@@ -2241,10 +2530,10 @@ class CodeEditor {
2241
2530
  this.selections.innerHTML = "";
2242
2531
 
2243
2532
  // Show elements
2244
- this.selections.classList.add('show');
2533
+ this.selections.classList.add( 'show' );
2245
2534
 
2246
2535
  // Create new selection instance
2247
- this.selection = new CodeSelection(this, cursor.position, cursor.line);
2536
+ this.selection = new CodeSelection( this, cursor.position, cursor.line );
2248
2537
  }
2249
2538
 
2250
2539
  deleteSelection( cursor ) {
@@ -2261,7 +2550,7 @@ class CodeEditor {
2261
2550
 
2262
2551
  // Get linear start index
2263
2552
  let index = 0;
2264
- for(let i = 0; i <= this.selection.fromY; i++)
2553
+ for( let i = 0; i <= this.selection.fromY; i++ )
2265
2554
  index += (i == this.selection.fromY ? this.selection.fromX : this.code.lines[ i ].length);
2266
2555
 
2267
2556
  index += this.selection.fromY * separator.length;
@@ -2274,18 +2563,17 @@ class CodeEditor {
2274
2563
 
2275
2564
  this.cursorToLine( cursor, this.selection.fromY, true );
2276
2565
  this.cursorToPosition( cursor, this.selection.fromX );
2277
-
2278
2566
  this.endSelection();
2279
-
2280
2567
  this.processLines();
2281
- this._refreshCodeInfo( cursor.line, cursor.position );
2282
2568
  }
2283
2569
 
2284
2570
  endSelection() {
2285
2571
 
2286
- this.selections.classList.remove('show');
2572
+ this.selections.classList.remove( 'show' );
2287
2573
  this.selections.innerHTML = "";
2288
2574
  delete this.selection;
2575
+ delete this._tripleClickSelection;
2576
+ delete this._lastSelectionKeyDir;
2289
2577
  }
2290
2578
 
2291
2579
  cursorToRight( key, cursor ) {
@@ -2297,7 +2585,6 @@ class CodeEditor {
2297
2585
  cursor.position++;
2298
2586
 
2299
2587
  this.restartBlink();
2300
- this._refreshCodeInfo( cursor.line, cursor.position );
2301
2588
 
2302
2589
  // Add horizontal scroll
2303
2590
 
@@ -2310,7 +2597,7 @@ class CodeEditor {
2310
2597
 
2311
2598
  cursorToLeft( key, cursor ) {
2312
2599
 
2313
- if(!key) return;
2600
+ if( !key ) return;
2314
2601
  cursor = cursor ?? this.cursors.children[ 0 ];
2315
2602
  cursor._left -= this.charWidth;
2316
2603
  cursor._left = Math.max( cursor._left, 0 );
@@ -2318,7 +2605,6 @@ class CodeEditor {
2318
2605
  cursor.position--;
2319
2606
  cursor.position = Math.max( cursor.position, 0 );
2320
2607
  this.restartBlink();
2321
- this._refreshCodeInfo( cursor.line, cursor.position );
2322
2608
 
2323
2609
  // Add horizontal scroll
2324
2610
 
@@ -2340,8 +2626,6 @@ class CodeEditor {
2340
2626
  if(resetLeft)
2341
2627
  this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
2342
2628
 
2343
- this._refreshCodeInfo( cursor.line, cursor.position );
2344
-
2345
2629
  doAsync(() => {
2346
2630
  var first_line = ( this.getScrollTop() / this.lineHeight )|0;
2347
2631
  if( (cursor.line - 1) < first_line )
@@ -2359,8 +2643,6 @@ class CodeEditor {
2359
2643
  if(resetLeft)
2360
2644
  this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
2361
2645
 
2362
- this._refreshCodeInfo( cursor.line, cursor.position );
2363
-
2364
2646
  doAsync(() => {
2365
2647
  var last_line = ( ( this.codeScroller.offsetHeight + this.getScrollTop() ) / this.lineHeight )|0;
2366
2648
  if( cursor.line >= last_line )
@@ -2370,9 +2652,10 @@ class CodeEditor {
2370
2652
 
2371
2653
  cursorToString( cursor, text, reverse ) {
2372
2654
 
2655
+ if( !text.length ) return;
2373
2656
  cursor = cursor ?? this.cursors.children[ 0 ];
2374
2657
  for( let char of text )
2375
- reverse ? this.cursorToLeft(char) : this.cursorToRight(char);
2658
+ reverse ? this.cursorToLeft( char ) : this.cursorToRight( char );
2376
2659
  }
2377
2660
 
2378
2661
  cursorToPosition( cursor, position ) {
@@ -2387,7 +2670,7 @@ class CodeEditor {
2387
2670
  cursor.line = line;
2388
2671
  cursor._top = this.lineHeight * line;
2389
2672
  cursor.style.top = cursor._top + "px";
2390
- if(resetLeft) this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
2673
+ if( resetLeft ) this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
2391
2674
  }
2392
2675
 
2393
2676
  saveCursor( cursor, state = {} ) {
@@ -2396,7 +2679,7 @@ class CodeEditor {
2396
2679
  state.top = cursor._top;
2397
2680
  state.left = cursor._left;
2398
2681
  state.line = cursor.line;
2399
- state.charPos = cursor.position;
2682
+ state.position = cursor.position;
2400
2683
  return state;
2401
2684
  }
2402
2685
 
@@ -2407,9 +2690,9 @@ class CodeEditor {
2407
2690
  cursor.position = state.position ?? 0;
2408
2691
 
2409
2692
  cursor._left = state.left ?? 0;
2410
- cursor.style.left = "calc(" + (cursor._left - this.getScrollLeft()) + "px + " + this.xPadding + ")";
2693
+ cursor.style.left = "calc(" + ( cursor._left - this.getScrollLeft() ) + "px + " + this.xPadding + ")";
2411
2694
  cursor._top = state.top ?? 0;
2412
- cursor.style.top = "calc(" + (cursor._top - this.getScrollTop()) + "px)";
2695
+ cursor.style.top = "calc(" + ( cursor._top - this.getScrollTop() ) + "px)";
2413
2696
  }
2414
2697
 
2415
2698
  resetCursorPos( flag, cursor ) {
@@ -2419,14 +2702,14 @@ class CodeEditor {
2419
2702
  if( flag & CodeEditor.CURSOR_LEFT )
2420
2703
  {
2421
2704
  cursor._left = 0;
2422
- cursor.style.left = "calc(" + (-this.getScrollLeft()) + "px + " + this.xPadding + ")";
2705
+ cursor.style.left = "calc(" + ( -this.getScrollLeft() ) + "px + " + this.xPadding + ")";
2423
2706
  cursor.position = 0;
2424
2707
  }
2425
2708
 
2426
2709
  if( flag & CodeEditor.CURSOR_TOP )
2427
2710
  {
2428
2711
  cursor._top = 0;
2429
- cursor.style.top = (cursor._top - this.getScrollTop()) + "px";
2712
+ cursor.style.top = ( -this.getScrollTop() ) + "px";
2430
2713
  cursor.line = 0;
2431
2714
  }
2432
2715
  }
@@ -2434,14 +2717,14 @@ class CodeEditor {
2434
2717
  addSpaceTabs(n) {
2435
2718
 
2436
2719
  for( var i = 0; i < n; ++i ) {
2437
- this.actions['Tab'].callback();
2720
+ this.actions[ 'Tab' ].callback();
2438
2721
  }
2439
2722
  }
2440
2723
 
2441
2724
  addSpaces(n) {
2442
2725
 
2443
2726
  for( var i = 0; i < n; ++i ) {
2444
- this.root.dispatchEvent(new CustomEvent('keydown', {'detail': {
2727
+ this.root.dispatchEvent( new CustomEvent( 'keydown', { 'detail': {
2445
2728
  skip_undo: true,
2446
2729
  key: ' '
2447
2730
  }}));
@@ -2450,26 +2733,26 @@ class CodeEditor {
2450
2733
 
2451
2734
  getScrollLeft() {
2452
2735
 
2453
- if(!this.codeScroller) return 0;
2736
+ if( !this.codeScroller ) return 0;
2454
2737
  return this.codeScroller.scrollLeft;
2455
2738
  }
2456
2739
 
2457
2740
  getScrollTop() {
2458
2741
 
2459
- if(!this.codeScroller) return 0;
2742
+ if( !this.codeScroller ) return 0;
2460
2743
  return this.codeScroller.scrollTop;
2461
2744
  }
2462
2745
 
2463
2746
  setScrollLeft( value ) {
2464
2747
 
2465
- if(!this.codeScroller) return;
2748
+ if( !this.codeScroller ) return;
2466
2749
  this.codeScroller.scrollLeft = value;
2467
2750
  this.setScrollBarValue( 'horizontal', 0 );
2468
2751
  }
2469
2752
 
2470
2753
  setScrollTop( value ) {
2471
2754
 
2472
- if(!this.codeScroller) return;
2755
+ if( !this.codeScroller ) return;
2473
2756
  this.codeScroller.scrollTop = value;
2474
2757
  this.setScrollBarValue( 'vertical' );
2475
2758
  }
@@ -2601,46 +2884,70 @@ class CodeEditor {
2601
2884
  getCharAtPos( cursor, offset = 0 ) {
2602
2885
 
2603
2886
  cursor = cursor ?? this.cursors.children[ 0 ];
2604
- return this.code.lines[ cursor.line ][cursor.position + offset];
2887
+ return this.code.lines[ cursor.line ][ cursor.position + offset ];
2605
2888
  }
2606
2889
 
2607
- getWordAtPos( cursor, offset = 0 ) {
2890
+ getWordAtPos( cursor, loffset = 0, roffset ) {
2608
2891
 
2892
+ roffset = roffset ?? loffset;
2609
2893
  cursor = cursor ?? this.cursors.children[ 0 ];
2610
2894
  const col = cursor.line;
2611
- const words = this.code.lines[col];
2895
+ const words = this.code.lines[ col ];
2612
2896
 
2613
- const is_char = (char) => {
2614
- const exceptions = ['_', '#', '!'];
2615
- const code = char.charCodeAt(0);
2616
- return (exceptions.indexOf(char) > - 1) || (code > 47 && code < 58) || (code > 64 && code < 91) || (code > 96 && code < 123);
2897
+ const is_char = char => {
2898
+ const exceptions = [ '_', '#', '!' ];
2899
+ const code = char.charCodeAt( 0 );
2900
+ return (exceptions.indexOf( char ) > - 1) || (code > 47 && code < 58) || (code > 64 && code < 91) || (code > 96 && code < 123);
2617
2901
  }
2618
2902
 
2619
- let it = cursor.position + offset;
2903
+ let from = cursor.position + roffset;
2904
+ let to = cursor.position + loffset;
2905
+
2906
+ // Check left ...
2907
+
2908
+ while( words[ from ] && is_char( words[ from ] ) )
2909
+ from--;
2620
2910
 
2621
- while( words[it] && is_char(words[it]) )
2622
- it--;
2911
+ from++;
2623
2912
 
2624
- const from = it + 1;
2625
- it = cursor.position + offset;
2913
+ // Check right ...
2626
2914
 
2627
- while( words[it] && is_char(words[it]) )
2628
- it++;
2915
+ while( words[ to ] && is_char( words[ to ] ) )
2916
+ to++;
2629
2917
 
2630
- const to = it;
2918
+ // Skip spaces ...
2631
2919
 
2632
- return [words.substring( from, to ), from, to];
2920
+ let word = words.substring( from, to );
2921
+ if( word == ' ' )
2922
+ {
2923
+ if( loffset < 0 )
2924
+ {
2925
+ while( words[ from - 1 ] != undefined && words[ from - 1 ] == ' ' )
2926
+ from--;
2927
+ to++;
2928
+ word = words.substring( from, to + 1 );
2929
+ }
2930
+ else
2931
+ {
2932
+ while( words[ to ] != undefined && words[ to ] == ' ' )
2933
+ to++;
2934
+ from--;
2935
+ word = words.substring( from, to );
2936
+ }
2937
+ }
2938
+
2939
+ return [ word, from, to ];
2633
2940
  }
2634
2941
 
2635
- measureChar( char = "a", get_bb = false ) {
2942
+ _measureChar( char = "a", get_bb = false ) {
2636
2943
 
2637
- var test = document.createElement("pre");
2944
+ var test = document.createElement( "pre" );
2638
2945
  test.className = "codechar";
2639
2946
  test.innerHTML = char;
2640
- document.body.appendChild(test);
2947
+ document.body.appendChild( test );
2641
2948
  var rect = test.getBoundingClientRect();
2642
2949
  deleteElement( test );
2643
- const bb = [Math.floor(rect.width), Math.floor(rect.height)];
2950
+ const bb = [ Math.floor( rect.width ), Math.floor( rect.height ) ];
2644
2951
  return get_bb ? bb : bb[ 0 ];
2645
2952
  }
2646
2953
 
@@ -2651,44 +2958,46 @@ class CodeEditor {
2651
2958
 
2652
2959
  runScript( code ) {
2653
2960
 
2654
- var script = document.createElement('script');
2961
+ var script = document.createElement( 'script' );
2655
2962
  script.type = 'module';
2656
2963
  script.innerHTML = code;
2657
2964
  // script.src = url[ i ] + ( version ? "?version=" + version : "" );
2658
2965
  script.async = false;
2659
2966
  // script.onload = function(e) { };
2660
- document.getElementsByTagName('head')[ 0 ].appendChild(script);
2967
+ document.getElementsByTagName( 'head' )[ 0 ].appendChild( script );
2661
2968
  }
2662
2969
 
2663
2970
  toJSONFormat( text ) {
2664
2971
 
2665
- let params = text.split(":");
2972
+ let params = text.split( ':' );
2666
2973
 
2667
2974
  for(let i = 0; i < params.length; i++) {
2668
- let key = params[ i ].split(',');
2669
- if(key.length > 1) {
2670
- if(key[key.length-1].includes("]"))
2975
+ let key = params[ i ].split( ',' );
2976
+ if( key.length > 1 )
2977
+ {
2978
+ if( key[ key.length - 1 ].includes( ']' ))
2671
2979
  continue;
2672
- key = key[key.length-1];
2980
+ key = key[ key.length - 1 ];
2673
2981
  }
2674
- else if(key[ 0 ].includes("}"))
2982
+ else if( key[ 0 ].includes( '}' ) )
2675
2983
  continue;
2676
2984
  else
2677
2985
  key = key[ 0 ];
2678
- key = key.replaceAll(/[{}\n\r]/g,"").replaceAll(" ","")
2679
- if(key[ 0 ] != '"' && key[key.length - 1] != '"') {
2680
- params[ i ] = params[ i ].replace(key, '"' + key + '"');
2986
+ key = key.replaceAll( /[{}\n\r]/g, '' ).replaceAll( ' ', '' )
2987
+ if( key[ 0 ] != '"' && key[ key.length - 1 ] != '"')
2988
+ {
2989
+ params[ i ] = params[ i ].replace( key, '"' + key + '"' );
2681
2990
  }
2682
2991
  }
2683
2992
 
2684
- text = params.join(':');
2993
+ text = params.join( ':' );
2685
2994
 
2686
2995
  try {
2687
- let json = JSON.parse(text);
2688
- return JSON.stringify(json, undefined, 4);
2996
+ let json = JSON.parse( text );
2997
+ return JSON.stringify( json, undefined, 4 );
2689
2998
  }
2690
- catch(e) {
2691
- alert("Invalid JSON format");
2999
+ catch( e ) {
3000
+ alert( "Invalid JSON format" );
2692
3001
  return;
2693
3002
  }
2694
3003
  }
@@ -2728,10 +3037,10 @@ class CodeEditor {
2728
3037
  if( !s.toLowerCase().includes(word.toLowerCase()) )
2729
3038
  continue;
2730
3039
 
2731
- var pre = document.createElement('pre');
3040
+ var pre = document.createElement( 'pre' );
2732
3041
  this.autocomplete.appendChild(pre);
2733
3042
 
2734
- var icon = document.createElement('a');
3043
+ var icon = document.createElement( 'a' );
2735
3044
 
2736
3045
  if( this._mustHightlightWord( s, this.utils ) )
2737
3046
  icon.className = "fa fa-cube";
@@ -2784,7 +3093,7 @@ class CodeEditor {
2784
3093
  hideAutoCompleteBox() {
2785
3094
 
2786
3095
  this.isAutoCompleteActive = false;
2787
- this.autocomplete.classList.remove('show');
3096
+ this.autocomplete.classList.remove( 'show' );
2788
3097
  }
2789
3098
 
2790
3099
  autoCompleteWord( cursor, suggestion ) {
@@ -2792,7 +3101,7 @@ class CodeEditor {
2792
3101
  if( !this.isAutoCompleteActive )
2793
3102
  return;
2794
3103
 
2795
- let [suggestedWord, idx] = this.getSelectedAutoComplete();
3104
+ let [suggestedWord, idx] = this._getSelectedAutoComplete();
2796
3105
  suggestedWord = suggestion ?? suggestedWord;
2797
3106
 
2798
3107
  const [word, start, end] = this.getWordAtPos( cursor, -1 );
@@ -2807,7 +3116,7 @@ class CodeEditor {
2807
3116
  this.hideAutoCompleteBox();
2808
3117
  }
2809
3118
 
2810
- getSelectedAutoComplete() {
3119
+ _getSelectedAutoComplete() {
2811
3120
 
2812
3121
  if( !this.isAutoCompleteActive )
2813
3122
  return;
@@ -2826,12 +3135,12 @@ class CodeEditor {
2826
3135
  }
2827
3136
  }
2828
3137
 
2829
- moveArrowSelectedAutoComplete( dir ) {
3138
+ _moveArrowSelectedAutoComplete( dir ) {
2830
3139
 
2831
3140
  if( !this.isAutoCompleteActive )
2832
3141
  return;
2833
3142
 
2834
- const [word, idx] = this.getSelectedAutoComplete();
3143
+ const [word, idx] = this._getSelectedAutoComplete();
2835
3144
  const offset = dir == 'down' ? 1 : -1;
2836
3145
 
2837
3146
  if( dir == 'down' ) {
@@ -2840,12 +3149,51 @@ class CodeEditor {
2840
3149
  if( (idx + offset) < 0 ) return;
2841
3150
  }
2842
3151
 
2843
- this.autocomplete.scrollTop += offset * 18;
3152
+ this.autocomplete.scrollTop += offset * 20;
2844
3153
 
2845
3154
  // Remove selected from the current word and add it to the next one
2846
3155
  this.autocomplete.childNodes[ idx ].classList.remove('selected');
2847
3156
  this.autocomplete.childNodes[ idx + offset ].classList.add('selected');
2848
3157
  }
3158
+
3159
+ _updateDataInfoPanel( signal, value ) {
3160
+
3161
+ if( !this.skipCodeInfo )
3162
+ {
3163
+ LX.emit( signal, value );
3164
+ }
3165
+ }
3166
+
3167
+ _setActiveLine( number ) {
3168
+
3169
+ number = number ?? this.state.activeLine;
3170
+
3171
+ this._updateDataInfoPanel( "@cursor-line", "Ln " + ( number + 1 ) );
3172
+
3173
+ const old_local = this.toLocalLine( this.state.activeLine );
3174
+ let line = this.code.childNodes[ old_local ];
3175
+
3176
+ if( !line )
3177
+ return;
3178
+
3179
+ line.classList.remove( 'active-line' );
3180
+
3181
+ // Set new active
3182
+ {
3183
+ this.state.activeLine = number;
3184
+
3185
+ const new_local = this.toLocalLine( number );
3186
+ line = this.code.childNodes[ new_local ];
3187
+ if( line ) line.classList.add( 'active-line' );
3188
+ }
3189
+ }
3190
+
3191
+ _clearTmpVariables() {
3192
+
3193
+ delete this._buildingString;
3194
+ delete this._pendingString;
3195
+ delete this._buildingBlockComment;
3196
+ }
2849
3197
  }
2850
3198
 
2851
3199
  LX.CodeEditor = CodeEditor;