lexgui 0.1.25 → 0.1.26

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,9 +365,13 @@ 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";
@@ -566,8 +610,12 @@ class CodeEditor {
566
610
  // Action keys
567
611
 
568
612
  this.action( 'Escape', false, ( ln, cursor, e ) => {
569
- this.hideAutoCompleteBox();
570
- this.hideSearchBox();
613
+ if( this.hideAutoCompleteBox() )
614
+ return;
615
+ if( this.hideSearchBox() )
616
+ return;
617
+ // Remove selections and cursors
618
+ this.endSelection();
571
619
  this._removeSecondaryCursors();
572
620
  });
573
621
 
@@ -575,7 +623,7 @@ class CodeEditor {
575
623
 
576
624
  this._addUndoStep( cursor );
577
625
 
578
- if( this.selection ) {
626
+ if( cursor.selection ) {
579
627
  this.deleteSelection( cursor );
580
628
  // Remove entire line when selecting with triple click
581
629
  if( this._tripleClickSelection )
@@ -629,7 +677,7 @@ class CodeEditor {
629
677
 
630
678
  this._addUndoStep( cursor );
631
679
 
632
- if(this.selection) {
680
+ if( cursor.selection ) {
633
681
  // Use 'Backspace' as it's the same callback...
634
682
  this.actions['Backspace'].callback( ln, cursor, e );
635
683
  }
@@ -640,7 +688,7 @@ class CodeEditor {
640
688
  this.code.lines[ ln ] = sliceChars( this.code.lines[ ln ], cursor.position );
641
689
  this.processLine( ln );
642
690
  }
643
- else if(this.code.lines[ ln + 1 ] != undefined) {
691
+ else if( this.code.lines[ ln + 1 ] != undefined ) {
644
692
  this.code.lines[ ln ] += this.code.lines[ ln + 1 ];
645
693
  this.code.lines.splice( ln + 1, 1 );
646
694
  this.processLines();
@@ -679,18 +727,18 @@ class CodeEditor {
679
727
  if( e.shiftKey && !e.cancelShift )
680
728
  {
681
729
  // Get last selection range
682
- if( this.selection )
683
- lastX += this.selection.chars;
730
+ if( cursor.selection )
731
+ lastX += cursor.selection.chars;
684
732
 
685
- if( !this.selection )
733
+ if( !cursor.selection )
686
734
  this.startSelection( cursor );
687
735
 
688
736
  var string = this.code.lines[ ln ].substring( idx, lastX );
689
- if( this.selection.sameLine() )
690
- this.selection.selectInline( idx, cursor.line, this.measureString( string ) );
737
+ if( cursor.selection.sameLine() )
738
+ cursor.selection.selectInline( cursor, idx, cursor.line, this.measureString( string ) );
691
739
  else
692
740
  {
693
- this.processSelection( e );
741
+ this._processSelection( cursor, e );
694
742
  }
695
743
  } else if( !e.keepSelection )
696
744
  this.endSelection();
@@ -701,15 +749,15 @@ class CodeEditor {
701
749
  if( ( e.shiftKey || e._shiftKey ) && !e.cancelShift ) {
702
750
 
703
751
  var string = this.code.lines[ ln ].substring( cursor.position );
704
- if( !this.selection )
752
+ if( !cursor.selection )
705
753
  this.startSelection( cursor );
706
- if( this.selection.sameLine() )
707
- this.selection.selectInline(cursor.position, cursor.line, this.measureString( string ));
754
+ if( cursor.selection.sameLine() )
755
+ cursor.selection.selectInline( cursor, cursor.position, cursor.line, this.measureString( string ));
708
756
  else
709
757
  {
710
758
  this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
711
759
  this.cursorToString( cursor, this.code.lines[ ln ] );
712
- this.processSelection( e );
760
+ this._processSelection( cursor, e );
713
761
  }
714
762
  } else if( !e.keepSelection )
715
763
  this.endSelection();
@@ -771,7 +819,7 @@ class CodeEditor {
771
819
  if( !this.isAutoCompleteActive )
772
820
  {
773
821
  if( e.shiftKey ) {
774
- if( !this.selection )
822
+ if( !cursor.selection )
775
823
  this.startSelection( cursor );
776
824
 
777
825
  this.lineUp( cursor );
@@ -781,7 +829,7 @@ class CodeEditor {
781
829
  this.cursorToPosition( cursor, this.code.lines[ cursor.line ].length );
782
830
  }
783
831
 
784
- this.processSelection( e, false );
832
+ this._processSelection( cursor, e, false );
785
833
 
786
834
  } else {
787
835
  this.endSelection();
@@ -804,7 +852,7 @@ class CodeEditor {
804
852
  if( !this.isAutoCompleteActive )
805
853
  {
806
854
  if( e.shiftKey ) {
807
- if( !this.selection )
855
+ if( !cursor.selection )
808
856
  this.startSelection( cursor );
809
857
  } else {
810
858
  this.endSelection();
@@ -819,7 +867,7 @@ class CodeEditor {
819
867
  }
820
868
 
821
869
  if( e.shiftKey ) {
822
- this.processSelection( e );
870
+ this._processSelection( cursor, e );
823
871
  }
824
872
  }
825
873
  // Move down autocomplete selection
@@ -853,37 +901,37 @@ class CodeEditor {
853
901
  var diff = Math.max( cursor.position - from, 1 );
854
902
  var substr = word.substr( 0, diff );
855
903
  // Selections...
856
- if( e.shiftKey ) { if( !this.selection ) this.startSelection( cursor ); }
904
+ if( e.shiftKey ) { if( !cursor.selection ) this.startSelection( cursor ); }
857
905
  else this.endSelection();
858
906
  this.cursorToString( cursor, substr, true );
859
- if( e.shiftKey ) this.processSelection( e, false, true );
907
+ if( e.shiftKey ) this._processSelection( cursor, e, false, true );
860
908
  }
861
909
  else {
862
910
  var letter = this.getCharAtPos( cursor, -1 );
863
911
  if( letter ) {
864
912
  if( e.shiftKey ) {
865
- if( !this.selection ) this.startSelection( cursor );
913
+ if( !cursor.selection ) this.startSelection( cursor );
866
914
  this.cursorToLeft( letter, cursor );
867
- this.processSelection( e, false, CodeEditor.SELECTION_X );
915
+ this._processSelection( cursor, e, false, CodeEditor.SELECTION_X );
868
916
  }
869
917
  else {
870
- if( !this.selection ) {
918
+ if( !cursor.selection ) {
871
919
  this.cursorToLeft( letter, cursor );
872
920
  if( this.useAutoComplete && this.isAutoCompleteActive )
873
921
  this.showAutoCompleteBox( 'foo', cursor );
874
922
  }
875
923
  else {
876
- this.selection.invertIfNecessary();
924
+ cursor.selection.invertIfNecessary();
877
925
  this.resetCursorPos( CodeEditor.CURSOR_LEFT | CodeEditor.CURSOR_TOP, cursor );
878
- this.cursorToLine( cursor, this.selection.fromY, true );
879
- this.cursorToPosition( cursor, this.selection.fromX );
926
+ this.cursorToLine( cursor, cursor.selection.fromY, true );
927
+ this.cursorToPosition( cursor, cursor.selection.fromX );
880
928
  this.endSelection();
881
929
  }
882
930
  }
883
931
  }
884
932
  else if( cursor.line > 0 ) {
885
933
 
886
- if( e.shiftKey && !this.selection ) this.startSelection( cursor );
934
+ if( e.shiftKey && !cursor.selection ) this.startSelection( cursor );
887
935
 
888
936
  this.lineUp( cursor );
889
937
 
@@ -891,7 +939,7 @@ class CodeEditor {
891
939
  this.actions[ 'End' ].callback( cursor.line, cursor, e );
892
940
  delete e.cancelShift; delete e.keepSelection;
893
941
 
894
- if( e.shiftKey ) this.processSelection( e, false );
942
+ if( e.shiftKey ) this._processSelection( cursor, e, false );
895
943
  }
896
944
  }
897
945
  });
@@ -903,40 +951,61 @@ class CodeEditor {
903
951
  cursor.position == this.code.lines[ cursor.line ].length )
904
952
  return;
905
953
 
906
- if( e.metaKey ) { // Apple devices (Command)
954
+ if( e.metaKey ) // Apple devices (Command)
955
+ {
907
956
  e.preventDefault();
908
957
  this.actions[ 'End' ].callback( ln, cursor );
909
- } else if( e.ctrlKey ) {
958
+ }
959
+ else if( e.ctrlKey ) // Next word
960
+ {
910
961
  // Get next word
911
962
  const [ word, from, to ] = this.getWordAtPos( cursor );
963
+
912
964
  // If no length, we change line..
913
965
  if( !word.length ) this.lineDown( cursor, true );
914
966
  var diff = cursor.position - from;
915
967
  var substr = word.substr( diff );
968
+
916
969
  // Selections...
917
- if( e.shiftKey ) { if( !this.selection ) this.startSelection( cursor ); }
918
- else this.endSelection();
970
+ if( e.shiftKey ) {
971
+ if( !cursor.selection )
972
+ this.startSelection( cursor );
973
+ }
974
+ else
975
+ this.endSelection();
976
+
919
977
  this.cursorToString( cursor, substr );
920
- if( e.shiftKey ) this.processSelection( e );
921
- } else {
978
+
979
+ if( e.shiftKey )
980
+ this._processSelection( cursor, e );
981
+ }
982
+ else // Next char
983
+ {
922
984
  var letter = this.getCharAtPos( cursor );
923
985
  if( letter ) {
924
- if( e.shiftKey ) {
925
- if( !this.selection ) this.startSelection( cursor );
986
+
987
+ // Selecting chars
988
+ if( e.shiftKey )
989
+ {
990
+ if( !cursor.selection )
991
+ this.startSelection( cursor );
992
+
926
993
  this.cursorToRight( letter, cursor );
927
- this.processSelection( e, false, CodeEditor.SELECTION_X );
928
- }else{
929
- if( !this.selection ) {
994
+ this._processSelection( cursor, e, false, CodeEditor.SELECTION_X );
995
+ }
996
+ else
997
+ {
998
+ if( !cursor.selection ) {
930
999
  this.cursorToRight( letter, cursor );
931
1000
  if( this.useAutoComplete && this.isAutoCompleteActive )
932
1001
  this.showAutoCompleteBox( 'foo', cursor );
933
1002
  }
934
- else
1003
+ else
935
1004
  {
936
- this.selection.invertIfNecessary();
1005
+ cursor.selection.invertIfNecessary();
937
1006
  this.resetCursorPos( CodeEditor.CURSOR_LEFT | CodeEditor.CURSOR_TOP, cursor );
938
- this.cursorToLine( cursor, this.selection.toY );
939
- this.cursorToPosition( cursor, this.selection.toX );
1007
+ this.cursorToLine( cursor, cursor.selection.toY );
1008
+ this.cursorToPosition( cursor, cursor.selection.toX );
940
1009
  this.endSelection();
941
1010
  }
942
1011
  }
@@ -944,12 +1013,13 @@ class CodeEditor {
944
1013
  else if( this.code.lines[ cursor.line + 1 ] !== undefined ) {
945
1014
 
946
1015
  if( e.shiftKey ) {
947
- if( !this.selection ) this.startSelection( cursor );
1016
+ if( !cursor.selection ) this.startSelection( cursor );
948
1017
  }
1018
+ else this.endSelection();
949
1019
 
950
1020
  this.lineDown( cursor, true );
951
1021
 
952
- if( e.shiftKey ) this.processSelection( e, false );
1022
+ if( e.shiftKey ) this._processSelection( cursor, e, false );
953
1023
 
954
1024
  this.hideAutoCompleteBox();
955
1025
  }
@@ -1049,14 +1119,14 @@ class CodeEditor {
1049
1119
 
1050
1120
  let lidx = cursor.line;
1051
1121
 
1052
- if( this.selection ) {
1122
+ if( cursor.selection ) {
1053
1123
  this.deleteSelection( cursor );
1054
1124
  lidx = cursor.line;
1055
1125
  }
1056
1126
 
1057
1127
  this.endSelection();
1058
1128
 
1059
- const new_lines = text.split( '\n' );
1129
+ const new_lines = text.replaceAll( '\r', '' ).split( '\n' );
1060
1130
 
1061
1131
  // Pasting Multiline...
1062
1132
  if( new_lines.length != 1 )
@@ -1111,10 +1181,16 @@ class CodeEditor {
1111
1181
 
1112
1182
  const inner_add_tab = ( text, name, title ) => {
1113
1183
 
1184
+ // Remove Carriage Return in some cases and sub tabs using spaces
1185
+ text = text.replaceAll( '\r', '' );
1186
+ text = text.replaceAll( /\t|\\t/g, ' '.repeat( this.tabSpaces ) );
1187
+
1114
1188
  // Set current text and language
1115
- const lines = text.replaceAll( '\r', '' ).split( '\n' );
1189
+
1190
+ const lines = text.split( '\n' );
1116
1191
 
1117
1192
  // Add item in the explorer if used
1193
+
1118
1194
  if( this.explorer )
1119
1195
  {
1120
1196
  this._storedLines = this._storedLines ?? {};
@@ -1163,6 +1239,7 @@ class CodeEditor {
1163
1239
  }
1164
1240
 
1165
1241
  let cursor = document.createElement( 'div' );
1242
+ cursor.name = "cursor" + this.cursors.childElementCount;
1166
1243
  cursor.className = "cursor";
1167
1244
  cursor.innerHTML = "&nbsp;";
1168
1245
  cursor.isMain = isMain;
@@ -1639,12 +1716,12 @@ class CodeEditor {
1639
1716
  {
1640
1717
  this.processClick( e );
1641
1718
 
1642
- this.canOpenContextMenu = !this.selection;
1719
+ this.canOpenContextMenu = !cursor.selection;
1643
1720
 
1644
- if( this.selection )
1721
+ if( cursor.selection )
1645
1722
  {
1646
- this.canOpenContextMenu |= (cursor.line >= this.selection.fromY && cursor.line <= this.selection.toY
1647
- && cursor.position >= this.selection.fromX && cursor.position <= this.selection.toX);
1723
+ this.canOpenContextMenu |= (cursor.line >= cursor.selection.fromY && cursor.line <= cursor.selection.toY
1724
+ && cursor.position >= cursor.selection.fromX && cursor.position <= cursor.selection.toX);
1648
1725
  if( this.canOpenContextMenu )
1649
1726
  return;
1650
1727
  }
@@ -1664,7 +1741,7 @@ class CodeEditor {
1664
1741
  else if( e.type == 'mousemove' )
1665
1742
  {
1666
1743
  if( this.state.selectingText )
1667
- this.processSelection( e );
1744
+ this.processSelections( e );
1668
1745
  }
1669
1746
 
1670
1747
  else if ( e.type == 'click' ) // trip
@@ -1676,7 +1753,7 @@ class CodeEditor {
1676
1753
  this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
1677
1754
  this.cursorToPosition( cursor, from );
1678
1755
  this.startSelection( cursor );
1679
- this.selection.selectInline( from, cursor.line, this.measureString( word ) );
1756
+ cursor.selection.selectInline( cursor, from, cursor.line, this.measureString( word ) );
1680
1757
  this.cursorToString( cursor, word ); // Go to the end of the word
1681
1758
  break;
1682
1759
  // Select entire line
@@ -1705,6 +1782,8 @@ class CodeEditor {
1705
1782
  m.add( "" );
1706
1783
  m.add( "Format/JSON", () => {
1707
1784
  let json = this.toJSONFormat( this.getText() );
1785
+ if( !json )
1786
+ return;
1708
1787
  this.code.lines = json.split( "\n" );
1709
1788
  this.processLines();
1710
1789
  } );
@@ -1722,10 +1801,9 @@ class CodeEditor {
1722
1801
  this.endSelection();
1723
1802
  }
1724
1803
 
1725
- if( this.selection )
1726
- {
1727
- this.selection.invertIfNecessary();
1728
- }
1804
+ const cursor = this._getCurrentCursor();
1805
+ if( cursor.selection )
1806
+ cursor.selection.invertIfNecessary();
1729
1807
 
1730
1808
  this.state.selectingText = false;
1731
1809
  delete this._lastSelectionKeyDir;
@@ -1764,13 +1842,32 @@ class CodeEditor {
1764
1842
  this.hideAutoCompleteBox();
1765
1843
  }
1766
1844
 
1767
- processSelection( e, keep_range, flags = CodeEditor.SELECTION_X_Y ) {
1845
+ updateSelections( e, keep_range, flags = CodeEditor.SELECTION_X_Y ) {
1846
+
1847
+ for( let cursor of this.cursors.children )
1848
+ {
1849
+ if( !cursor.selection )
1850
+ continue;
1851
+
1852
+ this._processSelection( cursor, e, keep_range, flags );
1853
+ }
1854
+ }
1855
+
1856
+ processSelections( e, keep_range, flags = CodeEditor.SELECTION_X_Y ) {
1857
+
1858
+ for( let cursor of this.cursors.children )
1859
+ {
1860
+ this._processSelection( cursor, e, keep_range, flags );
1861
+ }
1862
+ }
1863
+
1864
+ _processSelection( cursor, e, keep_range, flags = CodeEditor.SELECTION_X_Y ) {
1768
1865
 
1769
- var cursor = this._getCurrentCursor();
1770
1866
  const isMouseEvent = e && ( e.constructor == MouseEvent );
1771
1867
 
1772
1868
  if( isMouseEvent ) this.processClick( e );
1773
- if( !this.selection )
1869
+
1870
+ if( !cursor.selection )
1774
1871
  this.startSelection( cursor );
1775
1872
 
1776
1873
  this._hideActiveLine();
@@ -1781,46 +1878,48 @@ class CodeEditor {
1781
1878
  let ccw = true;
1782
1879
 
1783
1880
  // 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) )
1881
+ if( !isMouseEvent && this.line >= cursor.selection.fromY &&
1882
+ (this.line == cursor.selection.fromY ? this.position >= cursor.selection.fromX : true) )
1786
1883
  {
1787
1884
  ccw = ( e && this._lastSelectionKeyDir && ( e.key == 'ArrowRight' || e.key == 'ArrowDown' || e.key == 'End' ) );
1788
1885
  }
1789
1886
 
1790
1887
  if( ccw )
1791
1888
  {
1792
- if( flags & CodeEditor.SELECTION_X ) this.selection.fromX = cursor.position;
1793
- if( flags & CodeEditor.SELECTION_Y ) this.selection.fromY = cursor.line;
1889
+ if( flags & CodeEditor.SELECTION_X ) cursor.selection.fromX = cursor.position;
1890
+ if( flags & CodeEditor.SELECTION_Y ) cursor.selection.fromY = cursor.line;
1794
1891
  }
1795
1892
  else
1796
1893
  {
1797
- if( flags & CodeEditor.SELECTION_X ) this.selection.toX = cursor.position;
1798
- if( flags & CodeEditor.SELECTION_Y ) this.selection.toY = cursor.line;
1894
+ if( flags & CodeEditor.SELECTION_X ) cursor.selection.toX = cursor.position;
1895
+ if( flags & CodeEditor.SELECTION_Y ) cursor.selection.toY = cursor.line;
1799
1896
  }
1800
1897
 
1801
1898
  this._lastSelectionKeyDir = ccw;
1802
1899
  }
1803
1900
 
1804
1901
  // Only leave if not a mouse selection...
1805
- if( !isMouseEvent && this.selection.isEmpty() )
1902
+ if( !isMouseEvent && cursor.selection.isEmpty() )
1806
1903
  {
1807
1904
  this.endSelection();
1808
1905
  return;
1809
1906
  }
1810
1907
 
1811
- this.selection.chars = 0;
1908
+ cursor.selection.chars = 0;
1812
1909
 
1813
- const fromX = this.selection.fromX,
1814
- fromY = this.selection.fromY,
1815
- toX = this.selection.toX,
1816
- toY = this.selection.toY;
1910
+ const fromX = cursor.selection.fromX,
1911
+ fromY = cursor.selection.fromY,
1912
+ toX = cursor.selection.toX,
1913
+ toY = cursor.selection.toY;
1817
1914
  const deltaY = toY - fromY;
1818
1915
 
1916
+ let cursorSelections = this.selections[ cursor.name ];
1917
+
1819
1918
  // Selection goes down...
1820
1919
  if( deltaY >= 0 )
1821
1920
  {
1822
- while( deltaY < ( this.selections.childElementCount - 1 ) )
1823
- deleteElement( this.selections.lastChild );
1921
+ while( deltaY < ( cursorSelections.childElementCount - 1 ) )
1922
+ deleteElement( cursorSelections.lastChild );
1824
1923
 
1825
1924
  for(let i = fromY; i <= toY; i++){
1826
1925
 
@@ -1831,12 +1930,12 @@ class CodeEditor {
1831
1930
  if( isVisible )
1832
1931
  {
1833
1932
  // Make sure that the line selection is generated...
1834
- domEl = this.selections.childNodes[ sId ];
1933
+ domEl = cursorSelections.childNodes[ sId ];
1835
1934
  if(!domEl)
1836
1935
  {
1837
1936
  domEl = document.createElement( 'div' );
1838
1937
  domEl.className = "lexcodeselection";
1839
- this.selections.appendChild( domEl );
1938
+ cursorSelections.appendChild( domEl );
1840
1939
  }
1841
1940
  }
1842
1941
 
@@ -1858,7 +1957,7 @@ class CodeEditor {
1858
1957
  }
1859
1958
 
1860
1959
  const stringWidth = this.measureString( string );
1861
- this.selection.chars += stringWidth / this.charWidth;
1960
+ cursor.selection.chars += stringWidth / this.charWidth;
1862
1961
 
1863
1962
  if( isVisible )
1864
1963
  {
@@ -1870,8 +1969,8 @@ class CodeEditor {
1870
1969
  }
1871
1970
  else // Selection goes up...
1872
1971
  {
1873
- while( Math.abs( deltaY ) < ( this.selections.childElementCount - 1 ) )
1874
- deleteElement( this.selections.firstChild );
1972
+ while( Math.abs( deltaY ) < ( cursorSelections.childElementCount - 1 ) )
1973
+ deleteElement( cursorSelections.firstChild );
1875
1974
 
1876
1975
  for( let i = toY; i <= fromY; i++ ){
1877
1976
 
@@ -1882,12 +1981,12 @@ class CodeEditor {
1882
1981
  if( isVisible )
1883
1982
  {
1884
1983
  // Make sure that the line selection is generated...
1885
- domEl = this.selections.childNodes[ sId ];
1984
+ domEl = cursorSelections.childNodes[ sId ];
1886
1985
  if(!domEl)
1887
1986
  {
1888
1987
  domEl = document.createElement( 'div' );
1889
1988
  domEl.className = "lexcodeselection";
1890
- this.selections.appendChild( domEl );
1989
+ cursorSelections.appendChild( domEl );
1891
1990
  }
1892
1991
  }
1893
1992
 
@@ -1907,7 +2006,7 @@ class CodeEditor {
1907
2006
  }
1908
2007
 
1909
2008
  const stringWidth = this.measureString( string );
1910
- this.selection.chars += stringWidth / this.charWidth;
2009
+ cursor.selection.chars += stringWidth / this.charWidth;
1911
2010
 
1912
2011
  if( isVisible )
1913
2012
  {
@@ -1998,46 +2097,59 @@ class CodeEditor {
1998
2097
 
1999
2098
  if( e.ctrlKey || e.metaKey )
2000
2099
  {
2001
- e.preventDefault();
2002
-
2003
2100
  switch( key.toLowerCase() ) {
2004
2101
  case 'a': // select all
2102
+ e.preventDefault();
2005
2103
  this.selectAll();
2006
2104
  return true;
2007
2105
  case 'c': // k+c, comment line
2106
+ e.preventDefault();
2008
2107
  if( this.state.keyChain == 'k' ) {
2009
2108
  this._commentLines();
2010
2109
  return true;
2011
2110
  }
2012
2111
  return false;
2112
+ case 'd': // next ocurrence
2113
+ e.preventDefault();
2114
+ this.selectNextOcurrence( cursor );
2115
+ return true;
2013
2116
  case 'f': // find/search
2117
+ e.preventDefault();
2014
2118
  this.showSearchBox();
2015
2119
  return true;
2016
2120
  case 'g': // find line
2121
+ e.preventDefault();
2017
2122
  this.showSearchLineBox();
2018
2123
  return true;
2019
2124
  case 'k': // shortcut chain
2125
+ e.preventDefault();
2020
2126
  this.state.keyChain = 'k';
2021
2127
  return true;
2022
2128
  case 's': // save
2129
+ e.preventDefault();
2023
2130
  this.onsave( this.getText() );
2024
2131
  return true;
2025
2132
  case 'u': // k+u, uncomment line
2133
+ e.preventDefault();
2026
2134
  if( this.state.keyChain == 'k' ) {
2027
2135
  this._uncommentLines();
2028
2136
  return true;
2029
2137
  }
2030
2138
  return false;
2031
2139
  case 'y': // redo
2140
+ e.preventDefault();
2032
2141
  this._doRedo( cursor );
2033
2142
  return true;
2034
2143
  case 'z': // undo
2144
+ e.preventDefault();
2035
2145
  this._doUndo( cursor );
2036
2146
  return true;
2037
2147
  case '+': // increase size
2148
+ e.preventDefault();
2038
2149
  this._increaseFontSize();
2039
2150
  return true;
2040
2151
  case '-': // decrease size
2152
+ e.preventDefault();
2041
2153
  this._decreaseFontSize();
2042
2154
  return true;
2043
2155
  }
@@ -2066,14 +2178,8 @@ class CodeEditor {
2066
2178
  {
2067
2179
  switch( key.toLowerCase() ) {
2068
2180
  case 'c': // copy
2069
- // TODO: COPY TEXT FROM EVERY CURSOR
2070
2181
  this._copyContent( cursor );
2071
2182
  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
2183
  case 'v': // paste
2078
2184
  this._pasteContent( cursor );
2079
2185
  return;
@@ -2094,6 +2200,10 @@ class CodeEditor {
2094
2200
  else if( e.altKey )
2095
2201
  {
2096
2202
  switch( key ) {
2203
+ case 'd': // duplicate line
2204
+ e.preventDefault();
2205
+ this._duplicateLine( lidx, cursor );
2206
+ return;
2097
2207
  case 'ArrowUp':
2098
2208
  if(this.code.lines[ lidx - 1 ] == undefined)
2099
2209
  return;
@@ -2124,7 +2234,7 @@ class CodeEditor {
2124
2234
  if( key != actKey ) continue;
2125
2235
  e.preventDefault();
2126
2236
 
2127
- if( this.actions[ key ].deleteSelection && this.selection )
2237
+ if( this.actions[ key ].deleteSelection && cursor.selection )
2128
2238
  this.actions['Backspace'].callback( lidx, cursor, e );
2129
2239
 
2130
2240
  return this.actions[ key ].callback( lidx, cursor, e );
@@ -2153,7 +2263,7 @@ class CodeEditor {
2153
2263
  // Until this point, if there was a selection, we need
2154
2264
  // to delete the content..
2155
2265
 
2156
- if( this.selection )
2266
+ if( cursor.selection )
2157
2267
  {
2158
2268
  this.actions['Backspace'].callback( lidx, cursor, e );
2159
2269
  lidx = cursor.line;
@@ -2214,22 +2324,32 @@ class CodeEditor {
2214
2324
 
2215
2325
  let text = await navigator.clipboard.readText();
2216
2326
 
2327
+ // Remove any possible tabs (\t) and add spaces
2328
+ text = text.replaceAll( /\t|\\t/g, ' '.repeat( this.tabSpaces ) );
2329
+
2217
2330
  this._addUndoStep( cursor, true );
2218
2331
 
2219
2332
  this.appendText( text, cursor );
2333
+
2334
+ const currentScroll = this.getScrollTop();
2335
+ const scroll = Math.max( cursor.line * this.lineHeight - this.codeScroller.offsetWidth, 0 );
2336
+
2337
+ if( currentScroll < scroll ) {
2338
+ this.codeScroller.scrollTo( 0, cursor.line * this.lineHeight );
2339
+ }
2220
2340
  }
2221
2341
 
2222
2342
  async _copyContent( cursor ) {
2223
2343
 
2224
2344
  let text_to_copy = "";
2225
2345
 
2226
- if( !this.selection ) {
2346
+ if( !cursor.selection ) {
2227
2347
  text_to_copy = "\n" + this.code.lines[ cursor.line ];
2228
2348
  }
2229
2349
  else {
2230
2350
 
2231
2351
  // Some selections don't depend on mouse up..
2232
- if( this.selection ) this.selection.invertIfNecessary();
2352
+ if( cursor.selection ) cursor.selection.invertIfNecessary();
2233
2353
 
2234
2354
  const separator = "_NEWLINE_";
2235
2355
  let code = this.code.lines.join( separator );
@@ -2237,11 +2357,11 @@ class CodeEditor {
2237
2357
  // Get linear start index
2238
2358
  let index = 0;
2239
2359
 
2240
- for( let i = 0; i <= this.selection.fromY; i++ )
2241
- index += ( i == this.selection.fromY ? this.selection.fromX : this.code.lines[ i ].length );
2360
+ for( let i = 0; i <= cursor.selection.fromY; i++ )
2361
+ index += ( i == cursor.selection.fromY ? cursor.selection.fromX : this.code.lines[ i ].length );
2242
2362
 
2243
- index += this.selection.fromY * separator.length;
2244
- const num_chars = this.selection.chars + ( this.selection.toY - this.selection.fromY ) * separator.length;
2363
+ index += cursor.selection.fromY * separator.length;
2364
+ const num_chars = cursor.selection.chars + ( cursor.selection.toY - cursor.selection.fromY ) * separator.length;
2245
2365
  const text = code.substr( index, num_chars );
2246
2366
  const lines = text.split( separator );
2247
2367
  text_to_copy = lines.join('\n');
@@ -2257,7 +2377,7 @@ class CodeEditor {
2257
2377
 
2258
2378
  this._addUndoStep( cursor, true );
2259
2379
 
2260
- if( !this.selection ) {
2380
+ if( !cursor.selection ) {
2261
2381
  text_to_cut = "\n" + this.code.lines[ cursor.line ];
2262
2382
  this.code.lines.splice( lidx, 1 );
2263
2383
  this.processLines();
@@ -2268,7 +2388,7 @@ class CodeEditor {
2268
2388
  else {
2269
2389
 
2270
2390
  // Some selections don't depend on mouse up..
2271
- if( this.selection ) this.selection.invertIfNecessary();
2391
+ if( cursor.selection ) cursor.selection.invertIfNecessary();
2272
2392
 
2273
2393
  const separator = "_NEWLINE_";
2274
2394
  let code = this.code.lines.join(separator);
@@ -2276,11 +2396,11 @@ class CodeEditor {
2276
2396
  // Get linear start index
2277
2397
  let index = 0;
2278
2398
 
2279
- for(let i = 0; i <= this.selection.fromY; i++)
2280
- index += (i == this.selection.fromY ? this.selection.fromX : this.code.lines[ i ].length);
2399
+ for(let i = 0; i <= cursor.selection.fromY; i++)
2400
+ index += (i == cursor.selection.fromY ? cursor.selection.fromX : this.code.lines[ i ].length);
2281
2401
 
2282
- index += this.selection.fromY * separator.length;
2283
- const num_chars = this.selection.chars + (this.selection.toY - this.selection.fromY) * separator.length;
2402
+ index += cursor.selection.fromY * separator.length;
2403
+ const num_chars = cursor.selection.chars + (cursor.selection.toY - cursor.selection.fromY) * separator.length;
2284
2404
  const text = code.substr(index, num_chars);
2285
2405
  const lines = text.split(separator);
2286
2406
  text_to_cut = lines.join('\n');
@@ -2305,18 +2425,18 @@ class CodeEditor {
2305
2425
 
2306
2426
  this.state.keyChain = null;
2307
2427
 
2308
- if( this.selection )
2428
+ if( cursor.selection )
2309
2429
  {
2310
2430
  var cursor = this._getCurrentCursor();
2311
2431
  this._addUndoStep( cursor, true );
2312
2432
 
2313
- const selectedLines = this.code.lines.slice( this.selection.fromY, this.selection.toY );
2433
+ const selectedLines = this.code.lines.slice( cursor.selection.fromY, cursor.selection.toY );
2314
2434
  const minIdx = Math.min(...selectedLines.map( v => {
2315
2435
  var idx = firstNonspaceIndex( v );
2316
2436
  return idx < 0 ? 1e10 : idx;
2317
2437
  } ));
2318
2438
 
2319
- for( var i = this.selection.fromY; i <= this.selection.toY; ++i )
2439
+ for( var i = cursor.selection.fromY; i <= cursor.selection.toY; ++i )
2320
2440
  {
2321
2441
  this._commentLine( cursor, i, minIdx );
2322
2442
  }
@@ -2364,12 +2484,12 @@ class CodeEditor {
2364
2484
 
2365
2485
  this.state.keyChain = null;
2366
2486
 
2367
- if( this.selection )
2487
+ if( cursor.selection )
2368
2488
  {
2369
2489
  var cursor = this._getCurrentCursor();
2370
2490
  this._addUndoStep( cursor, true );
2371
2491
 
2372
- for( var i = this.selection.fromY; i <= this.selection.toY; ++i )
2492
+ for( var i = cursor.selection.fromY; i <= cursor.selection.toY; ++i )
2373
2493
  {
2374
2494
  this._uncommentLine( cursor, i );
2375
2495
  }
@@ -2478,8 +2598,7 @@ class CodeEditor {
2478
2598
  this.code.style.top = ( this.visibleLinesViewport.x * this.lineHeight ) + "px";
2479
2599
 
2480
2600
  // Update selections
2481
- if( this.selection )
2482
- this.processSelection( null, true );
2601
+ this.updateSelections( null, true );
2483
2602
 
2484
2603
  this._clearTmpVariables();
2485
2604
  this._setActiveLine();
@@ -2952,20 +3071,20 @@ class CodeEditor {
2952
3071
 
2953
3072
  _encloseSelectedWordWithKey( key, lidx, cursor ) {
2954
3073
 
2955
- if( !this.selection || (this.selection.fromY != this.selection.toY) )
3074
+ if( !cursor.selection || (cursor.selection.fromY != cursor.selection.toY) )
2956
3075
  return false;
2957
3076
 
2958
- this.selection.invertIfNecessary();
3077
+ cursor.selection.invertIfNecessary();
2959
3078
 
2960
3079
  // Insert first..
2961
3080
  this.code.lines[ lidx ] = [
2962
- this.code.lines[ lidx ].slice(0, this.selection.fromX),
3081
+ this.code.lines[ lidx ].slice(0, cursor.selection.fromX),
2963
3082
  key,
2964
- this.code.lines[ lidx ].slice(this.selection.fromX)
3083
+ this.code.lines[ lidx ].slice(cursor.selection.fromX)
2965
3084
  ].join('');
2966
3085
 
2967
3086
  // Go to the end of the word
2968
- this.cursorToPosition(cursor, this.selection.toX + 1);
3087
+ this.cursorToPosition(cursor, cursor.selection.toX + 1);
2969
3088
 
2970
3089
  // Change next key?
2971
3090
  switch(key)
@@ -2986,10 +3105,10 @@ class CodeEditor {
2986
3105
 
2987
3106
  // Recompute and reposition current selection
2988
3107
 
2989
- this.selection.fromX++;
2990
- this.selection.toX++;
3108
+ cursor.selection.fromX++;
3109
+ cursor.selection.toX++;
2991
3110
 
2992
- this.processSelection();
3111
+ this._processSelection( cursor );
2993
3112
  this.processLine( lidx );
2994
3113
 
2995
3114
  // Stop propagation
@@ -3037,14 +3156,16 @@ class CodeEditor {
3037
3156
 
3038
3157
  startSelection( cursor ) {
3039
3158
 
3040
- // Clear other selections...
3041
- this.selections.innerHTML = "";
3042
-
3043
3159
  // Show elements
3044
- this.selections.classList.add( 'show' );
3160
+ let selectionContainer = document.createElement( 'div' );
3161
+ selectionContainer.className = 'selections';
3162
+ selectionContainer.classList.add( 'show' );
3163
+
3164
+ this.codeSizer.insertChildAtIndex( selectionContainer, 2 );
3165
+ this.selections[ cursor.name ] = selectionContainer;
3045
3166
 
3046
3167
  // Create new selection instance
3047
- this.selection = new CodeSelection( this, cursor.position, cursor.line );
3168
+ cursor.selection = new CodeSelection( this, cursor );
3048
3169
  }
3049
3170
 
3050
3171
  deleteSelection( cursor ) {
@@ -3054,37 +3175,51 @@ class CodeEditor {
3054
3175
  return;
3055
3176
 
3056
3177
  // Some selections don't depend on mouse up..
3057
- if( this.selection ) this.selection.invertIfNecessary();
3178
+ if( cursor.selection ) cursor.selection.invertIfNecessary();
3058
3179
 
3059
3180
  const separator = "_NEWLINE_";
3060
3181
  let code = this.code.lines.join( separator );
3061
3182
 
3062
3183
  // Get linear start index
3063
3184
  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);
3185
+ for( let i = 0; i <= cursor.selection.fromY; i++ )
3186
+ index += (i == cursor.selection.fromY ? cursor.selection.fromX : this.code.lines[ i ].length);
3066
3187
 
3067
- index += this.selection.fromY * separator.length;
3188
+ index += cursor.selection.fromY * separator.length;
3068
3189
 
3069
- const num_chars = this.selection.chars + (this.selection.toY - this.selection.fromY) * separator.length;
3190
+ const num_chars = cursor.selection.chars + (cursor.selection.toY - cursor.selection.fromY) * separator.length;
3070
3191
  const pre = code.slice( 0, index );
3071
3192
  const post = code.slice( index + num_chars );
3072
3193
 
3073
3194
  this.code.lines = ( pre + post ).split( separator );
3074
3195
 
3075
- this.cursorToLine( cursor, this.selection.fromY, true );
3076
- this.cursorToPosition( cursor, this.selection.fromX );
3077
- this.endSelection();
3196
+ this.cursorToLine( cursor, cursor.selection.fromY, true );
3197
+ this.cursorToPosition( cursor, cursor.selection.fromX );
3198
+ this.endSelection( cursor );
3078
3199
  this.processLines();
3079
3200
  }
3080
3201
 
3081
- endSelection() {
3202
+ endSelection( cursor ) {
3082
3203
 
3083
- this.selections.classList.remove( 'show' );
3084
- this.selections.innerHTML = "";
3085
- delete this.selection;
3086
3204
  delete this._tripleClickSelection;
3087
3205
  delete this._lastSelectionKeyDir;
3206
+ delete this._currentOcurrences;
3207
+
3208
+ if( cursor )
3209
+ {
3210
+ deleteElement( this.selections[ cursor.name ] );
3211
+ delete this.selections[ cursor.name ];
3212
+ delete cursor.selection;
3213
+ }
3214
+ else
3215
+ {
3216
+ for( let cursor of this.cursors.children )
3217
+ {
3218
+ deleteElement( this.selections[ cursor.name ] );
3219
+ delete this.selections[ cursor.name ];
3220
+ delete cursor.selection;
3221
+ }
3222
+ }
3088
3223
  }
3089
3224
 
3090
3225
  selectAll() {
@@ -3098,13 +3233,13 @@ class CodeEditor {
3098
3233
  this.startSelection( cursor );
3099
3234
 
3100
3235
  const nlines = this.code.lines.length - 1;
3101
- this.selection.toX = this.code.lines[ nlines ].length;
3102
- this.selection.toY = nlines;
3236
+ cursor.selection.toX = this.code.lines[ nlines ].length;
3237
+ cursor.selection.toY = nlines;
3103
3238
 
3104
- this.cursorToPosition( cursor, this.selection.toX );
3105
- this.cursorToLine( cursor, this.selection.toY );
3239
+ this.cursorToPosition( cursor, cursor.selection.toX );
3240
+ this.cursorToLine( cursor, cursor.selection.toY );
3106
3241
 
3107
- this.processSelection( null, true );
3242
+ this._processSelection( cursor, null, true );
3108
3243
 
3109
3244
  this.hideAutoCompleteBox();
3110
3245
  }
@@ -3210,6 +3345,8 @@ class CodeEditor {
3210
3345
 
3211
3346
  state.position = cursor.position;
3212
3347
  state.line = cursor.line;
3348
+ state.selection = cursor.selection ? cursor.selection.save() : undefined;
3349
+
3213
3350
  return state;
3214
3351
  }
3215
3352
 
@@ -3242,7 +3379,7 @@ class CodeEditor {
3242
3379
  const cursorsInLine = Array.from( this.cursors.children ).filter( v => v.line == line );
3243
3380
 
3244
3381
  while( cursorsInLine.length > 1 )
3245
- cursorsInLine.pop().remove();
3382
+ this.removeCursor( cursorsInLine.pop() );
3246
3383
  }
3247
3384
 
3248
3385
  restoreCursor( cursor, state ) {
@@ -3254,6 +3391,22 @@ class CodeEditor {
3254
3391
  cursor.style.left = "calc(" + cursor._left + "px + " + this.xPadding + ")";
3255
3392
  cursor._top = cursor.line * this.lineHeight;
3256
3393
  cursor.style.top = "calc(" + cursor._top + "px)";
3394
+
3395
+ if( state.selection )
3396
+ {
3397
+ this.startSelection( cursor );
3398
+
3399
+ cursor.selection.load( state.selection );
3400
+
3401
+ this._processSelection( cursor, null, true );
3402
+ }
3403
+ }
3404
+
3405
+ removeCursor( cursor ) {
3406
+
3407
+ deleteElement( this.selections[ cursor.name ] );
3408
+ delete this.selections[ cursor.name ];
3409
+ deleteElement( cursor );
3257
3410
  }
3258
3411
 
3259
3412
  resetCursorPos( flag, cursor ) {
@@ -3718,6 +3871,8 @@ class CodeEditor {
3718
3871
 
3719
3872
  showSearchBox( clear ) {
3720
3873
 
3874
+ this.hideSearchLineBox();
3875
+
3721
3876
  this.searchbox.classList.add( 'opened' );
3722
3877
  this.searchboxActive = true;
3723
3878
 
@@ -3727,12 +3882,26 @@ class CodeEditor {
3727
3882
  {
3728
3883
  input.value = "";
3729
3884
  }
3885
+ else
3886
+ {
3887
+ const cursor = this._getCurrentCursor();
3888
+
3889
+ if( cursor.selection )
3890
+ {
3891
+ input.value = cursor.selection.getText() ?? input.value;
3892
+ }
3893
+ }
3894
+
3895
+ input.selectionStart = 0;
3896
+ input.selectionEnd = input.value.length;
3730
3897
 
3731
3898
  input.focus();
3732
3899
  }
3733
3900
 
3734
3901
  hideSearchBox() {
3735
3902
 
3903
+ const active = this.searchboxActive;
3904
+
3736
3905
  if( this.searchboxActive )
3737
3906
  {
3738
3907
  this.searchbox.classList.remove( 'opened' );
@@ -3741,31 +3910,49 @@ class CodeEditor {
3741
3910
 
3742
3911
  else if( this._lastResult )
3743
3912
  {
3744
- this._lastResult.dom.remove();
3913
+ deleteElement( this._lastResult.dom );
3745
3914
  delete this._lastResult;
3746
3915
  }
3916
+
3917
+ this.searchResultSelections.classList.remove( 'show' );
3918
+
3919
+ return active != this.searchboxActive;
3747
3920
  }
3748
3921
 
3749
- search( text, reverse ) {
3922
+ search( text, reverse, callback, skipAlert ) {
3750
3923
 
3751
3924
  text = text ?? this._lastTextFound;
3752
3925
 
3753
3926
  if( !text )
3754
3927
  return;
3755
3928
 
3756
- let cursorData = new LX.vec2( this.position, this.line );
3929
+ let cursor = this._getCurrentCursor();
3930
+ let cursorData = new LX.vec2( cursor.position, cursor.line );
3757
3931
  let line = null;
3758
3932
  let char = -1;
3759
3933
 
3760
3934
  if( this._lastResult )
3761
3935
  {
3762
- this._lastResult.dom.remove();
3936
+ deleteElement( this._lastResult.dom );
3763
3937
  cursorData = this._lastResult.pos;
3764
3938
  delete this._lastResult;
3765
3939
  }
3766
3940
 
3767
3941
  const getIndex = l => {
3768
- return this.code.lines[ l ].substr( l == cursorData.y ? cursorData.x : 0 ).indexOf( text );
3942
+
3943
+ var string = this.code.lines[ l ];
3944
+
3945
+ if( reverse )
3946
+ {
3947
+ string = string.substr( 0, l == cursorData.y ? cursorData.x : string.length );
3948
+ var reversed = strReverse( string );
3949
+ var reversedIdx = reversed.indexOf( strReverse( text ) );
3950
+ return reversedIdx == -1 ? -1 : string.length - reversedIdx - text.length;
3951
+ }
3952
+ else
3953
+ {
3954
+ return string.substr( l == cursorData.y ? cursorData.x : 0 ).indexOf( text );
3955
+ }
3769
3956
  };
3770
3957
 
3771
3958
  if( reverse )
@@ -3795,7 +3982,15 @@ class CodeEditor {
3795
3982
 
3796
3983
  if( line == null)
3797
3984
  {
3798
- alert("No results!")
3985
+ if( !skipAlert )
3986
+ alert( "No results!" );
3987
+
3988
+ const lastLine = this.code.lines.length - 1;
3989
+
3990
+ this._lastResult = {
3991
+ 'dom': this.searchResultSelections.lastChild,
3992
+ 'pos': reverse ? new LX.vec2( this.code.lines[ lastLine ].length, lastLine ) : new LX.vec2( 0, 0 )
3993
+ };
3799
3994
  return;
3800
3995
  }
3801
3996
 
@@ -3805,32 +4000,44 @@ class CodeEditor {
3805
4000
  have to add the length of the substring (0, first_ocurrence)
3806
4001
  */
3807
4002
 
3808
- char += ( line == cursorData.y ? cursorData.x : 0 );
4003
+
4004
+ if( !reverse )
4005
+ char += ( line == cursorData.y ? cursorData.x : 0 );
4006
+
3809
4007
 
3810
4008
  // Text found..
3811
4009
 
3812
4010
  this._lastTextFound = text;
3813
4011
 
3814
4012
  this.codeScroller.scrollTo(
3815
- Math.max( char * this.charWidth - this.codeScroller.clientWidth ),
3816
- Math.max( line - 10 ) * this.lineHeight
4013
+ Math.max( char * this.charWidth - this.codeScroller.clientWidth, 0 ),
4014
+ Math.max( line - 10, 0 ) * this.lineHeight
3817
4015
  );
3818
4016
 
3819
- // Show elements
3820
- this.selections.classList.add( 'show' );
4017
+ if( callback )
4018
+ {
4019
+ callback( char, line );
4020
+ }
4021
+ else
4022
+ {
4023
+ // Show elements
4024
+ this.searchResultSelections.classList.add( 'show' );
4025
+
4026
+ // Create new selection instance
4027
+ cursor.selection = new CodeSelection( this, cursor, "lexcodesearchresult" );
4028
+ cursor.selection.selectInline( cursor, char, line, this.measureString( text ), true );
4029
+ }
3821
4030
 
3822
- // Create new selection instance
3823
- this.selection = new CodeSelection( this, 0, 0, "lexcodesearchresult" );
3824
- this.selection.selectInline( char, line, this.measureString( text ) );
3825
4031
  this._lastResult = {
3826
- 'dom': this.selections.lastChild,
3827
- 'pos': new LX.vec2( char + text.length, line )
4032
+ 'dom': this.searchResultSelections.lastChild,
4033
+ 'pos': new LX.vec2( char + text.length * ( reverse ? -1 : 1 ) , line )
3828
4034
  };
3829
-
3830
4035
  }
3831
4036
 
3832
4037
  showSearchLineBox() {
3833
4038
 
4039
+ this.hideSearchBox();
4040
+
3834
4041
  this.searchlinebox.classList.add( 'opened' );
3835
4042
  this.searchlineboxActive = true;
3836
4043
 
@@ -3860,6 +4067,40 @@ class CodeEditor {
3860
4067
  this.cursorToLine( cursor, line - 1, true );
3861
4068
  }
3862
4069
 
4070
+ selectNextOcurrence( cursor ) {
4071
+
4072
+ if( !cursor.selection )
4073
+ return;
4074
+
4075
+ const text = cursor.selection.getText();
4076
+ if( !text )
4077
+ return;
4078
+
4079
+ if( !this._currentOcurrences )
4080
+ {
4081
+ const currentKey = [ cursor.position - text.length, cursor.line ].join( '_' );
4082
+ this._currentOcurrences = { };
4083
+ this._currentOcurrences[ currentKey ] = true;
4084
+ }
4085
+
4086
+ this.search( text, false, (col, ln) => {
4087
+
4088
+ const key = [ col, ln ].join( '_' );
4089
+
4090
+ if( this._currentOcurrences[ key ] ) {
4091
+ return;
4092
+ }
4093
+
4094
+ var newCursor = this._addCursor( ln, col, true );
4095
+ this.startSelection( newCursor );
4096
+ newCursor.selection.selectInline( newCursor, col, ln, this.measureString( text ) );
4097
+ this.cursorToString( newCursor, text );
4098
+
4099
+ this._currentOcurrences[ key ] = true;
4100
+
4101
+ }, true );
4102
+ }
4103
+
3863
4104
  _updateDataInfoPanel( signal, value ) {
3864
4105
 
3865
4106
  if( !this.skipCodeInfo )
@@ -3962,6 +4203,7 @@ class CodeEditor {
3962
4203
  delete this._pendingString;
3963
4204
  delete this._buildingBlockComment;
3964
4205
  delete this._markdownHeader;
4206
+ delete this._lastResult;
3965
4207
  }
3966
4208
  }
3967
4209
 
package/build/lexgui.js CHANGED
@@ -12,7 +12,7 @@ console.warn( 'Script "build/lexgui.js" is depracated and will be removed soon.
12
12
  */
13
13
 
14
14
  var LX = global.LX = {
15
- version: "0.1.25",
15
+ version: "0.1.26",
16
16
  ready: false,
17
17
  components: [], // specific pre-build components
18
18
  signals: {} // events and triggers
@@ -2178,7 +2178,7 @@ console.warn( 'Script "build/lexgui.js" is depracated and will be removed soon.
2178
2178
  set( value ) {
2179
2179
 
2180
2180
  if( this.onSetValue )
2181
- this.onSetValue( value );
2181
+ return this.onSetValue( value );
2182
2182
 
2183
2183
  console.warn("Can't set value of " + this.typeName());
2184
2184
  }
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  var LX = {
11
- version: "0.1.25",
11
+ version: "0.1.26",
12
12
  ready: false,
13
13
  components: [], // specific pre-build components
14
14
  signals: {} // events and triggers
@@ -2174,7 +2174,7 @@ class Widget {
2174
2174
  set( value ) {
2175
2175
 
2176
2176
  if( this.onSetValue )
2177
- this.onSetValue( value );
2177
+ return this.onSetValue( value );
2178
2178
 
2179
2179
  console.warn("Can't set value of " + this.typeName());
2180
2180
  }
package/changelog.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # lexgui.js changelog
2
2
 
3
+ ## 0.1.26
4
+
5
+ Code Editor:
6
+ - Search Next Ocurrence using "Ctrl+D" (Duplicate line moved to "Alt+D")
7
+ - Previous selection is restored on undo.
8
+ - Get text to search from selection.
9
+ - Reverse search bugfixes.
10
+ - Pasting content now scrolls to follow cursor.
11
+
12
+ Minor fixes.
13
+
3
14
  ## 0.1.25
4
15
 
5
16
  Code Editor:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lexgui",
3
- "version": "0.1.25",
3
+ "version": "0.1.26",
4
4
  "description": "JS library to create web graphical user interfaces",
5
5
  "type": "module",
6
6
  "main": "./build/lexgui.js",