lexgui 0.1.19 → 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
 
@@ -235,7 +256,7 @@ class CodeEditor {
235
256
  if( Object.keys( this.openedTabs ).length < 2 )
236
257
  {
237
258
  clearInterval( this.blinker );
238
- this.cursors.classList.remove('show');
259
+ this.cursors.classList.remove( 'show' );
239
260
  }
240
261
  } } );
241
262
  this.tabs.root.addEventListener( 'dblclick', (e) => {
@@ -271,6 +292,9 @@ class CodeEditor {
271
292
  this.root.addEventListener( 'click', this.processMouse.bind(this) );
272
293
  this.root.addEventListener( 'contextmenu', this.processMouse.bind(this) );
273
294
 
295
+ // Add mouseup event to document as well to detect when selections end
296
+ document.body.addEventListener( 'mouseup', this._onMouseUp.bind(this) );
297
+
274
298
  // Cursors and selection
275
299
 
276
300
  this.cursors = document.createElement( 'div' );
@@ -293,13 +317,32 @@ class CodeEditor {
293
317
  cursor.style.left = this.xPadding;
294
318
  cursor._top = 0;
295
319
  cursor.style.top = cursor._top + "px";
296
- cursor.position = 0;
320
+ cursor._position = 0;
297
321
  cursor._line = 0;
298
322
  cursor.print = (function() { console.log( this.line, this.position ) }).bind( cursor );
299
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
+
300
332
  Object.defineProperty( cursor, 'line', {
301
333
  get: (v) => { return this._line },
302
- 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
+ }
303
346
  } );
304
347
 
305
348
  this.cursors.appendChild( cursor );
@@ -400,7 +443,8 @@ class CodeEditor {
400
443
 
401
444
  this.state = {
402
445
  focused: false,
403
- selectingText: false
446
+ selectingText: false,
447
+ activeLine: null
404
448
  }
405
449
 
406
450
  // Code
@@ -415,7 +459,7 @@ class CodeEditor {
415
459
  this.maxUndoSteps = 16;
416
460
  this.lineHeight = 20;
417
461
  this.defaultSingleLineCommentToken = "//";
418
- this.charWidth = 8; //this.measureChar();
462
+ this.charWidth = 7; //this._measureChar();
419
463
  this._lastTime = null;
420
464
 
421
465
  this.pairKeys = {
@@ -443,6 +487,7 @@ class CodeEditor {
443
487
  'WGSL': { ext: 'wgsl' },
444
488
  'JSON': { ext: 'json' },
445
489
  'XML': { ext: 'xml' },
490
+ 'Rust': { ext: 'rs' },
446
491
  'Python': { ext: 'py', singleLineCommentToken: '#' },
447
492
  'HTML': { ext: 'html' },
448
493
  'Batch': { ext: 'bat', blockComments: false, singleLineCommentToken: '::' }
@@ -458,7 +503,7 @@ class CodeEditor {
458
503
  'JavaScript': ['var', 'let', 'const', 'this', 'in', 'of', 'true', 'false', 'new', 'function', 'NaN', 'static', 'class', 'constructor', 'null', 'typeof', 'debugger', 'abstract',
459
504
  'arguments', 'extends', 'instanceof'],
460
505
  'C++': ['int', 'float', 'double', 'bool', 'char', 'wchar_t', 'const', 'static_cast', 'dynamic_cast', 'new', 'delete', 'void', 'true', 'false', 'auto', 'struct', 'typedef', 'nullptr',
461
- 'NULL', 'unsigned'],
506
+ 'NULL', 'unsigned', 'namespace'],
462
507
  'JSON': ['true', 'false'],
463
508
  'GLSL': ['true', 'false', 'function', 'int', 'float', 'vec2', 'vec3', 'vec4', 'mat2x2', 'mat3x3', 'mat4x4', 'struct'],
464
509
  'CSS': ['body', 'html', 'canvas', 'div', 'input', 'span', '.'],
@@ -466,6 +511,8 @@ class CodeEditor {
466
511
  'sampler', 'sampler_comparison', 'texture_depth_2d', 'texture_depth_2d_array', 'texture_depth_cube', 'texture_depth_cube_array', 'texture_depth_multisampled_2d',
467
512
  'texture_external', 'texture_1d', 'texture_2d', 'texture_2d_array', 'texture_3d', 'texture_cube', 'texture_cube_array', 'texture_storage_1d', 'texture_storage_2d',
468
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'],
469
516
  'Python': ['False', 'def', 'None', 'True', 'in', 'is', 'and', 'lambda', 'nonlocal', 'not', 'or'],
470
517
  'Batch': ['set', 'SET', 'echo', 'ECHO', 'off', 'OFF', 'del', 'DEL', 'defined', 'DEFINED', 'setlocal', 'SETLOCAL', 'enabledelayedexpansion', 'ENABLEDELAYEDEXPANSION', 'driverquery',
471
518
  'DRIVERQUERY', 'print', 'PRINT'],
@@ -483,6 +530,7 @@ class CodeEditor {
483
530
  };
484
531
  this.types = {
485
532
  'JavaScript': ['Object', 'String', 'Function', 'Boolean', 'Symbol', 'Error', 'Number', 'TextEncoder', 'TextDecoder'],
533
+ 'Rust': ['u128'],
486
534
  'Python': ['int', 'type', 'float', 'map', 'list', 'ArithmeticError', 'AssertionError', 'AttributeError', 'Exception', 'EOFError', 'FloatingPointError', 'GeneratorExit',
487
535
  'ImportError', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'NotImplementedError', 'OSError',
488
536
  'OverflowError', 'ReferenceError', 'RuntimeError', 'StopIteration', 'SyntaxError', 'TabError', 'SystemError', 'SystemExit', 'TypeError', 'UnboundLocalError',
@@ -497,9 +545,10 @@ class CodeEditor {
497
545
  };
498
546
  this.statementsAndDeclarations = {
499
547
  'JavaScript': ['for', 'if', 'else', 'case', 'switch', 'return', 'while', 'continue', 'break', 'do', 'import', 'from', 'throw', 'async', 'try', 'catch', 'await'],
500
- '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'],
501
549
  'GLSL': ['for', 'if', 'else', 'return', 'continue', 'break'],
502
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'],
503
552
  'Python': ['if', 'raise', 'del', 'import', 'return', 'elif', 'try', 'else', 'while', 'as', 'except', 'with', 'assert', 'finally', 'yield', 'break', 'for', 'class', 'continue',
504
553
  'global', 'pass'],
505
554
  'Batch': ['if', 'IF', 'for', 'FOR', 'in', 'IN', 'do', 'DO', 'call', 'CALL', 'goto', 'GOTO', 'exit', 'EXIT']
@@ -511,6 +560,7 @@ class CodeEditor {
511
560
  'GLSL': ['[', ']', '{', '}', '(', ')'],
512
561
  'WGSL': ['[', ']', '{', '}', '(', ')', '->'],
513
562
  'CSS': ['{', '}', '(', ')', '*'],
563
+ 'Rust': ['<', '>', '[', ']', '(', ')', '='],
514
564
  'Python': ['<', '>', '[', ']', '(', ')', '='],
515
565
  'Batch': ['[', ']', '(', ')', '%'],
516
566
  'HTML': ['<', '>', '/']
@@ -610,7 +660,6 @@ class CodeEditor {
610
660
 
611
661
  this.resetCursorPos( CodeEditor.CURSOR_LEFT );
612
662
  if(idx > 0) this.cursorToString( cursor, prestring );
613
- this._refreshCodeInfo( cursor.line, cursor.position );
614
663
  this.setScrollLeft( 0 );
615
664
 
616
665
  if( e.shiftKey && !e.cancelShift )
@@ -626,7 +675,7 @@ class CodeEditor {
626
675
  this.selection.selectInline( idx, cursor.line, this.measureString( string ) );
627
676
  else
628
677
  {
629
- this.processSelection();
678
+ this.processSelection( e );
630
679
  }
631
680
  } else if( !e.keepSelection )
632
681
  this.endSelection();
@@ -634,7 +683,7 @@ class CodeEditor {
634
683
 
635
684
  this.action( 'End', false, ( ln, cursor, e ) => {
636
685
 
637
- if( e.shiftKey || e._shiftKey ) {
686
+ if( ( e.shiftKey || e._shiftKey ) && !e.cancelShift ) {
638
687
 
639
688
  var string = this.code.lines[ ln ].substring( cursor.position );
640
689
  if( !this.selection )
@@ -645,9 +694,9 @@ class CodeEditor {
645
694
  {
646
695
  this.resetCursorPos( CodeEditor.CURSOR_LEFT );
647
696
  this.cursorToString( cursor, this.code.lines[ ln ] );
648
- this.processSelection();
697
+ this.processSelection( e );
649
698
  }
650
- } else
699
+ } else if( !e.keepSelection )
651
700
  this.endSelection();
652
701
 
653
702
  this.resetCursorPos( CodeEditor.CURSOR_LEFT );
@@ -706,16 +755,14 @@ class CodeEditor {
706
755
  if( !this.selection )
707
756
  this.startSelection( cursor );
708
757
 
709
- this.selection.toY = ( this.selection.toY > 0 ) ? ( this.selection.toY - 1 ) : 0;
710
- this.cursorToLine( cursor, this.selection.toY );
758
+ this.lineUp();
711
759
 
712
760
  var letter = this.getCharAtPos( cursor );
713
761
  if( !letter ) {
714
- this.selection.toX = this.code.lines[ cursor.line ].length;
715
- this.cursorToPosition( cursor, this.selection.toX );
762
+ this.cursorToPosition( cursor, this.code.lines[ cursor.line ].length );
716
763
  }
717
764
 
718
- this.processSelection( null, true );
765
+ this.processSelection( e, false );
719
766
 
720
767
  } else {
721
768
  this.endSelection();
@@ -728,7 +775,7 @@ class CodeEditor {
728
775
  // Move up autocomplete selection
729
776
  else
730
777
  {
731
- this.moveArrowSelectedAutoComplete('up');
778
+ this._moveArrowSelectedAutoComplete('up');
732
779
  }
733
780
  });
734
781
 
@@ -741,16 +788,14 @@ class CodeEditor {
741
788
  if( !this.selection )
742
789
  this.startSelection( cursor );
743
790
 
744
- this.selection.toY = this.selection.toY < this.code.lines.length - 1 ? this.selection.toY + 1 : this.code.lines.length - 1;
745
- this.cursorToLine( cursor, this.selection.toY );
791
+ this.lineDown( cursor );
746
792
 
747
793
  var letter = this.getCharAtPos( cursor );
748
794
  if( !letter ) {
749
- this.selection.toX = Math.max(this.code.lines[ cursor.line ].length - 1, 0);
750
- this.cursorToPosition(cursor, this.selection.toX);
795
+ this.cursorToPosition( cursor, Math.max(this.code.lines[ cursor.line ].length - 1, 0) );
751
796
  }
752
797
 
753
- this.processSelection( null, true );
798
+ this.processSelection( e );
754
799
  } else {
755
800
 
756
801
  if( this.code.lines[ ln + 1 ] == undefined )
@@ -765,12 +810,16 @@ class CodeEditor {
765
810
  // Move down autocomplete selection
766
811
  else
767
812
  {
768
- this.moveArrowSelectedAutoComplete('down');
813
+ this._moveArrowSelectedAutoComplete('down');
769
814
  }
770
815
  });
771
816
 
772
817
  this.action( 'ArrowLeft', false, ( ln, cursor, e ) => {
773
818
 
819
+ // Nothing to do..
820
+ if( cursor.line == 0 && cursor.position == 0 )
821
+ return;
822
+
774
823
  if( e.metaKey ) { // Apple devices (Command)
775
824
  e.preventDefault();
776
825
  this.actions[ 'Home' ].callback( ln, cursor, e );
@@ -778,29 +827,29 @@ class CodeEditor {
778
827
  else if( e.ctrlKey ) {
779
828
  // Get next word
780
829
  const [word, from, to] = this.getWordAtPos( cursor, -1 );
781
- var diff = Math.max(cursor.position - from, 1);
782
- 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 );
783
840
  // Selections...
784
841
  if( e.shiftKey ) { if( !this.selection ) this.startSelection( cursor ); }
785
842
  else this.endSelection();
786
- this.cursorToString(cursor, substr, true);
787
- if( e.shiftKey ) this.processSelection();
843
+ this.cursorToString( cursor, substr, true );
844
+ if( e.shiftKey ) this.processSelection( e, false, true );
788
845
  }
789
846
  else {
790
847
  var letter = this.getCharAtPos( cursor, -1 );
791
848
  if( letter ) {
792
849
  if( e.shiftKey ) {
793
850
  if( !this.selection ) this.startSelection( cursor );
794
- if( ( ( cursor.position - 1 ) < this.selection.fromX ) && this.selection.sameLine() )
795
- this.selection.fromX--;
796
- else if( ( cursor.position - 1 ) == this.selection.fromX && this.selection.sameLine() ) {
797
- this.cursorToLeft( letter, cursor );
798
- this.endSelection();
799
- return;
800
- }
801
- else this.selection.toX--;
802
851
  this.cursorToLeft( letter, cursor );
803
- this.processSelection( null, true );
852
+ this.processSelection( e, false, CodeEditor.SELECTION_X );
804
853
  }
805
854
  else {
806
855
  if( !this.selection ) {
@@ -819,55 +868,48 @@ class CodeEditor {
819
868
  }
820
869
  else if( cursor.line > 0 ) {
821
870
 
822
- if( e.shiftKey ) {
823
- if( !this.selection ) this.startSelection( cursor );
824
- }
871
+ if( e.shiftKey && !this.selection ) this.startSelection( cursor );
825
872
 
826
873
  this.lineUp( cursor );
874
+
875
+ e.cancelShift = e.keepSelection = true;
827
876
  this.actions[ 'End' ].callback( cursor.line, cursor, e );
877
+ delete e.cancelShift; delete e.keepSelection;
828
878
 
829
- if( e.shiftKey ) {
830
- this.selection.toX = cursor.position;
831
- this.selection.toY--;
832
- this.processSelection( null, true );
833
- }
879
+ if( e.shiftKey ) this.processSelection( e, false );
834
880
  }
835
881
  }
836
882
  });
837
883
 
838
884
  this.action( 'ArrowRight', false, ( ln, cursor, e ) => {
839
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
+
840
891
  if( e.metaKey ) { // Apple devices (Command)
841
892
  e.preventDefault();
842
893
  this.actions[ 'End' ].callback( ln, cursor );
843
894
  } else if( e.ctrlKey ) {
844
895
  // Get next word
845
896
  const [ word, from, to ] = this.getWordAtPos( cursor );
897
+ // If no length, we change line..
898
+ if( !word.length ) this.lineDown( cursor, true );
846
899
  var diff = cursor.position - from;
847
900
  var substr = word.substr( diff );
848
901
  // Selections...
849
902
  if( e.shiftKey ) { if( !this.selection ) this.startSelection( cursor ); }
850
903
  else this.endSelection();
851
904
  this.cursorToString( cursor, substr);
852
- if( e.shiftKey ) this.processSelection();
905
+ if( e.shiftKey ) this.processSelection( e );
853
906
  } else {
854
907
  var letter = this.getCharAtPos( cursor );
855
908
  if( letter ) {
856
909
  if( e.shiftKey ) {
857
910
  if( !this.selection ) this.startSelection( cursor );
858
- var keep_range = false;
859
- if( cursor.position == this.selection.fromX ) {
860
- if( ( cursor.position + 1 ) == this.selection.toX && this.selection.sameLine() ) {
861
- this.cursorToRight( letter, cursor );
862
- this.endSelection();
863
- return;
864
- } else if( cursor.position < this.selection.toX ) {
865
- this.selection.fromX++;
866
- keep_range = true;
867
- } else this.selection.toX++;
868
- }
869
911
  this.cursorToRight( letter, cursor );
870
- this.processSelection( null, keep_range );
912
+ this.processSelection( e, false, CodeEditor.SELECTION_X );
871
913
  }else{
872
914
  if( !this.selection ) {
873
915
  this.cursorToRight( letter, cursor );
@@ -888,18 +930,11 @@ class CodeEditor {
888
930
 
889
931
  if( e.shiftKey ) {
890
932
  if( !this.selection ) this.startSelection( cursor );
891
- e.cancelShift = true;
892
- e.keepSelection = true;
893
933
  }
894
934
 
895
- this.lineDown( cursor );
896
- this.actions['Home'].callback( cursor.line, cursor, e );
935
+ this.lineDown( cursor, true );
897
936
 
898
- if( e.shiftKey ) {
899
- this.selection.toX = cursor.position;
900
- this.selection.toY++;
901
- this.processSelection( null, true );
902
- }
937
+ if( e.shiftKey ) this.processSelection( e, false );
903
938
 
904
939
  this.hideAutoCompleteBox();
905
940
  }
@@ -919,6 +954,28 @@ class CodeEditor {
919
954
  // Create inspector panel
920
955
  let panel = this._createPanelInfo();
921
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
+ });
922
979
  }
923
980
 
924
981
  static getInstances()
@@ -1106,7 +1163,7 @@ class CodeEditor {
1106
1163
 
1107
1164
  this.code.language = lang;
1108
1165
  this.highlight = lang;
1109
- this._refreshCodeInfo();
1166
+ this._updateDataInfoPanel( "@highlight", lang );
1110
1167
  this.processLines();
1111
1168
 
1112
1169
  const ext = this.languages[ lang ].ext;
@@ -1144,23 +1201,13 @@ class CodeEditor {
1144
1201
  if( !ext )
1145
1202
  return this._changeLanguage( this.code.language );
1146
1203
 
1147
- switch( ext.toLowerCase() )
1204
+ for( let l in this.languages )
1148
1205
  {
1149
- case 'js': return this._changeLanguage( 'JavaScript' );
1150
- case 'cpp': return this._changeLanguage( 'C++' );
1151
- case 'h': return this._changeLanguage( 'C++' );
1152
- case 'glsl': return this._changeLanguage( 'GLSL' );
1153
- case 'css': return this._changeLanguage( 'CSS' );
1154
- case 'json': return this._changeLanguage( 'JSON' );
1155
- case 'xml': return this._changeLanguage( 'XML' );
1156
- case 'wgsl': return this._changeLanguage( 'WGSL' );
1157
- case 'py': return this._changeLanguage( 'Python' );
1158
- case 'bat': return this._changeLanguage( 'Batch' );
1159
- case 'html': return this._changeLanguage( 'HTML' );
1160
- case 'txt':
1161
- default:
1162
- this._changeLanguage( 'Plain Text' );
1206
+ if( this.languages[l].ext == ext )
1207
+ return this._changeLanguage( l );
1163
1208
  }
1209
+
1210
+ this._changeLanguage( 'Plain Text' );
1164
1211
  }
1165
1212
 
1166
1213
  _createPanelInfo() {
@@ -1168,34 +1215,23 @@ class CodeEditor {
1168
1215
  if( !this.skipCodeInfo )
1169
1216
  {
1170
1217
  let panel = new LX.Panel({ className: "lexcodetabinfo", width: "calc(100%)", height: "auto" });
1171
- panel.ln = 0;
1172
- panel.col = 0;
1173
-
1174
- this._refreshCodeInfo = ( ln = panel.ln, col = panel.col ) => {
1175
- panel.ln = ln + 1;
1176
- panel.col = col + 1;
1177
- panel.clear();
1178
- panel.sameLine();
1179
- panel.addLabel( this.code.title, { float: 'right' });
1180
- panel.addLabel( "Ln " + panel.ln, { width: "64px" });
1181
- panel.addLabel( "Col " + panel.col, { width: "64px" });
1182
- panel.addButton( "<b>{ }</b>", this.highlight, ( value, event ) => {
1183
- LX.addContextMenu( "Language", event, m => {
1184
- for( const lang of Object.keys(this.languages) )
1185
- m.add( lang, this._changeLanguage.bind(this) );
1186
- });
1187
- }, { width: "25%", nameWidth: "15%" });
1188
- panel.endLine();
1189
- };
1190
1218
 
1191
- 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();
1192
1230
 
1193
1231
  return panel;
1194
1232
  }
1195
1233
  else
1196
1234
  {
1197
- this._refreshCodeInfo = () => {};
1198
-
1199
1235
  doAsync( () => {
1200
1236
 
1201
1237
  // Change css a little bit...
@@ -1212,10 +1248,10 @@ class CodeEditor {
1212
1248
  const isNewTabButton = name ? ( name === '+' ) : false;
1213
1249
  const ext = extension ?? LX.getExtension( name );
1214
1250
  return ext == 'html' ? "fa-solid fa-code orange" :
1215
- ext == "css" ? "fa-solid fa-hashtag dodgerblue" :
1216
- ext == "xml" ? "fa-solid fa-rss orange" :
1217
- ext == "bat" ? "fa-brands fa-windows lightblue" :
1218
- [ "js", "py", "json", "cpp" ].indexOf( ext ) > -1 ? "images/" + ext + ".png" :
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" :
1219
1255
  !isNewTabButton ? "fa-solid fa-align-left gray" : undefined;
1220
1256
  }
1221
1257
 
@@ -1229,6 +1265,37 @@ class CodeEditor {
1229
1265
  });
1230
1266
  }
1231
1267
 
1268
+ _onSelectTab( isNewTabButton, event, name, ) {
1269
+
1270
+ if( isNewTabButton )
1271
+ {
1272
+ this._onNewTab( event );
1273
+ return;
1274
+ }
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
+
1232
1299
  addTab( name, selected, title ) {
1233
1300
 
1234
1301
  // If already loaded, set new name...
@@ -1281,28 +1348,14 @@ class CodeEditor {
1281
1348
  this.explorer.frefresh( name );
1282
1349
  }
1283
1350
 
1284
- this.tabs.add(name, code, {
1351
+ this.tabs.add( name, code, {
1285
1352
  selected: selected,
1286
1353
  fixed: isNewTabButton,
1287
1354
  title: code.title,
1288
1355
  icon: tabIcon,
1289
- onSelect: (e, tabname) => {
1290
-
1291
- if( isNewTabButton )
1292
- {
1293
- this._onNewTab( e );
1294
- return;
1295
- }
1296
-
1297
- var cursor = cursor ?? this.cursors.children[ 0 ];
1298
- this.saveCursor( cursor, this.code.cursorState );
1299
- this.code = this.loadedTabs[ tabname ];
1300
- this.restoreCursor( cursor, this.code.cursorState );
1301
- this.endSelection();
1302
- this._changeLanguageFromExtension( LX.getExtension( tabname ) );
1303
- this._refreshCodeInfo( cursor.line, cursor.position );
1304
- }
1305
- });
1356
+ onSelect: this._onSelectTab.bind( this, isNewTabButton ),
1357
+ onContextMenu: this._onContextMenuTab.bind( this, isNewTabButton )
1358
+ } );
1306
1359
 
1307
1360
  // Move into the sizer..
1308
1361
  this.codeSizer.appendChild( code );
@@ -1314,9 +1367,10 @@ class CodeEditor {
1314
1367
  this.code = code;
1315
1368
  this.resetCursorPos( CodeEditor.CURSOR_LEFT | CodeEditor.CURSOR_TOP );
1316
1369
  this.processLines();
1317
- doAsync( () => this._refreshCodeInfo( 0, 0 ), 50 );
1318
1370
  }
1319
1371
 
1372
+ this._updateDataInfoPanel( "@tab-name", name );
1373
+
1320
1374
  // Bc it could be overrided..
1321
1375
  return name;
1322
1376
  }
@@ -1355,22 +1409,8 @@ class CodeEditor {
1355
1409
  fixed: isNewTabButton,
1356
1410
  title: code.title,
1357
1411
  icon: tabIcon,
1358
- onSelect: (e, tabname) => {
1359
-
1360
- if( isNewTabButton )
1361
- {
1362
- this._onNewTab( e );
1363
- return;
1364
- }
1365
-
1366
- var cursor = cursor ?? this.cursors.children[ 0 ];
1367
- this.saveCursor( cursor, this.code.cursorState );
1368
- this.code = this.loadedTabs[ tabname ];
1369
- this.restoreCursor( cursor, this.code.cursorState );
1370
- this.endSelection();
1371
- this._changeLanguageFromExtension( LX.getExtension( tabname ) );
1372
- this._refreshCodeInfo( cursor.line, cursor.position );
1373
- }
1412
+ onSelect: this._onSelectTab.bind( this, isNewTabButton ),
1413
+ onContextMenu: this._onContextMenuTab.bind( this, isNewTabButton )
1374
1414
  });
1375
1415
 
1376
1416
  // Move into the sizer..
@@ -1383,7 +1423,7 @@ class CodeEditor {
1383
1423
  this.resetCursorPos( CodeEditor.CURSOR_LEFT | CodeEditor.CURSOR_TOP );
1384
1424
  this.processLines();
1385
1425
  this._changeLanguageFromExtension( LX.getExtension( name ) );
1386
- doAsync( () => this._refreshCodeInfo( 0, 0 ), 50 );
1426
+ this._updateDataInfoPanel( "@tab-name", tabname );
1387
1427
  }
1388
1428
 
1389
1429
  loadTabFromFile() {
@@ -1405,7 +1445,7 @@ class CodeEditor {
1405
1445
  this.restartBlink();
1406
1446
  else {
1407
1447
  clearInterval( this.blinker );
1408
- this.cursors.classList.remove('show');
1448
+ this.cursors.classList.remove( 'show' );
1409
1449
  }
1410
1450
  }
1411
1451
 
@@ -1430,7 +1470,7 @@ class CodeEditor {
1430
1470
  // Left click only...
1431
1471
  if( e.button === 2 )
1432
1472
  {
1433
- this.processClick(e);
1473
+ this.processClick( e );
1434
1474
 
1435
1475
  this.canOpenContextMenu = !this.selection;
1436
1476
 
@@ -1450,21 +1490,13 @@ class CodeEditor {
1450
1490
 
1451
1491
  else if( e.type == 'mouseup' )
1452
1492
  {
1453
- if( (LX.getTime() - this.lastMouseDown) < 300 ) {
1454
- this.state.selectingText = false;
1455
- this.processClick(e);
1456
- this.endSelection();
1457
- }
1458
-
1459
- if(this.selection) this.selection.invertIfNecessary();
1460
-
1461
- this.state.selectingText = false;
1493
+ this._onMouseUp( e );
1462
1494
  }
1463
1495
 
1464
1496
  else if( e.type == 'mousemove' )
1465
1497
  {
1466
1498
  if( this.state.selectingText )
1467
- this.processSelection(e);
1499
+ this.processSelection( e );
1468
1500
  }
1469
1501
 
1470
1502
  else if ( e.type == 'click' ) // trip
@@ -1515,7 +1547,24 @@ class CodeEditor {
1515
1547
  }
1516
1548
  }
1517
1549
 
1518
- 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 ) {
1519
1568
 
1520
1569
  var cursor = this.cursors.children[ 0 ];
1521
1570
  var code_rect = this.codeScroller.getBoundingClientRect();
@@ -1532,24 +1581,51 @@ class CodeEditor {
1532
1581
  this.cursorToPosition( cursor, string.length );
1533
1582
 
1534
1583
  this.hideAutoCompleteBox();
1535
-
1536
- if( !skip_refresh )
1537
- this._refreshCodeInfo( ln, cursor.position );
1538
1584
  }
1539
1585
 
1540
- processSelection( e, keep_range ) {
1586
+ processSelection( e, keep_range, flags = CodeEditor.SELECTION_X_Y ) {
1541
1587
 
1542
1588
  var cursor = this.cursors.children[ 0 ];
1589
+ const isMouseEvent = e && ( e.constructor == MouseEvent );
1543
1590
 
1544
- if( e ) this.processClick( e, true );
1591
+ if( isMouseEvent ) this.processClick( e );
1545
1592
  if( !this.selection )
1546
1593
  this.startSelection( cursor );
1547
1594
 
1595
+ // Hide active line background
1596
+ this.code.childNodes.forEach( e => e.classList.remove( 'active-line' ) );
1597
+
1548
1598
  // Update selection
1549
- if(!keep_range)
1599
+ if( !keep_range )
1550
1600
  {
1551
- this.selection.toX = cursor.position;
1552
- this.selection.toY = cursor.line;
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() )
1626
+ {
1627
+ this.endSelection();
1628
+ return;
1553
1629
  }
1554
1630
 
1555
1631
  this.selection.chars = 0;
@@ -1692,9 +1768,10 @@ class CodeEditor {
1692
1768
  const nlines = this.code.lines.length - 1;
1693
1769
  this.selection.toX = this.code.lines[ nlines ].length;
1694
1770
  this.selection.toY = nlines;
1695
- this.processSelection( null, true );
1696
1771
  this.cursorToPosition( cursor, this.selection.toX );
1697
1772
  this.cursorToLine( cursor, this.selection.toY );
1773
+ this.processSelection( null, true );
1774
+ this.hideAutoCompleteBox();
1698
1775
  break;
1699
1776
  case 'c': // copy
1700
1777
  this._copyContent();
@@ -1704,6 +1781,7 @@ class CodeEditor {
1704
1781
  this.code.lines.splice( lidx, 0, this.code.lines[ lidx ] );
1705
1782
  this.lineDown( cursor );
1706
1783
  this.processLines();
1784
+ this.hideAutoCompleteBox();
1707
1785
  return;
1708
1786
  case 's': // save
1709
1787
  e.preventDefault();
@@ -1714,14 +1792,15 @@ class CodeEditor {
1714
1792
  return;
1715
1793
  case 'x': // cut line
1716
1794
  this._cutContent();
1795
+ this.hideAutoCompleteBox();
1717
1796
  return;
1718
1797
  case 'z': // undo
1719
1798
  if(!this.code.undoSteps.length)
1720
1799
  return;
1721
1800
  const step = this.code.undoSteps.pop();
1722
1801
  this.code.lines = step.lines;
1723
- this.restoreCursor( cursor, step.cursor );
1724
1802
  this.processLines();
1803
+ this.restoreCursor( cursor, step.cursor );
1725
1804
  return;
1726
1805
  }
1727
1806
  }
@@ -1736,6 +1815,7 @@ class CodeEditor {
1736
1815
  this.lineUp();
1737
1816
  this.processLine( lidx - 1 );
1738
1817
  this.processLine( lidx );
1818
+ this.hideAutoCompleteBox();
1739
1819
  return;
1740
1820
  case 'ArrowDown':
1741
1821
  if(this.code.lines[ lidx + 1 ] == undefined)
@@ -1744,6 +1824,7 @@ class CodeEditor {
1744
1824
  this.lineDown();
1745
1825
  this.processLine( lidx );
1746
1826
  this.processLine( lidx + 1 );
1827
+ this.hideAutoCompleteBox();
1747
1828
  return;
1748
1829
  }
1749
1830
  }
@@ -1776,7 +1857,7 @@ class CodeEditor {
1776
1857
  const enclosableKeys = ["\"", "'", "(", "{"];
1777
1858
  if( enclosableKeys.indexOf( key ) > -1 )
1778
1859
  {
1779
- if( this._encloseSelectedWordWithKey(key, lidx, cursor) )
1860
+ if( this._encloseSelectedWordWithKey( key, lidx, cursor ) )
1780
1861
  return;
1781
1862
  }
1782
1863
 
@@ -1785,7 +1866,8 @@ class CodeEditor {
1785
1866
 
1786
1867
  if( this.selection )
1787
1868
  {
1788
- this.actions['Backspace'].callback(lidx, cursor, e);
1869
+ this.actions['Backspace'].callback( lidx, cursor, e );
1870
+ lidx = cursor.line;
1789
1871
  }
1790
1872
 
1791
1873
  // Append key
@@ -1882,11 +1964,15 @@ class CodeEditor {
1882
1964
  let lidx = cursor.line;
1883
1965
  let text_to_cut = "";
1884
1966
 
1967
+ this._addUndoStep( cursor );
1968
+
1885
1969
  if( !this.selection ) {
1886
1970
  text_to_cut = "\n" + this.code.lines[ cursor.line ];
1887
1971
  this.code.lines.splice( lidx, 1 );
1888
1972
  this.processLines();
1889
1973
  this.resetCursorPos( CodeEditor.CURSOR_LEFT );
1974
+ if( this.code.lines[ lidx ] == undefined )
1975
+ this.lineUp();
1890
1976
  }
1891
1977
  else {
1892
1978
 
@@ -1949,7 +2035,6 @@ class CodeEditor {
1949
2035
 
1950
2036
  const start = performance.now();
1951
2037
 
1952
- var gutter_html = "";
1953
2038
  var code_html = "";
1954
2039
 
1955
2040
  // Reset all lines content
@@ -1975,16 +2060,11 @@ class CodeEditor {
1975
2060
  // Process visible lines
1976
2061
  for( let i = this.visibleLinesViewport.x; i < this.visibleLinesViewport.y; ++i )
1977
2062
  {
1978
- gutter_html += "<span>" + (i + 1) + "</span>";
1979
2063
  code_html += this.processLine( i, true );
1980
2064
  }
1981
2065
 
1982
2066
  this.code.innerHTML = code_html;
1983
2067
 
1984
- // console.log("RANGE:", this.visibleLinesViewport);
1985
- // console.log( "Num lines processed:", (this.visibleLinesViewport.y - this.visibleLinesViewport.x), performance.now() - start );
1986
- // console.log("--------------------------------------------");
1987
-
1988
2068
  // Update scroll data
1989
2069
  this.codeScroller.scrollTop = lastScrollTop;
1990
2070
  this.code.style.top = ( this.visibleLinesViewport.x * this.lineHeight ) + "px";
@@ -1993,10 +2073,8 @@ class CodeEditor {
1993
2073
  if( this.selection )
1994
2074
  this.processSelection( null, true );
1995
2075
 
1996
- // Clear tmp vars
1997
- delete this._buildingString;
1998
- delete this._pendingString;
1999
-
2076
+ this._clearTmpVariables();
2077
+ this._setActiveLine();
2000
2078
  this.resize();
2001
2079
  }
2002
2080
 
@@ -2007,7 +2085,10 @@ class CodeEditor {
2007
2085
 
2008
2086
  const UPDATE_LINE = ( html ) => {
2009
2087
  if( !force ) // Single line update
2088
+ {
2010
2089
  this.code.childNodes[ local_line_num ].innerHTML = gutter_line + html;
2090
+ this._clearTmpVariables();
2091
+ }
2011
2092
  else // Update all lines at once
2012
2093
  return "<pre>" + ( gutter_line + html ) + "</pre>";
2013
2094
  }
@@ -2061,8 +2142,6 @@ class CodeEditor {
2061
2142
  {
2062
2143
  if( token.substr( 0, 2 ) == '/*' )
2063
2144
  this._buildingBlockComment = true;
2064
- if( token.substr( token.length - 2 ) == '*/' )
2065
- delete this._buildingBlockComment;
2066
2145
  }
2067
2146
 
2068
2147
  line_inner_html += this._evaluateToken( token, prev, next, (i == tokensToEvaluate.length - 1) );
@@ -2073,7 +2152,7 @@ class CodeEditor {
2073
2152
 
2074
2153
  _processTokens( tokens, offset = 0 ) {
2075
2154
 
2076
- if( this.highlight == 'C++' )
2155
+ if( this.highlight == 'C++' || this.highlight == 'CSS' )
2077
2156
  {
2078
2157
  var idx = tokens.slice( offset ).findIndex( ( value, index ) => this.isNumber( value ) );
2079
2158
  if( idx > -1 )
@@ -2129,8 +2208,6 @@ class CodeEditor {
2129
2208
  linestring = ogLine.substring( 0, hasCommentIdx );
2130
2209
  }
2131
2210
 
2132
- // const usesBlockComments = this.languages[ this.highlight ].blockComments ?? true;
2133
-
2134
2211
  let tokensToEvaluate = []; // store in a temp array so we know prev and next tokens...
2135
2212
 
2136
2213
  const pushToken = function( t ) {
@@ -2139,7 +2216,7 @@ class CodeEditor {
2139
2216
  tokensToEvaluate.push( t );
2140
2217
  };
2141
2218
 
2142
- let iter = linestring.matchAll(/(::|[\[\](){}<>.,;:*"'%@!/= ])/g);
2219
+ let iter = linestring.matchAll(/(\*\/|\/\*|::|[\[\](){}<>.,;:*"'%@!/= ])/g);
2143
2220
  let subtokens = iter.next();
2144
2221
  if( subtokens.value )
2145
2222
  {
@@ -2159,26 +2236,6 @@ class CodeEditor {
2159
2236
  }
2160
2237
  else tokensToEvaluate.push( linestring );
2161
2238
 
2162
- // if( usesBlockComments )
2163
- // {
2164
- // var block = false;
2165
-
2166
- // if( t.includes('/*') )
2167
- // {
2168
- // const idx = t.indexOf( '/*' );
2169
- // tokensToEvaluate.push( t.substring( 0, idx ), '/*', t.substring( idx + 2 ) );
2170
- // block |= true;
2171
- // }
2172
- // else if( t.includes('*/') )
2173
- // {
2174
- // const idx = t.indexOf( '*/' );
2175
- // tokensToEvaluate.push( t.substring( 0, idx ), '*/', t.substring( idx + 2 ) );
2176
- // block |= true;
2177
- // }
2178
-
2179
- // if( block ) continue;
2180
- // }
2181
-
2182
2239
  if( hasCommentIdx != undefined )
2183
2240
  {
2184
2241
  pushToken( ogLine.substring( hasCommentIdx ) );
@@ -2215,6 +2272,8 @@ class CodeEditor {
2215
2272
  this._buildingString = token;
2216
2273
  }
2217
2274
 
2275
+ const usesBlockComments = this.languages[ this.highlight ].blockComments ?? true;
2276
+
2218
2277
  if(token == ' ')
2219
2278
  {
2220
2279
  if( this._buildingString != undefined )
@@ -2227,7 +2286,6 @@ class CodeEditor {
2227
2286
  else
2228
2287
  {
2229
2288
  const singleLineCommentToken = this.languages[ this.highlight ].singleLineCommentToken ?? this.defaultSingleLineCommentToken;
2230
- const usesBlockComments = this.languages[ this.highlight ].blockComments ?? true;
2231
2289
 
2232
2290
  let token_classname = "";
2233
2291
  let discardToken = false;
@@ -2253,12 +2311,6 @@ class CodeEditor {
2253
2311
  else if( token.substr( 0, singleLineCommentToken.length ) == singleLineCommentToken )
2254
2312
  token_classname = "cm-com";
2255
2313
 
2256
- else if( usesBlockComments && token.substr( 0, 2 ) == '/*' )
2257
- token_classname = "cm-com";
2258
-
2259
- else if( usesBlockComments && token.substr( token.length - 2 ) == '*/' )
2260
- token_classname = "cm-com";
2261
-
2262
2314
  else if( this.isNumber( token ) || this.isNumber( token.replace(/[px]|[em]|%/g,'') ) )
2263
2315
  token_classname = "cm-dec";
2264
2316
 
@@ -2290,6 +2342,11 @@ class CodeEditor {
2290
2342
  token_classname = "cm-mtd";
2291
2343
 
2292
2344
 
2345
+ if( usesBlockComments && this._buildingBlockComment && token.substr( 0, 2 ) == '*/' )
2346
+ {
2347
+ delete this._buildingBlockComment;
2348
+ }
2349
+
2293
2350
  // We finished constructing a string
2294
2351
  if( this._buildingString && ( this._stringEnded || isLastToken ) )
2295
2352
  {
@@ -2345,6 +2402,12 @@ class CodeEditor {
2345
2402
  else if( token.lastChar == 'u' )
2346
2403
  return !(token.includes('.')) && this.isNumber( token.substring(0, token.length - 1) );
2347
2404
  }
2405
+
2406
+ else if(this.highlight == 'CSS')
2407
+ {
2408
+ if( token.lastChar == '%' )
2409
+ return this.isNumber( token.substring(0, token.length - 1) )
2410
+ }
2348
2411
 
2349
2412
  return token.length && token != ' ' && !Number.isNaN(+token);
2350
2413
  }
@@ -2422,31 +2485,43 @@ class CodeEditor {
2422
2485
  lineUp( cursor, resetLeft ) {
2423
2486
 
2424
2487
  cursor = cursor ?? this.cursors.children[ 0 ];
2488
+
2489
+ if( this.code.lines[ cursor.line - 1 ] == undefined )
2490
+ return false;
2491
+
2425
2492
  cursor.line--;
2426
2493
  cursor.line = Math.max( 0, cursor.line );
2427
2494
  this.cursorToTop( cursor, resetLeft );
2495
+
2496
+ return true;
2428
2497
  }
2429
2498
 
2430
2499
  lineDown( cursor, resetLeft ) {
2431
2500
 
2432
2501
  cursor = cursor ?? this.cursors.children[ 0 ];
2502
+
2503
+ if( this.code.lines[ cursor.line + 1 ] == undefined )
2504
+ return false;
2505
+
2433
2506
  cursor.line++;
2434
2507
  this.cursorToBottom( cursor, resetLeft );
2508
+
2509
+ return true;
2435
2510
  }
2436
2511
 
2437
2512
  restartBlink() {
2438
2513
 
2439
2514
  if( !this.code ) return;
2440
2515
 
2441
- clearInterval(this.blinker);
2442
- this.cursors.classList.add('show');
2516
+ clearInterval( this.blinker );
2517
+ this.cursors.classList.add( 'show' );
2443
2518
 
2444
- if (this.cursorBlinkRate > 0)
2519
+ if( this.cursorBlinkRate > 0 )
2445
2520
  this.blinker = setInterval(() => {
2446
- this.cursors.classList.toggle('show');
2521
+ this.cursors.classList.toggle( 'show' );
2447
2522
  }, this.cursorBlinkRate);
2448
- else if (this.cursorBlinkRate < 0)
2449
- this.cursors.classList.remove('show');
2523
+ else if( this.cursorBlinkRate < 0 )
2524
+ this.cursors.classList.remove( 'show' );
2450
2525
  }
2451
2526
 
2452
2527
  startSelection( cursor ) {
@@ -2455,10 +2530,10 @@ class CodeEditor {
2455
2530
  this.selections.innerHTML = "";
2456
2531
 
2457
2532
  // Show elements
2458
- this.selections.classList.add('show');
2533
+ this.selections.classList.add( 'show' );
2459
2534
 
2460
2535
  // Create new selection instance
2461
- this.selection = new CodeSelection(this, cursor.position, cursor.line);
2536
+ this.selection = new CodeSelection( this, cursor.position, cursor.line );
2462
2537
  }
2463
2538
 
2464
2539
  deleteSelection( cursor ) {
@@ -2475,7 +2550,7 @@ class CodeEditor {
2475
2550
 
2476
2551
  // Get linear start index
2477
2552
  let index = 0;
2478
- for(let i = 0; i <= this.selection.fromY; i++)
2553
+ for( let i = 0; i <= this.selection.fromY; i++ )
2479
2554
  index += (i == this.selection.fromY ? this.selection.fromX : this.code.lines[ i ].length);
2480
2555
 
2481
2556
  index += this.selection.fromY * separator.length;
@@ -2488,19 +2563,17 @@ class CodeEditor {
2488
2563
 
2489
2564
  this.cursorToLine( cursor, this.selection.fromY, true );
2490
2565
  this.cursorToPosition( cursor, this.selection.fromX );
2491
-
2492
2566
  this.endSelection();
2493
-
2494
2567
  this.processLines();
2495
- this._refreshCodeInfo( cursor.line, cursor.position );
2496
2568
  }
2497
2569
 
2498
2570
  endSelection() {
2499
2571
 
2500
- this.selections.classList.remove('show');
2572
+ this.selections.classList.remove( 'show' );
2501
2573
  this.selections.innerHTML = "";
2502
2574
  delete this.selection;
2503
2575
  delete this._tripleClickSelection;
2576
+ delete this._lastSelectionKeyDir;
2504
2577
  }
2505
2578
 
2506
2579
  cursorToRight( key, cursor ) {
@@ -2512,7 +2585,6 @@ class CodeEditor {
2512
2585
  cursor.position++;
2513
2586
 
2514
2587
  this.restartBlink();
2515
- this._refreshCodeInfo( cursor.line, cursor.position );
2516
2588
 
2517
2589
  // Add horizontal scroll
2518
2590
 
@@ -2525,7 +2597,7 @@ class CodeEditor {
2525
2597
 
2526
2598
  cursorToLeft( key, cursor ) {
2527
2599
 
2528
- if(!key) return;
2600
+ if( !key ) return;
2529
2601
  cursor = cursor ?? this.cursors.children[ 0 ];
2530
2602
  cursor._left -= this.charWidth;
2531
2603
  cursor._left = Math.max( cursor._left, 0 );
@@ -2533,7 +2605,6 @@ class CodeEditor {
2533
2605
  cursor.position--;
2534
2606
  cursor.position = Math.max( cursor.position, 0 );
2535
2607
  this.restartBlink();
2536
- this._refreshCodeInfo( cursor.line, cursor.position );
2537
2608
 
2538
2609
  // Add horizontal scroll
2539
2610
 
@@ -2555,8 +2626,6 @@ class CodeEditor {
2555
2626
  if(resetLeft)
2556
2627
  this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
2557
2628
 
2558
- this._refreshCodeInfo( cursor.line, cursor.position );
2559
-
2560
2629
  doAsync(() => {
2561
2630
  var first_line = ( this.getScrollTop() / this.lineHeight )|0;
2562
2631
  if( (cursor.line - 1) < first_line )
@@ -2574,8 +2643,6 @@ class CodeEditor {
2574
2643
  if(resetLeft)
2575
2644
  this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
2576
2645
 
2577
- this._refreshCodeInfo( cursor.line, cursor.position );
2578
-
2579
2646
  doAsync(() => {
2580
2647
  var last_line = ( ( this.codeScroller.offsetHeight + this.getScrollTop() ) / this.lineHeight )|0;
2581
2648
  if( cursor.line >= last_line )
@@ -2585,9 +2652,10 @@ class CodeEditor {
2585
2652
 
2586
2653
  cursorToString( cursor, text, reverse ) {
2587
2654
 
2655
+ if( !text.length ) return;
2588
2656
  cursor = cursor ?? this.cursors.children[ 0 ];
2589
2657
  for( let char of text )
2590
- reverse ? this.cursorToLeft(char) : this.cursorToRight(char);
2658
+ reverse ? this.cursorToLeft( char ) : this.cursorToRight( char );
2591
2659
  }
2592
2660
 
2593
2661
  cursorToPosition( cursor, position ) {
@@ -2602,7 +2670,7 @@ class CodeEditor {
2602
2670
  cursor.line = line;
2603
2671
  cursor._top = this.lineHeight * line;
2604
2672
  cursor.style.top = cursor._top + "px";
2605
- if(resetLeft) this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
2673
+ if( resetLeft ) this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
2606
2674
  }
2607
2675
 
2608
2676
  saveCursor( cursor, state = {} ) {
@@ -2611,7 +2679,7 @@ class CodeEditor {
2611
2679
  state.top = cursor._top;
2612
2680
  state.left = cursor._left;
2613
2681
  state.line = cursor.line;
2614
- state.charPos = cursor.position;
2682
+ state.position = cursor.position;
2615
2683
  return state;
2616
2684
  }
2617
2685
 
@@ -2622,9 +2690,9 @@ class CodeEditor {
2622
2690
  cursor.position = state.position ?? 0;
2623
2691
 
2624
2692
  cursor._left = state.left ?? 0;
2625
- cursor.style.left = "calc(" + (cursor._left - this.getScrollLeft()) + "px + " + this.xPadding + ")";
2693
+ cursor.style.left = "calc(" + ( cursor._left - this.getScrollLeft() ) + "px + " + this.xPadding + ")";
2626
2694
  cursor._top = state.top ?? 0;
2627
- cursor.style.top = "calc(" + (cursor._top - this.getScrollTop()) + "px)";
2695
+ cursor.style.top = "calc(" + ( cursor._top - this.getScrollTop() ) + "px)";
2628
2696
  }
2629
2697
 
2630
2698
  resetCursorPos( flag, cursor ) {
@@ -2634,14 +2702,14 @@ class CodeEditor {
2634
2702
  if( flag & CodeEditor.CURSOR_LEFT )
2635
2703
  {
2636
2704
  cursor._left = 0;
2637
- cursor.style.left = "calc(" + (-this.getScrollLeft()) + "px + " + this.xPadding + ")";
2705
+ cursor.style.left = "calc(" + ( -this.getScrollLeft() ) + "px + " + this.xPadding + ")";
2638
2706
  cursor.position = 0;
2639
2707
  }
2640
2708
 
2641
2709
  if( flag & CodeEditor.CURSOR_TOP )
2642
2710
  {
2643
2711
  cursor._top = 0;
2644
- cursor.style.top = (cursor._top - this.getScrollTop()) + "px";
2712
+ cursor.style.top = ( -this.getScrollTop() ) + "px";
2645
2713
  cursor.line = 0;
2646
2714
  }
2647
2715
  }
@@ -2649,14 +2717,14 @@ class CodeEditor {
2649
2717
  addSpaceTabs(n) {
2650
2718
 
2651
2719
  for( var i = 0; i < n; ++i ) {
2652
- this.actions['Tab'].callback();
2720
+ this.actions[ 'Tab' ].callback();
2653
2721
  }
2654
2722
  }
2655
2723
 
2656
2724
  addSpaces(n) {
2657
2725
 
2658
2726
  for( var i = 0; i < n; ++i ) {
2659
- this.root.dispatchEvent(new CustomEvent('keydown', {'detail': {
2727
+ this.root.dispatchEvent( new CustomEvent( 'keydown', { 'detail': {
2660
2728
  skip_undo: true,
2661
2729
  key: ' '
2662
2730
  }}));
@@ -2665,26 +2733,26 @@ class CodeEditor {
2665
2733
 
2666
2734
  getScrollLeft() {
2667
2735
 
2668
- if(!this.codeScroller) return 0;
2736
+ if( !this.codeScroller ) return 0;
2669
2737
  return this.codeScroller.scrollLeft;
2670
2738
  }
2671
2739
 
2672
2740
  getScrollTop() {
2673
2741
 
2674
- if(!this.codeScroller) return 0;
2742
+ if( !this.codeScroller ) return 0;
2675
2743
  return this.codeScroller.scrollTop;
2676
2744
  }
2677
2745
 
2678
2746
  setScrollLeft( value ) {
2679
2747
 
2680
- if(!this.codeScroller) return;
2748
+ if( !this.codeScroller ) return;
2681
2749
  this.codeScroller.scrollLeft = value;
2682
2750
  this.setScrollBarValue( 'horizontal', 0 );
2683
2751
  }
2684
2752
 
2685
2753
  setScrollTop( value ) {
2686
2754
 
2687
- if(!this.codeScroller) return;
2755
+ if( !this.codeScroller ) return;
2688
2756
  this.codeScroller.scrollTop = value;
2689
2757
  this.setScrollBarValue( 'vertical' );
2690
2758
  }
@@ -2816,46 +2884,70 @@ class CodeEditor {
2816
2884
  getCharAtPos( cursor, offset = 0 ) {
2817
2885
 
2818
2886
  cursor = cursor ?? this.cursors.children[ 0 ];
2819
- return this.code.lines[ cursor.line ][cursor.position + offset];
2887
+ return this.code.lines[ cursor.line ][ cursor.position + offset ];
2820
2888
  }
2821
2889
 
2822
- getWordAtPos( cursor, offset = 0 ) {
2890
+ getWordAtPos( cursor, loffset = 0, roffset ) {
2823
2891
 
2892
+ roffset = roffset ?? loffset;
2824
2893
  cursor = cursor ?? this.cursors.children[ 0 ];
2825
2894
  const col = cursor.line;
2826
- const words = this.code.lines[col];
2895
+ const words = this.code.lines[ col ];
2827
2896
 
2828
- const is_char = (char) => {
2829
- const exceptions = ['_', '#', '!'];
2830
- const code = char.charCodeAt(0);
2831
- 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);
2832
2901
  }
2833
2902
 
2834
- 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--;
2835
2910
 
2836
- while( words[it] && is_char(words[it]) )
2837
- it--;
2911
+ from++;
2838
2912
 
2839
- const from = it + 1;
2840
- it = cursor.position + offset;
2913
+ // Check right ...
2841
2914
 
2842
- while( words[it] && is_char(words[it]) )
2843
- it++;
2915
+ while( words[ to ] && is_char( words[ to ] ) )
2916
+ to++;
2844
2917
 
2845
- const to = it;
2918
+ // Skip spaces ...
2846
2919
 
2847
- 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 ];
2848
2940
  }
2849
2941
 
2850
- measureChar( char = "a", get_bb = false ) {
2942
+ _measureChar( char = "a", get_bb = false ) {
2851
2943
 
2852
- var test = document.createElement("pre");
2944
+ var test = document.createElement( "pre" );
2853
2945
  test.className = "codechar";
2854
2946
  test.innerHTML = char;
2855
- document.body.appendChild(test);
2947
+ document.body.appendChild( test );
2856
2948
  var rect = test.getBoundingClientRect();
2857
2949
  deleteElement( test );
2858
- const bb = [Math.floor(rect.width), Math.floor(rect.height)];
2950
+ const bb = [ Math.floor( rect.width ), Math.floor( rect.height ) ];
2859
2951
  return get_bb ? bb : bb[ 0 ];
2860
2952
  }
2861
2953
 
@@ -2866,44 +2958,46 @@ class CodeEditor {
2866
2958
 
2867
2959
  runScript( code ) {
2868
2960
 
2869
- var script = document.createElement('script');
2961
+ var script = document.createElement( 'script' );
2870
2962
  script.type = 'module';
2871
2963
  script.innerHTML = code;
2872
2964
  // script.src = url[ i ] + ( version ? "?version=" + version : "" );
2873
2965
  script.async = false;
2874
2966
  // script.onload = function(e) { };
2875
- document.getElementsByTagName('head')[ 0 ].appendChild(script);
2967
+ document.getElementsByTagName( 'head' )[ 0 ].appendChild( script );
2876
2968
  }
2877
2969
 
2878
2970
  toJSONFormat( text ) {
2879
2971
 
2880
- let params = text.split(":");
2972
+ let params = text.split( ':' );
2881
2973
 
2882
2974
  for(let i = 0; i < params.length; i++) {
2883
- let key = params[ i ].split(',');
2884
- if(key.length > 1) {
2885
- 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( ']' ))
2886
2979
  continue;
2887
- key = key[key.length-1];
2980
+ key = key[ key.length - 1 ];
2888
2981
  }
2889
- else if(key[ 0 ].includes("}"))
2982
+ else if( key[ 0 ].includes( '}' ) )
2890
2983
  continue;
2891
2984
  else
2892
2985
  key = key[ 0 ];
2893
- key = key.replaceAll(/[{}\n\r]/g,"").replaceAll(" ","")
2894
- if(key[ 0 ] != '"' && key[key.length - 1] != '"') {
2895
- 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 + '"' );
2896
2990
  }
2897
2991
  }
2898
2992
 
2899
- text = params.join(':');
2993
+ text = params.join( ':' );
2900
2994
 
2901
2995
  try {
2902
- let json = JSON.parse(text);
2903
- return JSON.stringify(json, undefined, 4);
2996
+ let json = JSON.parse( text );
2997
+ return JSON.stringify( json, undefined, 4 );
2904
2998
  }
2905
- catch(e) {
2906
- alert("Invalid JSON format");
2999
+ catch( e ) {
3000
+ alert( "Invalid JSON format" );
2907
3001
  return;
2908
3002
  }
2909
3003
  }
@@ -2943,10 +3037,10 @@ class CodeEditor {
2943
3037
  if( !s.toLowerCase().includes(word.toLowerCase()) )
2944
3038
  continue;
2945
3039
 
2946
- var pre = document.createElement('pre');
3040
+ var pre = document.createElement( 'pre' );
2947
3041
  this.autocomplete.appendChild(pre);
2948
3042
 
2949
- var icon = document.createElement('a');
3043
+ var icon = document.createElement( 'a' );
2950
3044
 
2951
3045
  if( this._mustHightlightWord( s, this.utils ) )
2952
3046
  icon.className = "fa fa-cube";
@@ -2999,7 +3093,7 @@ class CodeEditor {
2999
3093
  hideAutoCompleteBox() {
3000
3094
 
3001
3095
  this.isAutoCompleteActive = false;
3002
- this.autocomplete.classList.remove('show');
3096
+ this.autocomplete.classList.remove( 'show' );
3003
3097
  }
3004
3098
 
3005
3099
  autoCompleteWord( cursor, suggestion ) {
@@ -3007,7 +3101,7 @@ class CodeEditor {
3007
3101
  if( !this.isAutoCompleteActive )
3008
3102
  return;
3009
3103
 
3010
- let [suggestedWord, idx] = this.getSelectedAutoComplete();
3104
+ let [suggestedWord, idx] = this._getSelectedAutoComplete();
3011
3105
  suggestedWord = suggestion ?? suggestedWord;
3012
3106
 
3013
3107
  const [word, start, end] = this.getWordAtPos( cursor, -1 );
@@ -3022,7 +3116,7 @@ class CodeEditor {
3022
3116
  this.hideAutoCompleteBox();
3023
3117
  }
3024
3118
 
3025
- getSelectedAutoComplete() {
3119
+ _getSelectedAutoComplete() {
3026
3120
 
3027
3121
  if( !this.isAutoCompleteActive )
3028
3122
  return;
@@ -3041,12 +3135,12 @@ class CodeEditor {
3041
3135
  }
3042
3136
  }
3043
3137
 
3044
- moveArrowSelectedAutoComplete( dir ) {
3138
+ _moveArrowSelectedAutoComplete( dir ) {
3045
3139
 
3046
3140
  if( !this.isAutoCompleteActive )
3047
3141
  return;
3048
3142
 
3049
- const [word, idx] = this.getSelectedAutoComplete();
3143
+ const [word, idx] = this._getSelectedAutoComplete();
3050
3144
  const offset = dir == 'down' ? 1 : -1;
3051
3145
 
3052
3146
  if( dir == 'down' ) {
@@ -3055,12 +3149,51 @@ class CodeEditor {
3055
3149
  if( (idx + offset) < 0 ) return;
3056
3150
  }
3057
3151
 
3058
- this.autocomplete.scrollTop += offset * 18;
3152
+ this.autocomplete.scrollTop += offset * 20;
3059
3153
 
3060
3154
  // Remove selected from the current word and add it to the next one
3061
3155
  this.autocomplete.childNodes[ idx ].classList.remove('selected');
3062
3156
  this.autocomplete.childNodes[ idx + offset ].classList.add('selected');
3063
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
+ }
3064
3197
  }
3065
3198
 
3066
3199
  LX.CodeEditor = CodeEditor;