lexgui 0.1.25 → 0.1.27

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,6 +30,10 @@ function firstNonspaceIndex( str ) {
30
30
  return index < str.length ? index : -1;
31
31
  }
32
32
 
33
+ function strReverse( str ) {
34
+ return str.split( "" ).reverse().join( "" );
35
+ }
36
+
33
37
  function indexOfFrom( str, reg, from, reverse ) {
34
38
 
35
39
  from = from ?? 0;
@@ -64,16 +68,17 @@ function doAsync( fn, ms ) {
64
68
 
65
69
  class CodeSelection {
66
70
 
67
- constructor( editor, ix, iy, className = "lexcodeselection" ) {
71
+ constructor( editor, cursor, className = "lexcodeselection" ) {
68
72
 
69
73
  this.editor = editor;
70
- this.chars = 0;
74
+ this.cursor = cursor;
71
75
  this.className = className;
76
+ this.chars = 0;
72
77
 
73
- this.fromX = ix;
74
- this.toX = ix;
75
- this.fromY = iy;
76
- this.toY = iy;
78
+ this.fromX = cursor.position;
79
+ this.toX = cursor.position;
80
+ this.fromY = cursor.line;
81
+ this.toY = cursor.line;
77
82
  }
78
83
 
79
84
  sameLine() {
@@ -101,7 +106,7 @@ class CodeSelection {
101
106
  }
102
107
  }
103
108
 
104
- selectInline( x, y, width ) {
109
+ selectInline( cursor, x, y, width, isSearchResult ) {
105
110
 
106
111
  this.chars = width / this.editor.charWidth;
107
112
  this.fromX = x;
@@ -116,11 +121,45 @@ class CodeSelection {
116
121
  domEl._left = x * this.editor.charWidth;
117
122
  domEl.style.left = "calc(" + domEl._left + "px + " + this.editor.xPadding + ")";
118
123
  domEl.style.width = width + "px";
119
- this.editor.selections.appendChild(domEl);
124
+
125
+ if( isSearchResult )
126
+ {
127
+ this.editor.searchResultSelections.appendChild( domEl );
128
+ }
129
+ else
130
+ {
131
+ this.editor.selections[ cursor.name ].appendChild( domEl );
132
+ }
120
133
 
121
134
  // Hide active line background
122
135
  this.editor._hideActiveLine();
123
136
  }
137
+
138
+ save() {
139
+
140
+ return {
141
+ fromX: this.fromX,
142
+ fromY: this.fromY,
143
+ toX: this.toX,
144
+ toY: this.toY
145
+ }
146
+ }
147
+
148
+ load( data ) {
149
+
150
+ this.fromX = data.fromX;
151
+ this.fromY = data.fromY;
152
+ this.toX = data.toX;
153
+ this.toY = data.toY;
154
+ }
155
+
156
+ getText() {
157
+
158
+ if( !this.editor.code || !this.sameLine() )
159
+ return null;
160
+
161
+ return this.editor.code.lines[ this.fromY ].substring( this.fromX, this.toX );
162
+ }
124
163
  };
125
164
 
126
165
  class ScrollBar {
@@ -141,6 +180,7 @@ class ScrollBar {
141
180
  this.thumb = document.createElement( 'div' );
142
181
  this.thumb._top = 0;
143
182
  this.thumb._left = 0;
183
+
144
184
  this.root.appendChild( this.thumb );
145
185
 
146
186
  this.thumb.addEventListener( "mousedown", inner_mousedown );
@@ -152,8 +192,8 @@ class ScrollBar {
152
192
  function inner_mousedown( e )
153
193
  {
154
194
  var doc = editor.root.ownerDocument;
155
- doc.addEventListener( "mousemove",inner_mousemove );
156
- doc.addEventListener( "mouseup",inner_mouseup );
195
+ doc.addEventListener( "mousemove", inner_mousemove );
196
+ doc.addEventListener( "mouseup", inner_mouseup );
157
197
  that.lastPosition.set( e.x, e.y );
158
198
  e.stopPropagation();
159
199
  e.preventDefault();
@@ -325,25 +365,19 @@ class CodeEditor {
325
365
  this.cursors.className = 'cursors';
326
366
  this.tabs.area.attach( this.cursors );
327
367
 
328
- this.selections = document.createElement( 'div' );
329
- this.selections.className = 'selections';
330
- this.tabs.area.attach( this.selections );
368
+ this.searchResultSelections = document.createElement( 'div' );
369
+ this.searchResultSelections.id = 'search-selections';
370
+ this.searchResultSelections.className = 'selections';
371
+ this.tabs.area.attach( this.searchResultSelections );
372
+
373
+ // Store here selections per cursor
374
+ this.selections = {};
331
375
 
332
376
  // Css char synchronization
333
377
  this.xPadding = "48px";
334
378
 
335
379
  // Add main cursor
336
- {
337
- this._addCursor( 0, 0, true, true );
338
-
339
- Object.defineProperty( this, 'line', {
340
- get: (v) => { return this._getCurrentCursor().line }
341
- } );
342
-
343
- Object.defineProperty( this, 'position', {
344
- get: (v) => { return this._getCurrentCursor().position }
345
- } );
346
- }
380
+ this._addCursor( 0, 0, true, true );
347
381
 
348
382
  // Scroll stuff
349
383
  {
@@ -556,18 +590,27 @@ class CodeEditor {
556
590
 
557
591
  // Convert reserved word arrays to maps so we can search tokens faster
558
592
 
559
- for( let lang in CodeEditor.keywords ) CodeEditor.keywords[lang] = CodeEditor.keywords[lang].reduce((a, v) => ({ ...a, [v]: true}), {});
560
- for( let lang in CodeEditor.utils ) CodeEditor.utils[lang] = CodeEditor.utils[lang].reduce((a, v) => ({ ...a, [v]: true}), {});
561
- for( let lang in CodeEditor.types ) CodeEditor.types[lang] = CodeEditor.types[lang].reduce((a, v) => ({ ...a, [v]: true}), {});
562
- for( let lang in CodeEditor.builtin ) CodeEditor.builtin[lang] = CodeEditor.builtin[lang].reduce((a, v) => ({ ...a, [v]: true}), {});
563
- for( let lang in CodeEditor.statementsAndDeclarations ) CodeEditor.statementsAndDeclarations[lang] = CodeEditor.statementsAndDeclarations[lang].reduce((a, v) => ({ ...a, [v]: true}), {});
564
- for( let lang in CodeEditor.symbols ) CodeEditor.symbols[lang] = CodeEditor.symbols[lang].reduce((a, v) => ({ ...a, [v]: true}), {});
593
+ if( !CodeEditor._staticReady )
594
+ {
595
+ for( let lang in CodeEditor.keywords ) CodeEditor.keywords[lang] = CodeEditor.keywords[lang].reduce((a, v) => ({ ...a, [v]: true}), {});
596
+ for( let lang in CodeEditor.utils ) CodeEditor.utils[lang] = CodeEditor.utils[lang].reduce((a, v) => ({ ...a, [v]: true}), {});
597
+ for( let lang in CodeEditor.types ) CodeEditor.types[lang] = CodeEditor.types[lang].reduce((a, v) => ({ ...a, [v]: true}), {});
598
+ for( let lang in CodeEditor.builtin ) CodeEditor.builtin[lang] = CodeEditor.builtin[lang].reduce((a, v) => ({ ...a, [v]: true}), {});
599
+ for( let lang in CodeEditor.statementsAndDeclarations ) CodeEditor.statementsAndDeclarations[lang] = CodeEditor.statementsAndDeclarations[lang].reduce((a, v) => ({ ...a, [v]: true}), {});
600
+ for( let lang in CodeEditor.symbols ) CodeEditor.symbols[lang] = CodeEditor.symbols[lang].reduce((a, v) => ({ ...a, [v]: true}), {});
601
+
602
+ CodeEditor._staticReady = true;
603
+ }
565
604
 
566
605
  // Action keys
567
606
 
568
607
  this.action( 'Escape', false, ( ln, cursor, e ) => {
569
- this.hideAutoCompleteBox();
570
- this.hideSearchBox();
608
+ if( this.hideAutoCompleteBox() )
609
+ return;
610
+ if( this.hideSearchBox() )
611
+ return;
612
+ // Remove selections and cursors
613
+ this.endSelection();
571
614
  this._removeSecondaryCursors();
572
615
  });
573
616
 
@@ -575,7 +618,7 @@ class CodeEditor {
575
618
 
576
619
  this._addUndoStep( cursor );
577
620
 
578
- if( this.selection ) {
621
+ if( cursor.selection ) {
579
622
  this.deleteSelection( cursor );
580
623
  // Remove entire line when selecting with triple click
581
624
  if( this._tripleClickSelection )
@@ -629,7 +672,7 @@ class CodeEditor {
629
672
 
630
673
  this._addUndoStep( cursor );
631
674
 
632
- if(this.selection) {
675
+ if( cursor.selection ) {
633
676
  // Use 'Backspace' as it's the same callback...
634
677
  this.actions['Backspace'].callback( ln, cursor, e );
635
678
  }
@@ -640,7 +683,7 @@ class CodeEditor {
640
683
  this.code.lines[ ln ] = sliceChars( this.code.lines[ ln ], cursor.position );
641
684
  this.processLine( ln );
642
685
  }
643
- else if(this.code.lines[ ln + 1 ] != undefined) {
686
+ else if( this.code.lines[ ln + 1 ] != undefined ) {
644
687
  this.code.lines[ ln ] += this.code.lines[ ln + 1 ];
645
688
  this.code.lines.splice( ln + 1, 1 );
646
689
  this.processLines();
@@ -650,14 +693,31 @@ class CodeEditor {
650
693
 
651
694
  this.action( 'Tab', true, ( ln, cursor, e ) => {
652
695
 
653
- if( this.isAutoCompleteActive )
696
+ if( this._skipTabs )
654
697
  {
655
- this.autoCompleteWord( cursor );
656
- } else
698
+ this._skipTabs--;
699
+ if( !this._skipTabs )
700
+ delete this._skipTabs;
701
+ }
702
+ else if( this.isAutoCompleteActive )
657
703
  {
658
- this.addSpaces( this.tabSpaces );
704
+ this.autoCompleteWord();
659
705
  }
660
- });
706
+ else
707
+ {
708
+ this._addUndoStep( cursor );
709
+
710
+ if( e && e.shiftKey )
711
+ {
712
+ this._removeSpaces( cursor );
713
+ }
714
+ else
715
+ {
716
+ const indentSpaces = this.tabSpaces - (cursor.position % this.tabSpaces);
717
+ this._addSpaces( indentSpaces );
718
+ }
719
+ }
720
+ }, "shiftKey");
661
721
 
662
722
  this.action( 'Home', false, ( ln, cursor, e ) => {
663
723
 
@@ -670,7 +730,8 @@ class CodeEditor {
670
730
  let lastX = cursor.position;
671
731
 
672
732
  this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
673
- if(idx > 0) this.cursorToString( cursor, prestring );
733
+ if(idx > 0)
734
+ this.cursorToString( cursor, prestring );
674
735
  this.setScrollLeft( 0 );
675
736
 
676
737
  // Merge cursors
@@ -679,18 +740,18 @@ class CodeEditor {
679
740
  if( e.shiftKey && !e.cancelShift )
680
741
  {
681
742
  // Get last selection range
682
- if( this.selection )
683
- lastX += this.selection.chars;
743
+ if( cursor.selection )
744
+ lastX += cursor.selection.chars;
684
745
 
685
- if( !this.selection )
746
+ if( !cursor.selection )
686
747
  this.startSelection( cursor );
687
748
 
688
749
  var string = this.code.lines[ ln ].substring( idx, lastX );
689
- if( this.selection.sameLine() )
690
- this.selection.selectInline( idx, cursor.line, this.measureString( string ) );
750
+ if( cursor.selection.sameLine() )
751
+ cursor.selection.selectInline( cursor, idx, cursor.line, this.measureString( string ) );
691
752
  else
692
753
  {
693
- this.processSelection( e );
754
+ this._processSelection( cursor, e );
694
755
  }
695
756
  } else if( !e.keepSelection )
696
757
  this.endSelection();
@@ -701,15 +762,15 @@ class CodeEditor {
701
762
  if( ( e.shiftKey || e._shiftKey ) && !e.cancelShift ) {
702
763
 
703
764
  var string = this.code.lines[ ln ].substring( cursor.position );
704
- if( !this.selection )
765
+ if( !cursor.selection )
705
766
  this.startSelection( cursor );
706
- if( this.selection.sameLine() )
707
- this.selection.selectInline(cursor.position, cursor.line, this.measureString( string ));
767
+ if( cursor.selection.sameLine() )
768
+ cursor.selection.selectInline( cursor, cursor.position, cursor.line, this.measureString( string ));
708
769
  else
709
770
  {
710
771
  this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
711
772
  this.cursorToString( cursor, this.code.lines[ ln ] );
712
- this.processSelection( e );
773
+ this._processSelection( cursor, e );
713
774
  }
714
775
  } else if( !e.keepSelection )
715
776
  this.endSelection();
@@ -729,7 +790,7 @@ class CodeEditor {
729
790
  // Add word
730
791
  if( this.isAutoCompleteActive )
731
792
  {
732
- this.autoCompleteWord( cursor );
793
+ this.autoCompleteWord();
733
794
  return;
734
795
  }
735
796
 
@@ -756,10 +817,10 @@ class CodeEditor {
756
817
 
757
818
  if( _c0 == '{' && _c1 == '}' ) {
758
819
  this.code.lines.splice( cursor.line, 0, "" );
759
- this.addSpaceTabs( tabs + 1 );
820
+ this._addSpaceTabs( cursor, tabs + 1 );
760
821
  this.code.lines[ cursor.line + 1 ] = " ".repeat(spaces) + this.code.lines[ cursor.line + 1 ];
761
822
  } else {
762
- this.addSpaceTabs( tabs );
823
+ this._addSpaceTabs( cursor, tabs );
763
824
  }
764
825
 
765
826
  this.processLines();
@@ -771,7 +832,7 @@ class CodeEditor {
771
832
  if( !this.isAutoCompleteActive )
772
833
  {
773
834
  if( e.shiftKey ) {
774
- if( !this.selection )
835
+ if( !cursor.selection )
775
836
  this.startSelection( cursor );
776
837
 
777
838
  this.lineUp( cursor );
@@ -781,7 +842,7 @@ class CodeEditor {
781
842
  this.cursorToPosition( cursor, this.code.lines[ cursor.line ].length );
782
843
  }
783
844
 
784
- this.processSelection( e, false );
845
+ this._processSelection( cursor, e, false );
785
846
 
786
847
  } else {
787
848
  this.endSelection();
@@ -804,7 +865,7 @@ class CodeEditor {
804
865
  if( !this.isAutoCompleteActive )
805
866
  {
806
867
  if( e.shiftKey ) {
807
- if( !this.selection )
868
+ if( !cursor.selection )
808
869
  this.startSelection( cursor );
809
870
  } else {
810
871
  this.endSelection();
@@ -819,7 +880,7 @@ class CodeEditor {
819
880
  }
820
881
 
821
882
  if( e.shiftKey ) {
822
- this.processSelection( e );
883
+ this._processSelection( cursor, e );
823
884
  }
824
885
  }
825
886
  // Move down autocomplete selection
@@ -853,37 +914,37 @@ class CodeEditor {
853
914
  var diff = Math.max( cursor.position - from, 1 );
854
915
  var substr = word.substr( 0, diff );
855
916
  // Selections...
856
- if( e.shiftKey ) { if( !this.selection ) this.startSelection( cursor ); }
917
+ if( e.shiftKey ) { if( !cursor.selection ) this.startSelection( cursor ); }
857
918
  else this.endSelection();
858
919
  this.cursorToString( cursor, substr, true );
859
- if( e.shiftKey ) this.processSelection( e, false, true );
920
+ if( e.shiftKey ) this._processSelection( cursor, e, false, true );
860
921
  }
861
922
  else {
862
923
  var letter = this.getCharAtPos( cursor, -1 );
863
924
  if( letter ) {
864
925
  if( e.shiftKey ) {
865
- if( !this.selection ) this.startSelection( cursor );
926
+ if( !cursor.selection ) this.startSelection( cursor );
866
927
  this.cursorToLeft( letter, cursor );
867
- this.processSelection( e, false, CodeEditor.SELECTION_X );
928
+ this._processSelection( cursor, e, false, CodeEditor.SELECTION_X );
868
929
  }
869
930
  else {
870
- if( !this.selection ) {
931
+ if( !cursor.selection ) {
871
932
  this.cursorToLeft( letter, cursor );
872
933
  if( this.useAutoComplete && this.isAutoCompleteActive )
873
934
  this.showAutoCompleteBox( 'foo', cursor );
874
935
  }
875
936
  else {
876
- this.selection.invertIfNecessary();
937
+ cursor.selection.invertIfNecessary();
877
938
  this.resetCursorPos( CodeEditor.CURSOR_LEFT | CodeEditor.CURSOR_TOP, cursor );
878
- this.cursorToLine( cursor, this.selection.fromY, true );
879
- this.cursorToPosition( cursor, this.selection.fromX );
939
+ this.cursorToLine( cursor, cursor.selection.fromY, true );
940
+ this.cursorToPosition( cursor, cursor.selection.fromX );
880
941
  this.endSelection();
881
942
  }
882
943
  }
883
944
  }
884
945
  else if( cursor.line > 0 ) {
885
946
 
886
- if( e.shiftKey && !this.selection ) this.startSelection( cursor );
947
+ if( e.shiftKey && !cursor.selection ) this.startSelection( cursor );
887
948
 
888
949
  this.lineUp( cursor );
889
950
 
@@ -891,7 +952,7 @@ class CodeEditor {
891
952
  this.actions[ 'End' ].callback( cursor.line, cursor, e );
892
953
  delete e.cancelShift; delete e.keepSelection;
893
954
 
894
- if( e.shiftKey ) this.processSelection( e, false );
955
+ if( e.shiftKey ) this._processSelection( cursor, e, false );
895
956
  }
896
957
  }
897
958
  });
@@ -903,40 +964,61 @@ class CodeEditor {
903
964
  cursor.position == this.code.lines[ cursor.line ].length )
904
965
  return;
905
966
 
906
- if( e.metaKey ) { // Apple devices (Command)
967
+ if( e.metaKey ) // Apple devices (Command)
968
+ {
907
969
  e.preventDefault();
908
970
  this.actions[ 'End' ].callback( ln, cursor );
909
- } else if( e.ctrlKey ) {
971
+ }
972
+ else if( e.ctrlKey ) // Next word
973
+ {
910
974
  // Get next word
911
975
  const [ word, from, to ] = this.getWordAtPos( cursor );
976
+
912
977
  // If no length, we change line..
913
978
  if( !word.length ) this.lineDown( cursor, true );
914
979
  var diff = cursor.position - from;
915
980
  var substr = word.substr( diff );
981
+
916
982
  // Selections...
917
- if( e.shiftKey ) { if( !this.selection ) this.startSelection( cursor ); }
918
- else this.endSelection();
983
+ if( e.shiftKey ) {
984
+ if( !cursor.selection )
985
+ this.startSelection( cursor );
986
+ }
987
+ else
988
+ this.endSelection();
989
+
919
990
  this.cursorToString( cursor, substr );
920
- if( e.shiftKey ) this.processSelection( e );
921
- } else {
991
+
992
+ if( e.shiftKey )
993
+ this._processSelection( cursor, e );
994
+ }
995
+ else // Next char
996
+ {
922
997
  var letter = this.getCharAtPos( cursor );
923
998
  if( letter ) {
924
- if( e.shiftKey ) {
925
- if( !this.selection ) this.startSelection( cursor );
999
+
1000
+ // Selecting chars
1001
+ if( e.shiftKey )
1002
+ {
1003
+ if( !cursor.selection )
1004
+ this.startSelection( cursor );
1005
+
926
1006
  this.cursorToRight( letter, cursor );
927
- this.processSelection( e, false, CodeEditor.SELECTION_X );
928
- }else{
929
- if( !this.selection ) {
1007
+ this._processSelection( cursor, e, false, CodeEditor.SELECTION_X );
1008
+ }
1009
+ else
1010
+ {
1011
+ if( !cursor.selection ) {
930
1012
  this.cursorToRight( letter, cursor );
931
1013
  if( this.useAutoComplete && this.isAutoCompleteActive )
932
1014
  this.showAutoCompleteBox( 'foo', cursor );
933
1015
  }
934
- else
1016
+ else
935
1017
  {
936
- this.selection.invertIfNecessary();
1018
+ cursor.selection.invertIfNecessary();
937
1019
  this.resetCursorPos( CodeEditor.CURSOR_LEFT | CodeEditor.CURSOR_TOP, cursor );
938
- this.cursorToLine( cursor, this.selection.toY );
939
- this.cursorToPosition( cursor, this.selection.toX );
1020
+ this.cursorToLine( cursor, cursor.selection.toY );
1021
+ this.cursorToPosition( cursor, cursor.selection.toX );
940
1022
  this.endSelection();
941
1023
  }
942
1024
  }
@@ -944,12 +1026,13 @@ class CodeEditor {
944
1026
  else if( this.code.lines[ cursor.line + 1 ] !== undefined ) {
945
1027
 
946
1028
  if( e.shiftKey ) {
947
- if( !this.selection ) this.startSelection( cursor );
1029
+ if( !cursor.selection ) this.startSelection( cursor );
948
1030
  }
1031
+ else this.endSelection();
949
1032
 
950
1033
  this.lineDown( cursor, true );
951
1034
 
952
- if( e.shiftKey ) this.processSelection( e, false );
1035
+ if( e.shiftKey ) this._processSelection( cursor, e, false );
953
1036
 
954
1037
  this.hideAutoCompleteBox();
955
1038
  }
@@ -1049,14 +1132,14 @@ class CodeEditor {
1049
1132
 
1050
1133
  let lidx = cursor.line;
1051
1134
 
1052
- if( this.selection ) {
1135
+ if( cursor.selection ) {
1053
1136
  this.deleteSelection( cursor );
1054
1137
  lidx = cursor.line;
1055
1138
  }
1056
1139
 
1057
1140
  this.endSelection();
1058
1141
 
1059
- const new_lines = text.split( '\n' );
1142
+ const new_lines = text.replaceAll( '\r', '' ).split( '\n' );
1060
1143
 
1061
1144
  // Pasting Multiline...
1062
1145
  if( new_lines.length != 1 )
@@ -1111,10 +1194,16 @@ class CodeEditor {
1111
1194
 
1112
1195
  const inner_add_tab = ( text, name, title ) => {
1113
1196
 
1197
+ // Remove Carriage Return in some cases and sub tabs using spaces
1198
+ text = text.replaceAll( '\r', '' );
1199
+ text = text.replaceAll( /\t|\\t/g, ' '.repeat( this.tabSpaces ) );
1200
+
1114
1201
  // Set current text and language
1115
- const lines = text.replaceAll( '\r', '' ).split( '\n' );
1202
+
1203
+ const lines = text.split( '\n' );
1116
1204
 
1117
1205
  // Add item in the explorer if used
1206
+
1118
1207
  if( this.explorer )
1119
1208
  {
1120
1209
  this._storedLines = this._storedLines ?? {};
@@ -1163,6 +1252,7 @@ class CodeEditor {
1163
1252
  }
1164
1253
 
1165
1254
  let cursor = document.createElement( 'div' );
1255
+ cursor.name = "cursor" + this.cursors.childElementCount;
1166
1256
  cursor.className = "cursor";
1167
1257
  cursor.innerHTML = "&nbsp;";
1168
1258
  cursor.isMain = isMain;
@@ -1385,14 +1475,25 @@ class CodeEditor {
1385
1475
 
1386
1476
  panel.sameLine();
1387
1477
  panel.addLabel( this.code.title, { float: 'right', signal: "@tab-name" });
1388
- panel.addLabel( "Ln " + 1, { width: "64px", signal: "@cursor-line" });
1389
- panel.addLabel( "Col " + 1, { width: "64px", signal: "@cursor-pos" });
1478
+ panel.addLabel( "Ln " + 1, { maxWidth: "48px", signal: "@cursor-line" });
1479
+ panel.addLabel( "Col " + 1, { maxWidth: "48px", signal: "@cursor-pos" });
1480
+ panel.addButton( null, "Spaces: " + this.tabSpaces, ( value, event ) => {
1481
+ LX.addContextMenu( "Spaces", event, m => {
1482
+ const options = [ 2, 4, 8 ];
1483
+ for( const n of options )
1484
+ m.add( n, (v) => {
1485
+ this.tabSpaces = v;
1486
+ this.processLines();
1487
+ this._updateDataInfoPanel( "@tab-spaces", "Spaces: " + this.tabSpaces );
1488
+ } );
1489
+ });
1490
+ }, { width: "10%", nameWidth: "15%", signal: "@tab-spaces" });
1390
1491
  panel.addButton( "<b>{ }</b>", this.highlight, ( value, event ) => {
1391
1492
  LX.addContextMenu( "Language", event, m => {
1392
1493
  for( const lang of Object.keys(this.languages) )
1393
1494
  m.add( lang, this._changeLanguage.bind(this) );
1394
1495
  });
1395
- }, { width: "25%", nameWidth: "15%", signal: "@highlight" });
1496
+ }, { width: "17.5%", nameWidth: "15%", signal: "@highlight" });
1396
1497
  panel.endLine();
1397
1498
 
1398
1499
  return panel;
@@ -1639,12 +1740,12 @@ class CodeEditor {
1639
1740
  {
1640
1741
  this.processClick( e );
1641
1742
 
1642
- this.canOpenContextMenu = !this.selection;
1743
+ this.canOpenContextMenu = !cursor.selection;
1643
1744
 
1644
- if( this.selection )
1745
+ if( cursor.selection )
1645
1746
  {
1646
- this.canOpenContextMenu |= (cursor.line >= this.selection.fromY && cursor.line <= this.selection.toY
1647
- && cursor.position >= this.selection.fromX && cursor.position <= this.selection.toX);
1747
+ this.canOpenContextMenu |= (cursor.line >= cursor.selection.fromY && cursor.line <= cursor.selection.toY
1748
+ && cursor.position >= cursor.selection.fromX && cursor.position <= cursor.selection.toX);
1648
1749
  if( this.canOpenContextMenu )
1649
1750
  return;
1650
1751
  }
@@ -1664,7 +1765,7 @@ class CodeEditor {
1664
1765
  else if( e.type == 'mousemove' )
1665
1766
  {
1666
1767
  if( this.state.selectingText )
1667
- this.processSelection( e );
1768
+ this.processSelections( e );
1668
1769
  }
1669
1770
 
1670
1771
  else if ( e.type == 'click' ) // trip
@@ -1676,7 +1777,7 @@ class CodeEditor {
1676
1777
  this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
1677
1778
  this.cursorToPosition( cursor, from );
1678
1779
  this.startSelection( cursor );
1679
- this.selection.selectInline( from, cursor.line, this.measureString( word ) );
1780
+ cursor.selection.selectInline( cursor, from, cursor.line, this.measureString( word ) );
1680
1781
  this.cursorToString( cursor, word ); // Go to the end of the word
1681
1782
  break;
1682
1783
  // Select entire line
@@ -1705,6 +1806,8 @@ class CodeEditor {
1705
1806
  m.add( "" );
1706
1807
  m.add( "Format/JSON", () => {
1707
1808
  let json = this.toJSONFormat( this.getText() );
1809
+ if( !json )
1810
+ return;
1708
1811
  this.code.lines = json.split( "\n" );
1709
1812
  this.processLines();
1710
1813
  } );
@@ -1722,10 +1825,9 @@ class CodeEditor {
1722
1825
  this.endSelection();
1723
1826
  }
1724
1827
 
1725
- if( this.selection )
1726
- {
1727
- this.selection.invertIfNecessary();
1728
- }
1828
+ const cursor = this._getCurrentCursor();
1829
+ if( cursor.selection )
1830
+ cursor.selection.invertIfNecessary();
1729
1831
 
1730
1832
  this.state.selectingText = false;
1731
1833
  delete this._lastSelectionKeyDir;
@@ -1764,13 +1866,32 @@ class CodeEditor {
1764
1866
  this.hideAutoCompleteBox();
1765
1867
  }
1766
1868
 
1767
- processSelection( e, keep_range, flags = CodeEditor.SELECTION_X_Y ) {
1869
+ updateSelections( e, keep_range, flags = CodeEditor.SELECTION_X_Y ) {
1870
+
1871
+ for( let cursor of this.cursors.children )
1872
+ {
1873
+ if( !cursor.selection )
1874
+ continue;
1875
+
1876
+ this._processSelection( cursor, e, keep_range, flags );
1877
+ }
1878
+ }
1879
+
1880
+ processSelections( e, keep_range, flags = CodeEditor.SELECTION_X_Y ) {
1881
+
1882
+ for( let cursor of this.cursors.children )
1883
+ {
1884
+ this._processSelection( cursor, e, keep_range, flags );
1885
+ }
1886
+ }
1887
+
1888
+ _processSelection( cursor, e, keep_range, flags = CodeEditor.SELECTION_X_Y ) {
1768
1889
 
1769
- var cursor = this._getCurrentCursor();
1770
1890
  const isMouseEvent = e && ( e.constructor == MouseEvent );
1771
1891
 
1772
1892
  if( isMouseEvent ) this.processClick( e );
1773
- if( !this.selection )
1893
+
1894
+ if( !cursor.selection )
1774
1895
  this.startSelection( cursor );
1775
1896
 
1776
1897
  this._hideActiveLine();
@@ -1781,46 +1902,48 @@ class CodeEditor {
1781
1902
  let ccw = true;
1782
1903
 
1783
1904
  // Check if we must change ccw or not ... (not with mouse)
1784
- if( !isMouseEvent && this.line >= this.selection.fromY &&
1785
- (this.line == this.selection.fromY ? this.position >= this.selection.fromX : true) )
1905
+ if( !isMouseEvent && cursor.line >= cursor.selection.fromY &&
1906
+ (cursor.line == cursor.selection.fromY ? cursor.position >= cursor.selection.fromX : true) )
1786
1907
  {
1787
1908
  ccw = ( e && this._lastSelectionKeyDir && ( e.key == 'ArrowRight' || e.key == 'ArrowDown' || e.key == 'End' ) );
1788
1909
  }
1789
1910
 
1790
1911
  if( ccw )
1791
1912
  {
1792
- if( flags & CodeEditor.SELECTION_X ) this.selection.fromX = cursor.position;
1793
- if( flags & CodeEditor.SELECTION_Y ) this.selection.fromY = cursor.line;
1913
+ if( flags & CodeEditor.SELECTION_X ) cursor.selection.fromX = cursor.position;
1914
+ if( flags & CodeEditor.SELECTION_Y ) cursor.selection.fromY = cursor.line;
1794
1915
  }
1795
1916
  else
1796
1917
  {
1797
- if( flags & CodeEditor.SELECTION_X ) this.selection.toX = cursor.position;
1798
- if( flags & CodeEditor.SELECTION_Y ) this.selection.toY = cursor.line;
1918
+ if( flags & CodeEditor.SELECTION_X ) cursor.selection.toX = cursor.position;
1919
+ if( flags & CodeEditor.SELECTION_Y ) cursor.selection.toY = cursor.line;
1799
1920
  }
1800
1921
 
1801
1922
  this._lastSelectionKeyDir = ccw;
1802
1923
  }
1803
1924
 
1804
1925
  // Only leave if not a mouse selection...
1805
- if( !isMouseEvent && this.selection.isEmpty() )
1926
+ if( !isMouseEvent && cursor.selection.isEmpty() )
1806
1927
  {
1807
1928
  this.endSelection();
1808
1929
  return;
1809
1930
  }
1810
1931
 
1811
- this.selection.chars = 0;
1932
+ cursor.selection.chars = 0;
1812
1933
 
1813
- const fromX = this.selection.fromX,
1814
- fromY = this.selection.fromY,
1815
- toX = this.selection.toX,
1816
- toY = this.selection.toY;
1934
+ const fromX = cursor.selection.fromX,
1935
+ fromY = cursor.selection.fromY,
1936
+ toX = cursor.selection.toX,
1937
+ toY = cursor.selection.toY;
1817
1938
  const deltaY = toY - fromY;
1818
1939
 
1940
+ let cursorSelections = this.selections[ cursor.name ];
1941
+
1819
1942
  // Selection goes down...
1820
1943
  if( deltaY >= 0 )
1821
1944
  {
1822
- while( deltaY < ( this.selections.childElementCount - 1 ) )
1823
- deleteElement( this.selections.lastChild );
1945
+ while( deltaY < ( cursorSelections.childElementCount - 1 ) )
1946
+ deleteElement( cursorSelections.lastChild );
1824
1947
 
1825
1948
  for(let i = fromY; i <= toY; i++){
1826
1949
 
@@ -1831,12 +1954,12 @@ class CodeEditor {
1831
1954
  if( isVisible )
1832
1955
  {
1833
1956
  // Make sure that the line selection is generated...
1834
- domEl = this.selections.childNodes[ sId ];
1957
+ domEl = cursorSelections.childNodes[ sId ];
1835
1958
  if(!domEl)
1836
1959
  {
1837
1960
  domEl = document.createElement( 'div' );
1838
1961
  domEl.className = "lexcodeselection";
1839
- this.selections.appendChild( domEl );
1962
+ cursorSelections.appendChild( domEl );
1840
1963
  }
1841
1964
  }
1842
1965
 
@@ -1858,7 +1981,7 @@ class CodeEditor {
1858
1981
  }
1859
1982
 
1860
1983
  const stringWidth = this.measureString( string );
1861
- this.selection.chars += stringWidth / this.charWidth;
1984
+ cursor.selection.chars += stringWidth / this.charWidth;
1862
1985
 
1863
1986
  if( isVisible )
1864
1987
  {
@@ -1870,8 +1993,8 @@ class CodeEditor {
1870
1993
  }
1871
1994
  else // Selection goes up...
1872
1995
  {
1873
- while( Math.abs( deltaY ) < ( this.selections.childElementCount - 1 ) )
1874
- deleteElement( this.selections.firstChild );
1996
+ while( Math.abs( deltaY ) < ( cursorSelections.childElementCount - 1 ) )
1997
+ deleteElement( cursorSelections.firstChild );
1875
1998
 
1876
1999
  for( let i = toY; i <= fromY; i++ ){
1877
2000
 
@@ -1882,12 +2005,12 @@ class CodeEditor {
1882
2005
  if( isVisible )
1883
2006
  {
1884
2007
  // Make sure that the line selection is generated...
1885
- domEl = this.selections.childNodes[ sId ];
2008
+ domEl = cursorSelections.childNodes[ sId ];
1886
2009
  if(!domEl)
1887
2010
  {
1888
2011
  domEl = document.createElement( 'div' );
1889
2012
  domEl.className = "lexcodeselection";
1890
- this.selections.appendChild( domEl );
2013
+ cursorSelections.appendChild( domEl );
1891
2014
  }
1892
2015
  }
1893
2016
 
@@ -1907,7 +2030,7 @@ class CodeEditor {
1907
2030
  }
1908
2031
 
1909
2032
  const stringWidth = this.measureString( string );
1910
- this.selection.chars += stringWidth / this.charWidth;
2033
+ cursor.selection.chars += stringWidth / this.charWidth;
1911
2034
 
1912
2035
  if( isVisible )
1913
2036
  {
@@ -1998,46 +2121,59 @@ class CodeEditor {
1998
2121
 
1999
2122
  if( e.ctrlKey || e.metaKey )
2000
2123
  {
2001
- e.preventDefault();
2002
-
2003
2124
  switch( key.toLowerCase() ) {
2004
2125
  case 'a': // select all
2126
+ e.preventDefault();
2005
2127
  this.selectAll();
2006
2128
  return true;
2007
2129
  case 'c': // k+c, comment line
2130
+ e.preventDefault();
2008
2131
  if( this.state.keyChain == 'k' ) {
2009
2132
  this._commentLines();
2010
2133
  return true;
2011
2134
  }
2012
2135
  return false;
2136
+ case 'd': // next ocurrence
2137
+ e.preventDefault();
2138
+ this.selectNextOcurrence( cursor );
2139
+ return true;
2013
2140
  case 'f': // find/search
2141
+ e.preventDefault();
2014
2142
  this.showSearchBox();
2015
2143
  return true;
2016
2144
  case 'g': // find line
2145
+ e.preventDefault();
2017
2146
  this.showSearchLineBox();
2018
2147
  return true;
2019
2148
  case 'k': // shortcut chain
2149
+ e.preventDefault();
2020
2150
  this.state.keyChain = 'k';
2021
2151
  return true;
2022
2152
  case 's': // save
2153
+ e.preventDefault();
2023
2154
  this.onsave( this.getText() );
2024
2155
  return true;
2025
2156
  case 'u': // k+u, uncomment line
2157
+ e.preventDefault();
2026
2158
  if( this.state.keyChain == 'k' ) {
2027
2159
  this._uncommentLines();
2028
2160
  return true;
2029
2161
  }
2030
2162
  return false;
2031
2163
  case 'y': // redo
2164
+ e.preventDefault();
2032
2165
  this._doRedo( cursor );
2033
2166
  return true;
2034
2167
  case 'z': // undo
2168
+ e.preventDefault();
2035
2169
  this._doUndo( cursor );
2036
2170
  return true;
2037
2171
  case '+': // increase size
2172
+ e.preventDefault();
2038
2173
  this._increaseFontSize();
2039
2174
  return true;
2040
2175
  case '-': // decrease size
2176
+ e.preventDefault();
2041
2177
  this._decreaseFontSize();
2042
2178
  return true;
2043
2179
  }
@@ -2066,14 +2202,8 @@ class CodeEditor {
2066
2202
  {
2067
2203
  switch( key.toLowerCase() ) {
2068
2204
  case 'c': // copy
2069
- // TODO: COPY TEXT FROM EVERY CURSOR
2070
2205
  this._copyContent( cursor );
2071
2206
  return;
2072
- case 'd': // duplicate line
2073
- e.preventDefault();
2074
- // TODO: UPDATE NEXT CURSOR ON MODIFY STATE
2075
- this._duplicateLine( lidx, cursor );
2076
- return;
2077
2207
  case 'v': // paste
2078
2208
  this._pasteContent( cursor );
2079
2209
  return;
@@ -2094,6 +2224,10 @@ class CodeEditor {
2094
2224
  else if( e.altKey )
2095
2225
  {
2096
2226
  switch( key ) {
2227
+ case 'd': // duplicate line
2228
+ e.preventDefault();
2229
+ this._duplicateLine( lidx, cursor );
2230
+ return;
2097
2231
  case 'ArrowUp':
2098
2232
  if(this.code.lines[ lidx - 1 ] == undefined)
2099
2233
  return;
@@ -2121,10 +2255,12 @@ class CodeEditor {
2121
2255
 
2122
2256
  for( const actKey in this.actions ) {
2123
2257
 
2124
- if( key != actKey ) continue;
2258
+ if( key != actKey )
2259
+ continue;
2260
+
2125
2261
  e.preventDefault();
2126
2262
 
2127
- if( this.actions[ key ].deleteSelection && this.selection )
2263
+ if( this._actionMustDelete( cursor, this.actions[ key ], e ) )
2128
2264
  this.actions['Backspace'].callback( lidx, cursor, e );
2129
2265
 
2130
2266
  return this.actions[ key ].callback( lidx, cursor, e );
@@ -2153,7 +2289,7 @@ class CodeEditor {
2153
2289
  // Until this point, if there was a selection, we need
2154
2290
  // to delete the content..
2155
2291
 
2156
- if( this.selection )
2292
+ if( cursor.selection )
2157
2293
  {
2158
2294
  this.actions['Backspace'].callback( lidx, cursor, e );
2159
2295
  lidx = cursor.line;
@@ -2214,22 +2350,32 @@ class CodeEditor {
2214
2350
 
2215
2351
  let text = await navigator.clipboard.readText();
2216
2352
 
2353
+ // Remove any possible tabs (\t) and add spaces
2354
+ text = text.replaceAll( /\t|\\t/g, ' '.repeat( this.tabSpaces ) );
2355
+
2217
2356
  this._addUndoStep( cursor, true );
2218
2357
 
2219
2358
  this.appendText( text, cursor );
2359
+
2360
+ const currentScroll = this.getScrollTop();
2361
+ const scroll = Math.max( cursor.line * this.lineHeight - this.codeScroller.offsetWidth, 0 );
2362
+
2363
+ if( currentScroll < scroll ) {
2364
+ this.codeScroller.scrollTo( 0, cursor.line * this.lineHeight );
2365
+ }
2220
2366
  }
2221
2367
 
2222
2368
  async _copyContent( cursor ) {
2223
2369
 
2224
2370
  let text_to_copy = "";
2225
2371
 
2226
- if( !this.selection ) {
2372
+ if( !cursor.selection ) {
2227
2373
  text_to_copy = "\n" + this.code.lines[ cursor.line ];
2228
2374
  }
2229
2375
  else {
2230
2376
 
2231
2377
  // Some selections don't depend on mouse up..
2232
- if( this.selection ) this.selection.invertIfNecessary();
2378
+ if( cursor.selection ) cursor.selection.invertIfNecessary();
2233
2379
 
2234
2380
  const separator = "_NEWLINE_";
2235
2381
  let code = this.code.lines.join( separator );
@@ -2237,11 +2383,11 @@ class CodeEditor {
2237
2383
  // Get linear start index
2238
2384
  let index = 0;
2239
2385
 
2240
- for( let i = 0; i <= this.selection.fromY; i++ )
2241
- index += ( i == this.selection.fromY ? this.selection.fromX : this.code.lines[ i ].length );
2386
+ for( let i = 0; i <= cursor.selection.fromY; i++ )
2387
+ index += ( i == cursor.selection.fromY ? cursor.selection.fromX : this.code.lines[ i ].length );
2242
2388
 
2243
- index += this.selection.fromY * separator.length;
2244
- const num_chars = this.selection.chars + ( this.selection.toY - this.selection.fromY ) * separator.length;
2389
+ index += cursor.selection.fromY * separator.length;
2390
+ const num_chars = cursor.selection.chars + ( cursor.selection.toY - cursor.selection.fromY ) * separator.length;
2245
2391
  const text = code.substr( index, num_chars );
2246
2392
  const lines = text.split( separator );
2247
2393
  text_to_copy = lines.join('\n');
@@ -2257,7 +2403,7 @@ class CodeEditor {
2257
2403
 
2258
2404
  this._addUndoStep( cursor, true );
2259
2405
 
2260
- if( !this.selection ) {
2406
+ if( !cursor.selection ) {
2261
2407
  text_to_cut = "\n" + this.code.lines[ cursor.line ];
2262
2408
  this.code.lines.splice( lidx, 1 );
2263
2409
  this.processLines();
@@ -2268,7 +2414,7 @@ class CodeEditor {
2268
2414
  else {
2269
2415
 
2270
2416
  // Some selections don't depend on mouse up..
2271
- if( this.selection ) this.selection.invertIfNecessary();
2417
+ if( cursor.selection ) cursor.selection.invertIfNecessary();
2272
2418
 
2273
2419
  const separator = "_NEWLINE_";
2274
2420
  let code = this.code.lines.join(separator);
@@ -2276,11 +2422,11 @@ class CodeEditor {
2276
2422
  // Get linear start index
2277
2423
  let index = 0;
2278
2424
 
2279
- for(let i = 0; i <= this.selection.fromY; i++)
2280
- index += (i == this.selection.fromY ? this.selection.fromX : this.code.lines[ i ].length);
2425
+ for(let i = 0; i <= cursor.selection.fromY; i++)
2426
+ index += (i == cursor.selection.fromY ? cursor.selection.fromX : this.code.lines[ i ].length);
2281
2427
 
2282
- index += this.selection.fromY * separator.length;
2283
- const num_chars = this.selection.chars + (this.selection.toY - this.selection.fromY) * separator.length;
2428
+ index += cursor.selection.fromY * separator.length;
2429
+ const num_chars = cursor.selection.chars + (cursor.selection.toY - cursor.selection.fromY) * separator.length;
2284
2430
  const text = code.substr(index, num_chars);
2285
2431
  const lines = text.split(separator);
2286
2432
  text_to_cut = lines.join('\n');
@@ -2305,18 +2451,18 @@ class CodeEditor {
2305
2451
 
2306
2452
  this.state.keyChain = null;
2307
2453
 
2308
- if( this.selection )
2454
+ if( cursor.selection )
2309
2455
  {
2310
2456
  var cursor = this._getCurrentCursor();
2311
2457
  this._addUndoStep( cursor, true );
2312
2458
 
2313
- const selectedLines = this.code.lines.slice( this.selection.fromY, this.selection.toY );
2459
+ const selectedLines = this.code.lines.slice( cursor.selection.fromY, cursor.selection.toY );
2314
2460
  const minIdx = Math.min(...selectedLines.map( v => {
2315
2461
  var idx = firstNonspaceIndex( v );
2316
2462
  return idx < 0 ? 1e10 : idx;
2317
2463
  } ));
2318
2464
 
2319
- for( var i = this.selection.fromY; i <= this.selection.toY; ++i )
2465
+ for( var i = cursor.selection.fromY; i <= cursor.selection.toY; ++i )
2320
2466
  {
2321
2467
  this._commentLine( cursor, i, minIdx );
2322
2468
  }
@@ -2364,12 +2510,12 @@ class CodeEditor {
2364
2510
 
2365
2511
  this.state.keyChain = null;
2366
2512
 
2367
- if( this.selection )
2513
+ if( cursor.selection )
2368
2514
  {
2369
2515
  var cursor = this._getCurrentCursor();
2370
2516
  this._addUndoStep( cursor, true );
2371
2517
 
2372
- for( var i = this.selection.fromY; i <= this.selection.toY; ++i )
2518
+ for( var i = cursor.selection.fromY; i <= cursor.selection.toY; ++i )
2373
2519
  {
2374
2520
  this._uncommentLine( cursor, i );
2375
2521
  }
@@ -2409,14 +2555,22 @@ class CodeEditor {
2409
2555
  }
2410
2556
  }
2411
2557
 
2412
- action( key, deleteSelection, fn ) {
2558
+ action( key, deleteSelection, fn, eventSkipDelete ) {
2413
2559
 
2414
2560
  this.actions[ key ] = {
2561
+ "key": key,
2415
2562
  "callback": fn,
2416
- "deleteSelection": deleteSelection
2563
+ "deleteSelection": deleteSelection,
2564
+ "eventSkipDelete": eventSkipDelete
2417
2565
  };
2418
2566
  }
2419
2567
 
2568
+ _actionMustDelete( cursor, action, e ) {
2569
+
2570
+ return cursor.selection && action.deleteSelection &&
2571
+ ( action.eventSkipDelete ? !e[ action.eventSkipDelete ] : true );
2572
+ }
2573
+
2420
2574
  scanWordSuggestions() {
2421
2575
 
2422
2576
  this.code.tokens = {};
@@ -2478,8 +2632,7 @@ class CodeEditor {
2478
2632
  this.code.style.top = ( this.visibleLinesViewport.x * this.lineHeight ) + "px";
2479
2633
 
2480
2634
  // Update selections
2481
- if( this.selection )
2482
- this.processSelection( null, true );
2635
+ this.updateSelections( null, true );
2483
2636
 
2484
2637
  this._clearTmpVariables();
2485
2638
  this._setActiveLine();
@@ -2742,111 +2895,98 @@ class CodeEditor {
2742
2895
 
2743
2896
  const usesBlockComments = lang.blockComments ?? true;
2744
2897
  const blockCommentsTokens = lang.blockCommentsTokens ?? this.defaultBlockCommentTokens;
2898
+ const singleLineCommentToken = lang.singleLineCommentToken ?? this.defaultSingleLineCommentToken;
2899
+
2900
+ let token_classname = "";
2901
+ let discardToken = false;
2745
2902
 
2746
- if( !usePreviousTokenToCheckString && token == ' ' )
2747
- {
2748
- if( this._buildingString != undefined )
2749
- {
2750
- this._appendStringToken( token );
2751
- return "";
2752
- }
2753
- return token;
2754
- }
2755
- else
2756
- {
2757
- const singleLineCommentToken = lang.singleLineCommentToken ?? this.defaultSingleLineCommentToken;
2758
-
2759
- let token_classname = "";
2760
- let discardToken = false;
2761
-
2762
- if( this._buildingBlockComment != undefined )
2763
- token_classname = "cm-com";
2764
-
2765
- else if( this._buildingString != undefined )
2766
- discardToken = this._appendStringToken( token );
2767
-
2768
- else if( this._mustHightlightWord( token, CodeEditor.keywords ) && ( lang.tags ?? false ? ( this._enclosedByTokens( token, tokenIndex, '<', '>' ) ) : true ) )
2769
- token_classname = "cm-kwd";
2903
+ if( this._buildingBlockComment != undefined )
2904
+ token_classname = "cm-com";
2905
+
2906
+ else if( this._buildingString != undefined )
2907
+ discardToken = this._appendStringToken( token );
2908
+
2909
+ else if( this._mustHightlightWord( token, CodeEditor.keywords ) && ( lang.tags ?? false ? ( this._enclosedByTokens( token, tokenIndex, '<', '>' ) ) : true ) )
2910
+ token_classname = "cm-kwd";
2770
2911
 
2771
- else if( this._mustHightlightWord( token, CodeEditor.builtin ) && ( lang.tags ?? false ? ( this._enclosedByTokens( token, tokenIndex, '<', '>' ) ) : true ) )
2772
- token_classname = "cm-bln";
2912
+ else if( this._mustHightlightWord( token, CodeEditor.builtin ) && ( lang.tags ?? false ? ( this._enclosedByTokens( token, tokenIndex, '<', '>' ) ) : true ) )
2913
+ token_classname = "cm-bln";
2773
2914
 
2774
- else if( this._mustHightlightWord( token, CodeEditor.statementsAndDeclarations ) )
2775
- token_classname = "cm-std";
2915
+ else if( this._mustHightlightWord( token, CodeEditor.statementsAndDeclarations ) )
2916
+ token_classname = "cm-std";
2776
2917
 
2777
- else if( this._mustHightlightWord( token, CodeEditor.symbols ) )
2778
- token_classname = "cm-sym";
2918
+ else if( this._mustHightlightWord( token, CodeEditor.symbols ) )
2919
+ token_classname = "cm-sym";
2779
2920
 
2780
- else if( token.substr( 0, singleLineCommentToken.length ) == singleLineCommentToken )
2781
- token_classname = "cm-com";
2921
+ else if( token.substr( 0, singleLineCommentToken.length ) == singleLineCommentToken )
2922
+ token_classname = "cm-com";
2782
2923
 
2783
- else if( this._isNumber( token ) || this._isNumber( token.replace(/[px]|[em]|%/g,'') ) )
2784
- token_classname = "cm-dec";
2924
+ else if( this._isNumber( token ) || this._isNumber( token.replace(/[px]|[em]|%/g,'') ) )
2925
+ token_classname = "cm-dec";
2785
2926
 
2786
- else if( this._isCSSClass( token, prev, next ) )
2787
- token_classname = "cm-kwd";
2927
+ else if( this._isCSSClass( token, prev, next ) )
2928
+ token_classname = "cm-kwd";
2788
2929
 
2789
- else if ( this._isType( token, prev, next ) )
2790
- token_classname = "cm-typ";
2930
+ else if ( this._isType( token, prev, next ) )
2931
+ token_classname = "cm-typ";
2791
2932
 
2792
- else if ( highlight == 'batch' && ( token == '@' || prev == ':' || prev == '@' ) )
2793
- token_classname = "cm-kwd";
2933
+ else if ( highlight == 'batch' && ( token == '@' || prev == ':' || prev == '@' ) )
2934
+ token_classname = "cm-kwd";
2794
2935
 
2795
- else if ( [ 'cpp', 'wgsl', 'glsl' ].indexOf( highlight ) > -1 && token.includes( '#' ) ) // C++ preprocessor
2796
- token_classname = "cm-ppc";
2936
+ else if ( [ 'cpp', 'wgsl', 'glsl' ].indexOf( highlight ) > -1 && token.includes( '#' ) ) // C++ preprocessor
2937
+ token_classname = "cm-ppc";
2797
2938
 
2798
- else if ( highlight == 'cpp' && prev == '<' && (next == '>' || next == '*') ) // Defining template type in C++
2799
- token_classname = "cm-typ";
2939
+ else if ( highlight == 'cpp' && prev == '<' && (next == '>' || next == '*') ) // Defining template type in C++
2940
+ token_classname = "cm-typ";
2800
2941
 
2801
- else if ( highlight == 'cpp' && (next == '::' || prev == '::' && next != '(' )) // C++ Class
2802
- token_classname = "cm-typ";
2942
+ else if ( highlight == 'cpp' && (next == '::' || prev == '::' && next != '(' )) // C++ Class
2943
+ token_classname = "cm-typ";
2803
2944
 
2804
- else if ( highlight == 'css' && prev == ':' && (next == ';' || next == '!important') ) // CSS value
2805
- token_classname = "cm-str";
2945
+ else if ( highlight == 'css' && prev == ':' && (next == ';' || next == '!important') ) // CSS value
2946
+ token_classname = "cm-str";
2806
2947
 
2807
- else if ( highlight == 'css' && prev == undefined && next == ':' ) // CSS attribute
2808
- token_classname = "cm-typ";
2809
-
2810
- else if ( this._markdownHeader || ( highlight == 'markdown' && isFirstToken && token.replaceAll('#', '').length != token.length ) ) // Header
2811
- {
2812
- token_classname = "cm-kwd";
2813
- this._markdownHeader = true;
2814
- }
2948
+ else if ( highlight == 'css' && prev == undefined && next == ':' ) // CSS attribute
2949
+ token_classname = "cm-typ";
2950
+
2951
+ else if ( this._markdownHeader || ( highlight == 'markdown' && isFirstToken && token.replaceAll('#', '').length != token.length ) ) // Header
2952
+ {
2953
+ token_classname = "cm-kwd";
2954
+ this._markdownHeader = true;
2955
+ }
2815
2956
 
2816
- else if ( token[ 0 ] != '@' && token[ 0 ] != ',' && next == '(' )
2817
- token_classname = "cm-mtd";
2957
+ else if ( token[ 0 ] != '@' && token[ 0 ] != ',' && next == '(' )
2958
+ token_classname = "cm-mtd";
2818
2959
 
2819
2960
 
2820
- if( usesBlockComments && this._buildingBlockComment != undefined
2821
- && token.substr( 0, blockCommentsTokens[ 1 ].length ) == blockCommentsTokens[ 1 ] )
2822
- {
2823
- this._blockCommentCache.push( new LX.vec2( this._buildingBlockComment, this._currentLineNumber ) );
2824
- delete this._buildingBlockComment;
2825
- }
2961
+ if( usesBlockComments && this._buildingBlockComment != undefined
2962
+ && token.substr( 0, blockCommentsTokens[ 1 ].length ) == blockCommentsTokens[ 1 ] )
2963
+ {
2964
+ this._blockCommentCache.push( new LX.vec2( this._buildingBlockComment, this._currentLineNumber ) );
2965
+ delete this._buildingBlockComment;
2966
+ }
2826
2967
 
2827
- // We finished constructing a string
2828
- if( this._buildingString && ( this._stringEnded || isLastToken ) )
2829
- {
2830
- token = this._getCurrentString();
2831
- token_classname = "cm-str";
2832
- discardToken = false;
2833
- }
2968
+ // We finished constructing a string
2969
+ if( this._buildingString && ( this._stringEnded || isLastToken ) )
2970
+ {
2971
+ token = this._getCurrentString();
2972
+ token_classname = "cm-str";
2973
+ discardToken = false;
2974
+ }
2834
2975
 
2835
- // Update state
2836
- this._buildingString = this._stringEnded ? undefined : this._buildingString;
2976
+ // Update state
2977
+ this._buildingString = this._stringEnded ? undefined : this._buildingString;
2837
2978
 
2838
- if( discardToken )
2839
- return "";
2979
+ if( discardToken )
2980
+ return "";
2840
2981
 
2841
- token = token.replace( "<", "&lt;" );
2842
- token = token.replace( ">", "&gt;" );
2982
+ token = token.replace( "<", "&lt;" );
2983
+ token = token.replace( ">", "&gt;" );
2843
2984
 
2844
- // No highlighting, no need to put it inside another span..
2845
- if( !token_classname.length )
2846
- return token;
2985
+ // No highlighting, no need to put it inside another span..
2986
+ if( !token_classname.length )
2987
+ return token;
2847
2988
 
2848
- return "<span class='" + highlight + " " + token_classname + "'>" + token + "</span>";
2849
- }
2989
+ return "<span class='" + highlight + " " + token_classname + "'>" + token + "</span>";
2850
2990
  }
2851
2991
 
2852
2992
  _appendStringToken( token ) {
@@ -2952,20 +3092,20 @@ class CodeEditor {
2952
3092
 
2953
3093
  _encloseSelectedWordWithKey( key, lidx, cursor ) {
2954
3094
 
2955
- if( !this.selection || (this.selection.fromY != this.selection.toY) )
3095
+ if( !cursor.selection || (cursor.selection.fromY != cursor.selection.toY) )
2956
3096
  return false;
2957
3097
 
2958
- this.selection.invertIfNecessary();
3098
+ cursor.selection.invertIfNecessary();
2959
3099
 
2960
3100
  // Insert first..
2961
3101
  this.code.lines[ lidx ] = [
2962
- this.code.lines[ lidx ].slice(0, this.selection.fromX),
3102
+ this.code.lines[ lidx ].slice(0, cursor.selection.fromX),
2963
3103
  key,
2964
- this.code.lines[ lidx ].slice(this.selection.fromX)
3104
+ this.code.lines[ lidx ].slice(cursor.selection.fromX)
2965
3105
  ].join('');
2966
3106
 
2967
3107
  // Go to the end of the word
2968
- this.cursorToPosition(cursor, this.selection.toX + 1);
3108
+ this.cursorToPosition(cursor, cursor.selection.toX + 1);
2969
3109
 
2970
3110
  // Change next key?
2971
3111
  switch(key)
@@ -2986,10 +3126,10 @@ class CodeEditor {
2986
3126
 
2987
3127
  // Recompute and reposition current selection
2988
3128
 
2989
- this.selection.fromX++;
2990
- this.selection.toX++;
3129
+ cursor.selection.fromX++;
3130
+ cursor.selection.toX++;
2991
3131
 
2992
- this.processSelection();
3132
+ this._processSelection( cursor );
2993
3133
  this.processLine( lidx );
2994
3134
 
2995
3135
  // Stop propagation
@@ -3037,14 +3177,16 @@ class CodeEditor {
3037
3177
 
3038
3178
  startSelection( cursor ) {
3039
3179
 
3040
- // Clear other selections...
3041
- this.selections.innerHTML = "";
3042
-
3043
3180
  // Show elements
3044
- this.selections.classList.add( 'show' );
3181
+ let selectionContainer = document.createElement( 'div' );
3182
+ selectionContainer.className = 'selections';
3183
+ selectionContainer.classList.add( 'show' );
3184
+
3185
+ this.codeSizer.insertChildAtIndex( selectionContainer, 2 );
3186
+ this.selections[ cursor.name ] = selectionContainer;
3045
3187
 
3046
3188
  // Create new selection instance
3047
- this.selection = new CodeSelection( this, cursor.position, cursor.line );
3189
+ cursor.selection = new CodeSelection( this, cursor );
3048
3190
  }
3049
3191
 
3050
3192
  deleteSelection( cursor ) {
@@ -3054,37 +3196,52 @@ class CodeEditor {
3054
3196
  return;
3055
3197
 
3056
3198
  // Some selections don't depend on mouse up..
3057
- if( this.selection ) this.selection.invertIfNecessary();
3199
+ if( cursor.selection ) cursor.selection.invertIfNecessary();
3058
3200
 
3059
3201
  const separator = "_NEWLINE_";
3060
3202
  let code = this.code.lines.join( separator );
3061
3203
 
3062
3204
  // Get linear start index
3063
3205
  let index = 0;
3064
- for( let i = 0; i <= this.selection.fromY; i++ )
3065
- index += (i == this.selection.fromY ? this.selection.fromX : this.code.lines[ i ].length);
3206
+ for( let i = 0; i <= cursor.selection.fromY; i++ )
3207
+ index += (i == cursor.selection.fromY ? cursor.selection.fromX : this.code.lines[ i ].length);
3066
3208
 
3067
- index += this.selection.fromY * separator.length;
3209
+ index += cursor.selection.fromY * separator.length;
3068
3210
 
3069
- const num_chars = this.selection.chars + (this.selection.toY - this.selection.fromY) * separator.length;
3211
+ const num_chars = cursor.selection.chars + (cursor.selection.toY - cursor.selection.fromY) * separator.length;
3070
3212
  const pre = code.slice( 0, index );
3071
3213
  const post = code.slice( index + num_chars );
3072
3214
 
3073
3215
  this.code.lines = ( pre + post ).split( separator );
3074
3216
 
3075
- this.cursorToLine( cursor, this.selection.fromY, true );
3076
- this.cursorToPosition( cursor, this.selection.fromX );
3077
- this.endSelection();
3217
+ this.cursorToLine( cursor, cursor.selection.fromY, true );
3218
+ this.cursorToPosition( cursor, cursor.selection.fromX );
3219
+ this.endSelection( cursor );
3078
3220
  this.processLines();
3079
3221
  }
3080
3222
 
3081
- endSelection() {
3223
+ endSelection( cursor ) {
3082
3224
 
3083
- this.selections.classList.remove( 'show' );
3084
- this.selections.innerHTML = "";
3085
- delete this.selection;
3086
3225
  delete this._tripleClickSelection;
3087
3226
  delete this._lastSelectionKeyDir;
3227
+ delete this._currentOcurrences;
3228
+ delete this._lastResult;
3229
+
3230
+ if( cursor )
3231
+ {
3232
+ deleteElement( this.selections[ cursor.name ] );
3233
+ delete this.selections[ cursor.name ];
3234
+ delete cursor.selection;
3235
+ }
3236
+ else
3237
+ {
3238
+ for( let cursor of this.cursors.children )
3239
+ {
3240
+ deleteElement( this.selections[ cursor.name ] );
3241
+ delete this.selections[ cursor.name ];
3242
+ delete cursor.selection;
3243
+ }
3244
+ }
3088
3245
  }
3089
3246
 
3090
3247
  selectAll() {
@@ -3098,13 +3255,13 @@ class CodeEditor {
3098
3255
  this.startSelection( cursor );
3099
3256
 
3100
3257
  const nlines = this.code.lines.length - 1;
3101
- this.selection.toX = this.code.lines[ nlines ].length;
3102
- this.selection.toY = nlines;
3258
+ cursor.selection.toX = this.code.lines[ nlines ].length;
3259
+ cursor.selection.toY = nlines;
3103
3260
 
3104
- this.cursorToPosition( cursor, this.selection.toX );
3105
- this.cursorToLine( cursor, this.selection.toY );
3261
+ this.cursorToPosition( cursor, cursor.selection.toX );
3262
+ this.cursorToLine( cursor, cursor.selection.toY );
3106
3263
 
3107
- this.processSelection( null, true );
3264
+ this._processSelection( cursor, null, true );
3108
3265
 
3109
3266
  this.hideAutoCompleteBox();
3110
3267
  }
@@ -3210,6 +3367,8 @@ class CodeEditor {
3210
3367
 
3211
3368
  state.position = cursor.position;
3212
3369
  state.line = cursor.line;
3370
+ state.selection = cursor.selection ? cursor.selection.save() : undefined;
3371
+
3213
3372
  return state;
3214
3373
  }
3215
3374
 
@@ -3242,7 +3401,7 @@ class CodeEditor {
3242
3401
  const cursorsInLine = Array.from( this.cursors.children ).filter( v => v.line == line );
3243
3402
 
3244
3403
  while( cursorsInLine.length > 1 )
3245
- cursorsInLine.pop().remove();
3404
+ this.removeCursor( cursorsInLine.pop() );
3246
3405
  }
3247
3406
 
3248
3407
  restoreCursor( cursor, state ) {
@@ -3254,6 +3413,22 @@ class CodeEditor {
3254
3413
  cursor.style.left = "calc(" + cursor._left + "px + " + this.xPadding + ")";
3255
3414
  cursor._top = cursor.line * this.lineHeight;
3256
3415
  cursor.style.top = "calc(" + cursor._top + "px)";
3416
+
3417
+ if( state.selection )
3418
+ {
3419
+ this.startSelection( cursor );
3420
+
3421
+ cursor.selection.load( state.selection );
3422
+
3423
+ this._processSelection( cursor, null, true );
3424
+ }
3425
+ }
3426
+
3427
+ removeCursor( cursor ) {
3428
+
3429
+ deleteElement( this.selections[ cursor.name ] );
3430
+ delete this.selections[ cursor.name ];
3431
+ deleteElement( cursor );
3257
3432
  }
3258
3433
 
3259
3434
  resetCursorPos( flag, cursor ) {
@@ -3275,14 +3450,14 @@ class CodeEditor {
3275
3450
  }
3276
3451
  }
3277
3452
 
3278
- addSpaceTabs( n ) {
3453
+ _addSpaceTabs( cursor, n ) {
3279
3454
 
3280
3455
  for( var i = 0; i < n; ++i ) {
3281
- this.actions[ 'Tab' ].callback();
3456
+ this.actions[ 'Tab' ].callback( cursor.line, cursor, null );
3282
3457
  }
3283
3458
  }
3284
3459
 
3285
- addSpaces( n ) {
3460
+ _addSpaces( n ) {
3286
3461
 
3287
3462
  for( var i = 0; i < n; ++i ) {
3288
3463
  this.root.dispatchEvent( new CustomEvent( 'keydown', { 'detail': {
@@ -3293,6 +3468,37 @@ class CodeEditor {
3293
3468
  }
3294
3469
  }
3295
3470
 
3471
+ _removeSpaces( cursor ) {
3472
+
3473
+ // Remove indentation
3474
+ const lidx = cursor.line;
3475
+ const lineStart = firstNonspaceIndex( this.code.lines[ lidx ] );
3476
+
3477
+ // Nothing to remove...
3478
+ if( lineStart == 0 )
3479
+ return;
3480
+
3481
+ let indentSpaces = lineStart % this.tabSpaces;
3482
+ indentSpaces = indentSpaces == 0 ? this.tabSpaces : indentSpaces;
3483
+ const newStart = Math.max( lineStart - indentSpaces, 0 );
3484
+
3485
+ this.code.lines[ lidx ] = [
3486
+ this.code.lines[ lidx ].slice( 0, newStart ),
3487
+ this.code.lines[ lidx ].slice( lineStart )
3488
+ ].join('');
3489
+
3490
+ this.processLine( lidx );
3491
+
3492
+ this.cursorToString( cursor, " ".repeat( indentSpaces ), true );
3493
+
3494
+ if( cursor.selection )
3495
+ {
3496
+ cursor.selection.invertIfNecessary();
3497
+ cursor.selection.fromX = Math.max( cursor.selection.fromX - indentSpaces, 0 );
3498
+ this._processSelection( cursor );
3499
+ }
3500
+ }
3501
+
3296
3502
  getScrollLeft() {
3297
3503
 
3298
3504
  if( !this.codeScroller ) return 0;
@@ -3562,8 +3768,11 @@ class CodeEditor {
3562
3768
 
3563
3769
  showAutoCompleteBox( key, cursor ) {
3564
3770
 
3771
+ if( !cursor.isMain )
3772
+ return;
3773
+
3565
3774
  const [word, start, end] = this.getWordAtPos( cursor, -1 );
3566
- if(key == ' ' || !word.length) {
3775
+ if( key == ' ' || !word.length ) {
3567
3776
  this.hideAutoCompleteBox();
3568
3777
  return;
3569
3778
  }
@@ -3610,7 +3819,7 @@ class CodeEditor {
3610
3819
  pre.appendChild( icon );
3611
3820
 
3612
3821
  pre.addEventListener( 'click', () => {
3613
- this.autoCompleteWord( cursor, s );
3822
+ this.autoCompleteWord( s );
3614
3823
  } );
3615
3824
 
3616
3825
  // Highlight the written part
@@ -3642,8 +3851,8 @@ class CodeEditor {
3642
3851
  // Show box
3643
3852
  this.autocomplete.classList.toggle('show', true);
3644
3853
  this.autocomplete.classList.toggle('no-scrollbar', !(this.autocomplete.scrollHeight > this.autocomplete.offsetHeight));
3645
- this.autocomplete.style.left = (cursor._left + 36 - this.getScrollLeft()) + "px";
3646
- this.autocomplete.style.top = (cursor._top + 48 - this.getScrollTop()) + "px";
3854
+ this.autocomplete.style.left = (cursor._left + 48 - this.getScrollLeft()) + "px";
3855
+ this.autocomplete.style.top = (cursor._top + 28 + this.lineHeight - this.getScrollTop()) + "px";
3647
3856
 
3648
3857
  this.isAutoCompleteActive = true;
3649
3858
  }
@@ -3656,23 +3865,30 @@ class CodeEditor {
3656
3865
  return isActive != this.isAutoCompleteActive;
3657
3866
  }
3658
3867
 
3659
- autoCompleteWord( cursor, suggestion ) {
3868
+ autoCompleteWord( suggestion ) {
3660
3869
 
3661
3870
  if( !this.isAutoCompleteActive )
3662
- return;
3871
+ return;
3663
3872
 
3664
3873
  let [suggestedWord, idx] = this._getSelectedAutoComplete();
3665
3874
  suggestedWord = suggestion ?? suggestedWord;
3666
3875
 
3667
- const [word, start, end] = this.getWordAtPos( cursor, -1 );
3876
+ for( let cursor of this.cursors.children )
3877
+ {
3878
+ const [word, start, end] = this.getWordAtPos( cursor, -1 );
3879
+
3880
+ const lineString = this.code.lines[ cursor.line ];
3881
+ this.code.lines[ cursor.line ] =
3882
+ lineString.slice(0, start) + suggestedWord + lineString.slice( end );
3883
+
3884
+ // Process lines and remove suggestion box
3885
+ this.cursorToPosition( cursor, start + suggestedWord.length );
3886
+ this.processLine( cursor.line );
3887
+ }
3668
3888
 
3669
- const lineString = this.code.lines[ cursor.line ];
3670
- this.code.lines[ cursor.line ] =
3671
- lineString.slice(0, start) + suggestedWord + lineString.slice(end);
3889
+ // Only the main cursor autocompletes, skip the "Tab" event for the rest
3890
+ this._skipTabs = this.cursors.childElementCount - 1;
3672
3891
 
3673
- // Process lines and remove suggestion box
3674
- this.cursorToPosition(cursor, start + suggestedWord.length);
3675
- this.processLine(cursor.line);
3676
3892
  this.hideAutoCompleteBox();
3677
3893
  }
3678
3894
 
@@ -3718,6 +3934,8 @@ class CodeEditor {
3718
3934
 
3719
3935
  showSearchBox( clear ) {
3720
3936
 
3937
+ this.hideSearchLineBox();
3938
+
3721
3939
  this.searchbox.classList.add( 'opened' );
3722
3940
  this.searchboxActive = true;
3723
3941
 
@@ -3727,12 +3945,26 @@ class CodeEditor {
3727
3945
  {
3728
3946
  input.value = "";
3729
3947
  }
3948
+ else
3949
+ {
3950
+ const cursor = this._getCurrentCursor();
3951
+
3952
+ if( cursor.selection )
3953
+ {
3954
+ input.value = cursor.selection.getText() ?? input.value;
3955
+ }
3956
+ }
3957
+
3958
+ input.selectionStart = 0;
3959
+ input.selectionEnd = input.value.length;
3730
3960
 
3731
3961
  input.focus();
3732
3962
  }
3733
3963
 
3734
3964
  hideSearchBox() {
3735
3965
 
3966
+ const active = this.searchboxActive;
3967
+
3736
3968
  if( this.searchboxActive )
3737
3969
  {
3738
3970
  this.searchbox.classList.remove( 'opened' );
@@ -3741,31 +3973,49 @@ class CodeEditor {
3741
3973
 
3742
3974
  else if( this._lastResult )
3743
3975
  {
3744
- this._lastResult.dom.remove();
3976
+ deleteElement( this._lastResult.dom );
3745
3977
  delete this._lastResult;
3746
3978
  }
3979
+
3980
+ this.searchResultSelections.classList.remove( 'show' );
3981
+
3982
+ return active != this.searchboxActive;
3747
3983
  }
3748
3984
 
3749
- search( text, reverse ) {
3985
+ search( text, reverse, callback, skipAlert ) {
3750
3986
 
3751
3987
  text = text ?? this._lastTextFound;
3752
3988
 
3753
3989
  if( !text )
3754
3990
  return;
3755
3991
 
3756
- let cursorData = new LX.vec2( this.position, this.line );
3992
+ let cursor = this._getCurrentCursor();
3993
+ let cursorData = new LX.vec2( cursor.position, cursor.line );
3757
3994
  let line = null;
3758
3995
  let char = -1;
3759
3996
 
3760
3997
  if( this._lastResult )
3761
3998
  {
3762
- this._lastResult.dom.remove();
3999
+ deleteElement( this._lastResult.dom );
3763
4000
  cursorData = this._lastResult.pos;
3764
4001
  delete this._lastResult;
3765
4002
  }
3766
4003
 
3767
4004
  const getIndex = l => {
3768
- return this.code.lines[ l ].substr( l == cursorData.y ? cursorData.x : 0 ).indexOf( text );
4005
+
4006
+ var string = this.code.lines[ l ];
4007
+
4008
+ if( reverse )
4009
+ {
4010
+ string = string.substr( 0, l == cursorData.y ? cursorData.x : string.length );
4011
+ var reversed = strReverse( string );
4012
+ var reversedIdx = reversed.indexOf( strReverse( text ) );
4013
+ return reversedIdx == -1 ? -1 : string.length - reversedIdx - text.length;
4014
+ }
4015
+ else
4016
+ {
4017
+ return string.substr( l == cursorData.y ? cursorData.x : 0 ).indexOf( text );
4018
+ }
3769
4019
  };
3770
4020
 
3771
4021
  if( reverse )
@@ -3795,7 +4045,15 @@ class CodeEditor {
3795
4045
 
3796
4046
  if( line == null)
3797
4047
  {
3798
- alert("No results!")
4048
+ if( !skipAlert )
4049
+ alert( "No results!" );
4050
+
4051
+ const lastLine = this.code.lines.length - 1;
4052
+
4053
+ this._lastResult = {
4054
+ 'dom': this.searchResultSelections.lastChild,
4055
+ 'pos': reverse ? new LX.vec2( this.code.lines[ lastLine ].length, lastLine ) : new LX.vec2( 0, 0 )
4056
+ };
3799
4057
  return;
3800
4058
  }
3801
4059
 
@@ -3805,32 +4063,44 @@ class CodeEditor {
3805
4063
  have to add the length of the substring (0, first_ocurrence)
3806
4064
  */
3807
4065
 
3808
- char += ( line == cursorData.y ? cursorData.x : 0 );
4066
+
4067
+ if( !reverse )
4068
+ char += ( line == cursorData.y ? cursorData.x : 0 );
4069
+
3809
4070
 
3810
4071
  // Text found..
3811
4072
 
3812
4073
  this._lastTextFound = text;
3813
4074
 
3814
4075
  this.codeScroller.scrollTo(
3815
- Math.max( char * this.charWidth - this.codeScroller.clientWidth ),
3816
- Math.max( line - 10 ) * this.lineHeight
4076
+ Math.max( char * this.charWidth - this.codeScroller.clientWidth, 0 ),
4077
+ Math.max( line - 10, 0 ) * this.lineHeight
3817
4078
  );
3818
4079
 
3819
- // Show elements
3820
- this.selections.classList.add( 'show' );
4080
+ if( callback )
4081
+ {
4082
+ callback( char, line );
4083
+ }
4084
+ else
4085
+ {
4086
+ // Show elements
4087
+ this.searchResultSelections.classList.add( 'show' );
4088
+
4089
+ // Create new selection instance
4090
+ cursor.selection = new CodeSelection( this, cursor, "lexcodesearchresult" );
4091
+ cursor.selection.selectInline( cursor, char, line, this.measureString( text ), true );
4092
+ }
3821
4093
 
3822
- // Create new selection instance
3823
- this.selection = new CodeSelection( this, 0, 0, "lexcodesearchresult" );
3824
- this.selection.selectInline( char, line, this.measureString( text ) );
3825
4094
  this._lastResult = {
3826
- 'dom': this.selections.lastChild,
3827
- 'pos': new LX.vec2( char + text.length, line )
4095
+ 'dom': this.searchResultSelections.lastChild,
4096
+ 'pos': new LX.vec2( char + text.length * ( reverse ? -1 : 1 ) , line )
3828
4097
  };
3829
-
3830
4098
  }
3831
4099
 
3832
4100
  showSearchLineBox() {
3833
4101
 
4102
+ this.hideSearchBox();
4103
+
3834
4104
  this.searchlinebox.classList.add( 'opened' );
3835
4105
  this.searchlineboxActive = true;
3836
4106
 
@@ -3860,6 +4130,40 @@ class CodeEditor {
3860
4130
  this.cursorToLine( cursor, line - 1, true );
3861
4131
  }
3862
4132
 
4133
+ selectNextOcurrence( cursor ) {
4134
+
4135
+ if( !cursor.selection )
4136
+ return;
4137
+
4138
+ const text = cursor.selection.getText();
4139
+ if( !text )
4140
+ return;
4141
+
4142
+ if( !this._currentOcurrences )
4143
+ {
4144
+ const currentKey = [ cursor.position - text.length, cursor.line ].join( '_' );
4145
+ this._currentOcurrences = { };
4146
+ this._currentOcurrences[ currentKey ] = true;
4147
+ }
4148
+
4149
+ this.search( text, false, (col, ln) => {
4150
+
4151
+ const key = [ col, ln ].join( '_' );
4152
+
4153
+ if( this._currentOcurrences[ key ] ) {
4154
+ return;
4155
+ }
4156
+
4157
+ var newCursor = this._addCursor( ln, col, true );
4158
+ this.startSelection( newCursor );
4159
+ newCursor.selection.selectInline( newCursor, col, ln, this.measureString( text ) );
4160
+ this.cursorToString( newCursor, text );
4161
+
4162
+ this._currentOcurrences[ key ] = true;
4163
+
4164
+ }, true );
4165
+ }
4166
+
3863
4167
  _updateDataInfoPanel( signal, value ) {
3864
4168
 
3865
4169
  if( !this.skipCodeInfo )
@@ -3962,13 +4266,14 @@ class CodeEditor {
3962
4266
  delete this._pendingString;
3963
4267
  delete this._buildingBlockComment;
3964
4268
  delete this._markdownHeader;
4269
+ delete this._lastResult;
3965
4270
  }
3966
4271
  }
3967
4272
 
3968
4273
  CodeEditor.keywords = {
3969
4274
 
3970
4275
  'JavaScript': ['var', 'let', 'const', 'this', 'in', 'of', 'true', 'false', 'new', 'function', 'NaN', 'static', 'class', 'constructor', 'null', 'typeof', 'debugger', 'abstract',
3971
- 'arguments', 'extends', 'instanceof'],
4276
+ 'arguments', 'extends', 'instanceof', 'Infinity'],
3972
4277
  'C++': ['int', 'float', 'double', 'bool', 'char', 'wchar_t', 'const', 'static_cast', 'dynamic_cast', 'new', 'delete', 'void', 'true', 'false', 'auto', 'struct', 'typedef', 'nullptr',
3973
4278
  'NULL', 'unsigned', 'namespace'],
3974
4279
  'JSON': ['true', 'false'],
@@ -4001,7 +4306,8 @@ CodeEditor.utils = { // These ones don't have hightlight, used as suggestions to
4001
4306
 
4002
4307
  CodeEditor.types = {
4003
4308
 
4004
- 'JavaScript': ['Object', 'String', 'Function', 'Boolean', 'Symbol', 'Error', 'Number', 'TextEncoder', 'TextDecoder'],
4309
+ 'JavaScript': ['Object', 'String', 'Function', 'Boolean', 'Symbol', 'Error', 'Number', 'TextEncoder', 'TextDecoder', 'Array', 'ArrayBuffer', 'InputEvent', 'MouseEvent',
4310
+ 'Int8Array', 'Int16Array', 'Int32Array', 'Float32Array', 'Float64Array', 'Element'],
4005
4311
  'Rust': ['u128'],
4006
4312
  'Python': ['int', 'type', 'float', 'map', 'list', 'ArithmeticError', 'AssertionError', 'AttributeError', 'Exception', 'EOFError', 'FloatingPointError', 'GeneratorExit',
4007
4313
  'ImportError', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'NotImplementedError', 'OSError',