lexgui 0.1.22 → 0.1.23

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.
@@ -201,8 +201,8 @@ class CodeEditor {
201
201
  static WORD_TYPE_METHOD = 0;
202
202
  static WORD_TYPE_CLASS = 1;
203
203
 
204
- static CODE_MAX_FONT_SIZE = 20;
205
- static CODE_MIN_FONT_SIZE = 11;
204
+ static CODE_MAX_FONT_SIZE = 22;
205
+ static CODE_MIN_FONT_SIZE = 9;
206
206
 
207
207
  /**
208
208
  * @param {*} options
@@ -333,42 +333,15 @@ class CodeEditor {
333
333
 
334
334
  // Add main cursor
335
335
  {
336
- var cursor = document.createElement( 'div' );
337
- cursor.className = "cursor";
338
- cursor.innerHTML = " ";
339
- cursor._left = 0;
340
- cursor.style.left = this.xPadding;
341
- cursor._top = 0;
342
- cursor.style.top = cursor._top + "px";
343
- cursor._position = 0;
344
- cursor._line = 0;
345
- cursor.print = (function() { console.log( this.line, this.position ) }).bind( cursor );
336
+ this._addCursor( 0, 0, true );
346
337
 
347
338
  Object.defineProperty( this, 'line', {
348
- get: (v) => { return cursor.line }
339
+ get: (v) => { return this._getCurrentCursor().line }
349
340
  } );
350
-
341
+
351
342
  Object.defineProperty( this, 'position', {
352
- get: (v) => { return cursor.position }
353
- } );
354
-
355
- Object.defineProperty( cursor, 'line', {
356
- get: (v) => { return this._line },
357
- set: (v) => {
358
- this._line = v;
359
- this._setActiveLine( v );
360
- }
343
+ get: (v) => { return this._getCurrentCursor().position }
361
344
  } );
362
-
363
- Object.defineProperty( cursor, 'position', {
364
- get: (v) => { return this._position },
365
- set: (v) => {
366
- this._position = v;
367
- this._updateDataInfoPanel( "@cursor-pos", "Col " + v );
368
- }
369
- } );
370
-
371
- this.cursors.appendChild( cursor );
372
345
  }
373
346
 
374
347
  // Scroll stuff
@@ -534,6 +507,7 @@ class CodeEditor {
534
507
  this.tabSpaces = 4;
535
508
  this.maxUndoSteps = 16;
536
509
  this.lineHeight = 20;
510
+ this.charWidth = 7; // To update later depending on size..
537
511
  this.defaultSingleLineCommentToken = '//';
538
512
  this.defaultBlockCommentTokens = [ '/*', '*/' ];
539
513
  this._lastTime = null;
@@ -624,6 +598,7 @@ class CodeEditor {
624
598
  };
625
599
  this.statementsAndDeclarations = {
626
600
  'JavaScript': ['for', 'if', 'else', 'case', 'switch', 'return', 'while', 'continue', 'break', 'do', 'import', 'from', 'throw', 'async', 'try', 'catch', 'await'],
601
+ 'CSS': ['@', 'import'],
627
602
  'C++': ['std', 'for', 'if', 'else', 'return', 'continue', 'break', 'case', 'switch', 'while', 'using', 'glm', 'spdlog'],
628
603
  'GLSL': ['for', 'if', 'else', 'return', 'continue', 'break'],
629
604
  'WGSL': ['const','for', 'if', 'else', 'return', 'continue', 'break', 'storage', 'read', 'uniform'],
@@ -659,6 +634,7 @@ class CodeEditor {
659
634
  this.action( 'Escape', false, ( ln, cursor, e ) => {
660
635
  this.hideAutoCompleteBox();
661
636
  this.hideSearchBox();
637
+ this._removeSecondaryCursors();
662
638
  });
663
639
 
664
640
  this.action( 'Backspace', false, ( ln, cursor, e ) => {
@@ -704,7 +680,7 @@ class CodeEditor {
704
680
  }
705
681
  else if( this.code.lines[ ln - 1 ] != undefined ) {
706
682
 
707
- this.lineUp();
683
+ this.lineUp( cursor );
708
684
  e.cancelShift = true;
709
685
  this.actions[ 'End' ].callback( cursor.line, cursor, e );
710
686
  // Move line on top
@@ -759,7 +735,7 @@ class CodeEditor {
759
735
  const prestring = this.code.lines[ ln ].substring( 0, idx );
760
736
  let lastX = cursor.position;
761
737
 
762
- this.resetCursorPos( CodeEditor.CURSOR_LEFT );
738
+ this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
763
739
  if(idx > 0) this.cursorToString( cursor, prestring );
764
740
  this.setScrollLeft( 0 );
765
741
 
@@ -793,14 +769,14 @@ class CodeEditor {
793
769
  this.selection.selectInline(cursor.position, cursor.line, this.measureString( string ));
794
770
  else
795
771
  {
796
- this.resetCursorPos( CodeEditor.CURSOR_LEFT );
772
+ this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
797
773
  this.cursorToString( cursor, this.code.lines[ ln ] );
798
774
  this.processSelection( e );
799
775
  }
800
776
  } else if( !e.keepSelection )
801
777
  this.endSelection();
802
778
 
803
- this.resetCursorPos( CodeEditor.CURSOR_LEFT );
779
+ this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
804
780
  this.cursorToString( cursor, this.code.lines[ ln ] );
805
781
 
806
782
  const last_char = ( this.code.clientWidth / this.charWidth )|0;
@@ -856,7 +832,7 @@ class CodeEditor {
856
832
  if( !this.selection )
857
833
  this.startSelection( cursor );
858
834
 
859
- this.lineUp();
835
+ this.lineUp( cursor );
860
836
 
861
837
  var letter = this.getCharAtPos( cursor );
862
838
  if( !letter ) {
@@ -867,7 +843,7 @@ class CodeEditor {
867
843
 
868
844
  } else {
869
845
  this.endSelection();
870
- this.lineUp();
846
+ this.lineUp( cursor );
871
847
  // Go to end of line if out of line
872
848
  var letter = this.getCharAtPos( cursor );
873
849
  if( !letter ) this.actions['End'].callback( cursor.line, cursor, e );
@@ -960,7 +936,7 @@ class CodeEditor {
960
936
  }
961
937
  else {
962
938
  this.selection.invertIfNecessary();
963
- this.resetCursorPos( CodeEditor.CURSOR_LEFT | CodeEditor.CURSOR_TOP );
939
+ this.resetCursorPos( CodeEditor.CURSOR_LEFT | CodeEditor.CURSOR_TOP, cursor );
964
940
  this.cursorToLine( cursor, this.selection.fromY, true );
965
941
  this.cursorToPosition( cursor, this.selection.fromX );
966
942
  this.endSelection();
@@ -986,7 +962,7 @@ class CodeEditor {
986
962
 
987
963
  // Nothing to do..
988
964
  if( cursor.line == this.code.lines.length - 1 &&
989
- cursor.position == this.code.lines[ cursor.line - 1 ].length )
965
+ cursor.position == this.code.lines[ cursor.line ].length )
990
966
  return;
991
967
 
992
968
  if( e.metaKey ) { // Apple devices (Command)
@@ -1002,7 +978,7 @@ class CodeEditor {
1002
978
  // Selections...
1003
979
  if( e.shiftKey ) { if( !this.selection ) this.startSelection( cursor ); }
1004
980
  else this.endSelection();
1005
- this.cursorToString( cursor, substr);
981
+ this.cursorToString( cursor, substr );
1006
982
  if( e.shiftKey ) this.processSelection( e );
1007
983
  } else {
1008
984
  var letter = this.getCharAtPos( cursor );
@@ -1020,7 +996,7 @@ class CodeEditor {
1020
996
  else
1021
997
  {
1022
998
  this.selection.invertIfNecessary();
1023
- this.resetCursorPos( CodeEditor.CURSOR_LEFT | CodeEditor.CURSOR_TOP );
999
+ this.resetCursorPos( CodeEditor.CURSOR_LEFT | CodeEditor.CURSOR_TOP, cursor );
1024
1000
  this.cursorToLine( cursor, this.selection.toY );
1025
1001
  this.cursorToPosition( cursor, this.selection.toX );
1026
1002
  this.endSelection();
@@ -1106,7 +1082,8 @@ class CodeEditor {
1106
1082
  }
1107
1083
 
1108
1084
  getText( min ) {
1109
- return this.code.lines.join(min ? ' ' : '\n');
1085
+
1086
+ return this.code.lines.join( min ? ' ' : '\n' );
1110
1087
  }
1111
1088
 
1112
1089
  // This can be used to empty all text...
@@ -1115,7 +1092,9 @@ class CodeEditor {
1115
1092
  let new_lines = text.split( '\n' );
1116
1093
  this.code.lines = [].concat( new_lines );
1117
1094
 
1118
- let cursor = this.cursors.children[ 0 ];
1095
+ this._removeSecondaryCursors();
1096
+
1097
+ let cursor = this._getCurrentCursor( true );
1119
1098
  let lastLine = new_lines.pop();
1120
1099
 
1121
1100
  this.cursorToLine( cursor, new_lines.length ); // Already substracted 1
@@ -1128,9 +1107,8 @@ class CodeEditor {
1128
1107
  }
1129
1108
  }
1130
1109
 
1131
- appendText( text ) {
1110
+ appendText( text, cursor ) {
1132
1111
 
1133
- let cursor = this.cursors.children[ 0 ];
1134
1112
  let lidx = cursor.line;
1135
1113
 
1136
1114
  if( this.selection ) {
@@ -1234,6 +1212,65 @@ class CodeEditor {
1234
1212
  }
1235
1213
  }
1236
1214
 
1215
+ _addCursor( line = 0, position = 0, isMain = false ) {
1216
+
1217
+ let cursor = document.createElement( 'div' );
1218
+ cursor.className = "cursor";
1219
+ cursor.innerHTML = " ";
1220
+ cursor.isMainCursor = isMain;
1221
+ cursor._left = position * this.charWidth;
1222
+ cursor.style.left = "calc( " + cursor._left + "px + " + this.xPadding + " )";
1223
+ cursor._top = line * this.lineHeight;
1224
+ cursor.style.top = cursor._top + "px";
1225
+ cursor._position = position;
1226
+ cursor._line = line;
1227
+ cursor.print = (function() { console.log( this, this._line, this._position ) }).bind( cursor );
1228
+
1229
+ Object.defineProperty( cursor, 'line', {
1230
+ get: (v) => { return cursor._line },
1231
+ set: (v) => {
1232
+ cursor._line = v;
1233
+ if( cursor.isMainCursor ) this._setActiveLine( v );
1234
+ }
1235
+ } );
1236
+
1237
+ Object.defineProperty( cursor, 'position', {
1238
+ get: (v) => { return cursor._position },
1239
+ set: (v) => {
1240
+ cursor._position = v;
1241
+ if( cursor.isMainCursor ) this._updateDataInfoPanel( "@cursor-pos", "Col " + v );
1242
+ }
1243
+ } );
1244
+
1245
+ this.cursors.appendChild( cursor );
1246
+
1247
+ return cursor;
1248
+ }
1249
+
1250
+ _getCurrentCursor( removeOthers ) {
1251
+
1252
+ if( removeOthers )
1253
+ {
1254
+ this._removeSecondaryCursors();
1255
+ }
1256
+
1257
+ return this.cursors.children[ 0 ];
1258
+ }
1259
+
1260
+ _removeSecondaryCursors() {
1261
+
1262
+ while( this.cursors.childElementCount > 1 )
1263
+ this.cursors.lastChild.remove();
1264
+ }
1265
+
1266
+ _logCursors() {
1267
+
1268
+ for( let cursor of this.cursors.children )
1269
+ {
1270
+ cursor.print();
1271
+ }
1272
+ }
1273
+
1237
1274
  _addUndoStep( cursor, force, deleteRedo = true ) {
1238
1275
 
1239
1276
  const d = new Date();
@@ -1260,8 +1297,6 @@ class CodeEditor {
1260
1297
  this.code.redoSteps.length = 0;
1261
1298
  }
1262
1299
 
1263
- var cursor = cursor ?? this.cursors.children[ 0 ];
1264
-
1265
1300
  this.code.undoSteps.push( {
1266
1301
  lines: LX.deepCopy( this.code.lines ),
1267
1302
  cursor: this.saveCursor( cursor ),
@@ -1272,8 +1307,6 @@ class CodeEditor {
1272
1307
 
1273
1308
  _addRedoStep( cursor ) {
1274
1309
 
1275
- var cursor = cursor ?? this.cursors.children[ 0 ];
1276
-
1277
1310
  this.code.redoSteps.push( {
1278
1311
  lines: LX.deepCopy( this.code.lines ),
1279
1312
  cursor: this.saveCursor( cursor ),
@@ -1374,7 +1407,7 @@ class CodeEditor {
1374
1407
  ext == 'css' ? "fa-solid fa-hashtag dodgerblue" :
1375
1408
  ext == 'xml' ? "fa-solid fa-rss orange" :
1376
1409
  ext == 'bat' ? "fa-brands fa-windows lightblue" :
1377
- [ 'js', 'py', 'json', 'cpp', 'rs' ].indexOf( ext ) > -1 ? "images/" + ext + ".png" :
1410
+ [ 'js', 'py', 'json', 'cpp', 'rs', 'md' ].indexOf( ext ) > -1 ? "images/" + ext + ".png" :
1378
1411
  !isNewTabButton ? "fa-solid fa-align-left gray" : undefined;
1379
1412
  }
1380
1413
 
@@ -1388,7 +1421,7 @@ class CodeEditor {
1388
1421
  });
1389
1422
  }
1390
1423
 
1391
- _onSelectTab( isNewTabButton, event, name, ) {
1424
+ _onSelectTab( isNewTabButton, event, name ) {
1392
1425
 
1393
1426
  if( isNewTabButton )
1394
1427
  {
@@ -1396,7 +1429,9 @@ class CodeEditor {
1396
1429
  return;
1397
1430
  }
1398
1431
 
1399
- var cursor = cursor ?? this.cursors.children[ 0 ];
1432
+ this._removeSecondaryCursors();
1433
+ var cursor = this._getCurrentCursor( true );
1434
+
1400
1435
  this.saveCursor( cursor, this.code.cursorState );
1401
1436
 
1402
1437
  this.code = this.loadedTabs[ name ];
@@ -1573,12 +1608,12 @@ class CodeEditor {
1573
1608
  }
1574
1609
  }
1575
1610
 
1576
- processMouse(e) {
1611
+ processMouse( e ) {
1577
1612
 
1578
1613
  if( !e.target.classList.contains('code') ) return;
1579
1614
  if( !this.code ) return;
1580
1615
 
1581
- var cursor = this.cursors.children[ 0 ];
1616
+ var cursor = this._getCurrentCursor();
1582
1617
  var code_rect = this.code.getBoundingClientRect();
1583
1618
  var mouse_pos = [(e.clientX - code_rect.x), (e.clientY - code_rect.y)];
1584
1619
 
@@ -1630,7 +1665,7 @@ class CodeEditor {
1630
1665
  {
1631
1666
  case LX.MOUSE_DOUBLE_CLICK:
1632
1667
  const [word, from, to] = this.getWordAtPos( cursor );
1633
- this.resetCursorPos( CodeEditor.CURSOR_LEFT );
1668
+ this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
1634
1669
  this.cursorToPosition( cursor, from );
1635
1670
  this.startSelection( cursor );
1636
1671
  this.selection.selectInline( from, cursor.line, this.measureString( word ) );
@@ -1638,7 +1673,7 @@ class CodeEditor {
1638
1673
  break;
1639
1674
  // Select entire line
1640
1675
  case LX.MOUSE_TRIPLE_CLICK:
1641
- this.resetCursorPos( CodeEditor.CURSOR_LEFT );
1676
+ this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
1642
1677
  e._shiftKey = true;
1643
1678
  this.actions['End'].callback(cursor.line, cursor, e);
1644
1679
  this._tripleClickSelection = true;
@@ -1654,11 +1689,11 @@ class CodeEditor {
1654
1689
  return;
1655
1690
 
1656
1691
  LX.addContextMenu( null, e, m => {
1657
- m.add( "Copy", () => { this._copyContent(); } );
1692
+ m.add( "Copy", () => { this._copyContent( cursor ); } );
1658
1693
  if( !this.disableEdition )
1659
1694
  {
1660
- m.add( "Cut", () => { this._cutContent(); } );
1661
- m.add( "Paste", () => { this._pasteContent(); } );
1695
+ m.add( "Cut", () => { this._cutContent( cursor ); } );
1696
+ m.add( "Paste", () => { this._pasteContent( cursor ); } );
1662
1697
  m.add( "" );
1663
1698
  m.add( "Format/JSON", () => {
1664
1699
  let json = this.toJSONFormat( this.getText() );
@@ -1690,26 +1725,39 @@ class CodeEditor {
1690
1725
 
1691
1726
  processClick( e ) {
1692
1727
 
1693
- var cursor = this.cursors.children[ 0 ];
1728
+ var cursor = this._getCurrentCursor();
1694
1729
  var code_rect = this.codeScroller.getBoundingClientRect();
1695
1730
  var position = [( e.clientX - code_rect.x ) + this.getScrollLeft(), (e.clientY - code_rect.y) + this.getScrollTop()];
1696
1731
  var ln = (position[ 1 ] / this.lineHeight)|0;
1697
1732
 
1698
1733
  if( this.code.lines[ ln ] == undefined )
1699
1734
  return;
1700
-
1701
- this.cursorToLine( cursor, ln, true );
1702
-
1735
+
1703
1736
  var ch = ( ( position[ 0 ] - parseInt( this.xPadding ) + 3) / this.charWidth )|0;
1704
1737
  var string = this.code.lines[ ln ].slice( 0, ch );
1705
- this.cursorToPosition( cursor, string.length );
1738
+
1739
+ // Move main cursor there...
1740
+ if( !e.altKey )
1741
+ {
1742
+ // Make sure we only keep the main cursor..
1743
+ this._removeSecondaryCursors();
1744
+
1745
+ this.cursorToLine( cursor, ln, true );
1746
+ this.cursorToPosition( cursor, string.length );
1747
+ }
1748
+
1749
+ // Add new cursor
1750
+ else
1751
+ {
1752
+ this._addCursor( ln, string.length );
1753
+ }
1706
1754
 
1707
1755
  this.hideAutoCompleteBox();
1708
1756
  }
1709
1757
 
1710
1758
  processSelection( e, keep_range, flags = CodeEditor.SELECTION_X_Y ) {
1711
1759
 
1712
- var cursor = this.cursors.children[ 0 ];
1760
+ var cursor = this._getCurrentCursor();
1713
1761
  const isMouseEvent = e && ( e.constructor == MouseEvent );
1714
1762
 
1715
1763
  if( isMouseEvent ) this.processClick( e );
@@ -1863,7 +1911,23 @@ class CodeEditor {
1863
1911
  }
1864
1912
  }
1865
1913
 
1866
- async processKey( e ) {
1914
+ async processKey( event ) {
1915
+
1916
+ const numCursors = this.cursors.childElementCount;
1917
+
1918
+ for( var i = 0; i < numCursors; i++ )
1919
+ {
1920
+ let cursor = this.cursors.children[ i ];
1921
+
1922
+ // We could delete secondary cursor while iterating..
1923
+ if( !cursor )
1924
+ break;
1925
+
1926
+ this.processKeyAtCursor( event, cursor );
1927
+ }
1928
+ }
1929
+
1930
+ async processKeyAtCursor( e, cursor ) {
1867
1931
 
1868
1932
  if( !this.code || e.srcElement.constructor != HTMLDivElement )
1869
1933
  return;
@@ -1876,7 +1940,6 @@ class CodeEditor {
1876
1940
  if( key.length > 1 && this.specialKeys.indexOf( key ) == -1 )
1877
1941
  return;
1878
1942
 
1879
- let cursor = this.cursors.children[ 0 ];
1880
1943
  let lidx = cursor.line;
1881
1944
  this.code.lines[ lidx ] = this.code.lines[ lidx ] ?? "";
1882
1945
 
@@ -1887,26 +1950,14 @@ class CodeEditor {
1887
1950
  switch( key.toLowerCase() ) {
1888
1951
  case 'a': // select all
1889
1952
  e.preventDefault();
1890
- this.resetCursorPos( CodeEditor.CURSOR_LEFT | CodeEditor.CURSOR_TOP );
1891
- this.startSelection( cursor );
1892
- const nlines = this.code.lines.length - 1;
1893
- this.selection.toX = this.code.lines[ nlines ].length;
1894
- this.selection.toY = nlines;
1895
- this.cursorToPosition( cursor, this.selection.toX );
1896
- this.cursorToLine( cursor, this.selection.toY );
1897
- this.processSelection( null, true );
1898
- this.hideAutoCompleteBox();
1953
+ this.selectAll( cursor );
1899
1954
  break;
1900
1955
  case 'c': // copy
1901
- this._copyContent();
1956
+ this._copyContent( cursor );
1902
1957
  return;
1903
1958
  case 'd': // duplicate line
1904
1959
  e.preventDefault();
1905
- this.endSelection();
1906
- this.code.lines.splice( lidx, 0, this.code.lines[ lidx ] );
1907
- this.lineDown( cursor );
1908
- this.processLines();
1909
- this.hideAutoCompleteBox();
1960
+ this._duplicateLine( lidx, cursor );
1910
1961
  return;
1911
1962
  case 'f': // find/search
1912
1963
  e.preventDefault();
@@ -1921,14 +1972,14 @@ class CodeEditor {
1921
1972
  this.onsave( this.getText() );
1922
1973
  return;
1923
1974
  case 'v': // paste
1924
- this._pasteContent();
1975
+ this._pasteContent( cursor );
1925
1976
  return;
1926
1977
  case 'x': // cut line
1927
- this._cutContent();
1978
+ this._cutContent( cursor );
1928
1979
  this.hideAutoCompleteBox();
1929
1980
  return;
1930
1981
  case 'y': // redo
1931
- if(!this.code.redoSteps.length)
1982
+ if( !this.code.redoSteps.length )
1932
1983
  return;
1933
1984
  this._addUndoStep( cursor, true, false);
1934
1985
  const redo_step = this.code.redoSteps.pop();
@@ -1937,7 +1988,7 @@ class CodeEditor {
1937
1988
  this.restoreCursor( cursor, redo_step.cursor );
1938
1989
  return;
1939
1990
  case 'z': // undo
1940
- if(!this.code.undoSteps.length)
1991
+ if( !this.code.undoSteps.length )
1941
1992
  return;
1942
1993
  this._addRedoStep( cursor );
1943
1994
  const undo_step = this.code.undoSteps.pop();
@@ -1953,6 +2004,13 @@ class CodeEditor {
1953
2004
  e.preventDefault();
1954
2005
  this._decreaseFontSize();
1955
2006
  return;
2007
+ case 'arrowdown': // add cursor below only for the main cursor..
2008
+ if( cursor.isMainCursor && this.code.lines[ lidx + 1 ] != undefined )
2009
+ {
2010
+ var new_cursor = this._addCursor( cursor.line, cursor.position );
2011
+ this.lineDown( new_cursor );
2012
+ return;
2013
+ }
1956
2014
  }
1957
2015
  }
1958
2016
 
@@ -1962,8 +2020,9 @@ class CodeEditor {
1962
2020
  case 'ArrowUp':
1963
2021
  if(this.code.lines[ lidx - 1 ] == undefined)
1964
2022
  return;
1965
- swapArrayElements(this.code.lines, lidx - 1, lidx);
1966
- this.lineUp();
2023
+ this._addUndoStep( cursor, true );
2024
+ swapArrayElements( this.code.lines, lidx - 1, lidx );
2025
+ this.lineUp( cursor );
1967
2026
  this.processLine( lidx - 1 );
1968
2027
  this.processLine( lidx );
1969
2028
  this.hideAutoCompleteBox();
@@ -1971,8 +2030,9 @@ class CodeEditor {
1971
2030
  case 'ArrowDown':
1972
2031
  if(this.code.lines[ lidx + 1 ] == undefined)
1973
2032
  return;
1974
- swapArrayElements(this.code.lines, lidx, lidx + 1);
1975
- this.lineDown();
2033
+ this._addUndoStep( cursor, true );
2034
+ swapArrayElements( this.code.lines, lidx, lidx + 1 );
2035
+ this.lineDown( cursor );
1976
2036
  this.processLine( lidx );
1977
2037
  this.processLine( lidx + 1 );
1978
2038
  this.hideAutoCompleteBox();
@@ -1983,17 +2043,18 @@ class CodeEditor {
1983
2043
  // Apply binded actions...
1984
2044
 
1985
2045
  for( const actKey in this.actions ) {
2046
+
1986
2047
  if( key != actKey ) continue;
1987
2048
  e.preventDefault();
1988
2049
 
1989
- if(this.actions[ key ].deleteSelection && this.selection)
1990
- this.actions['Backspace'].callback(lidx, cursor, e);
2050
+ if( this.actions[ key ].deleteSelection && this.selection )
2051
+ this.actions['Backspace'].callback( lidx, cursor, e );
1991
2052
 
1992
2053
  return this.actions[ key ].callback( lidx, cursor, e );
1993
2054
  }
1994
2055
 
1995
2056
  // From now on, don't allow ctrl, shift or meta (mac) combinations
1996
- if( (e.ctrlKey || e.metaKey) )
2057
+ if( e.ctrlKey || e.metaKey )
1997
2058
  return;
1998
2059
 
1999
2060
  // Add undo steps
@@ -2005,7 +2066,7 @@ class CodeEditor {
2005
2066
 
2006
2067
  // Some custom cases for word enclosing (), {}, "", '', ...
2007
2068
 
2008
- const enclosableKeys = ["\"", "'", "(", "{"];
2069
+ const enclosableKeys = [ "\"", "'", "(", "{" ];
2009
2070
  if( enclosableKeys.indexOf( key ) > -1 )
2010
2071
  {
2011
2072
  if( this._encloseSelectedWordWithKey( key, lidx, cursor ) )
@@ -2023,19 +2084,19 @@ class CodeEditor {
2023
2084
 
2024
2085
  // Append key
2025
2086
 
2026
- const isPairKey = (Object.values( this.pairKeys ).indexOf( key ) > -1) && !this.wasKeyPaired;
2027
- const sameKeyNext = isPairKey && (this.code.lines[ lidx ][cursor.position] === key);
2087
+ const isPairKey = ( Object.values( this.pairKeys ).indexOf( key ) > -1 ) && !this.wasKeyPaired;
2088
+ const sameKeyNext = isPairKey && ( this.code.lines[ lidx ][ cursor.position ] === key );
2028
2089
 
2029
2090
  if( !sameKeyNext )
2030
2091
  {
2031
2092
  this.code.lines[ lidx ] = [
2032
- this.code.lines[ lidx ].slice(0, cursor.position),
2093
+ this.code.lines[ lidx ].slice( 0, cursor.position ),
2033
2094
  key,
2034
- this.code.lines[ lidx ].slice(cursor.position)
2095
+ this.code.lines[ lidx ].slice( cursor.position )
2035
2096
  ].join('');
2036
2097
  }
2037
2098
 
2038
- this.cursorToRight( key );
2099
+ this.cursorToRight( key, cursor );
2039
2100
 
2040
2101
  // Some custom cases for auto key pair (), {}, "", '', ...
2041
2102
 
@@ -2045,7 +2106,7 @@ class CodeEditor {
2045
2106
  // Make sure to detect later that the key is paired automatically to avoid loops...
2046
2107
  this.wasKeyPaired = true;
2047
2108
 
2048
- if(sameKeyNext) return;
2109
+ if( sameKeyNext ) return;
2049
2110
 
2050
2111
  this.root.dispatchEvent(new KeyboardEvent('keydown', { 'key': this.pairKeys[ key ] }));
2051
2112
  this.cursorToLeft( key, cursor );
@@ -2072,14 +2133,14 @@ class CodeEditor {
2072
2133
  this.showAutoCompleteBox( key, cursor );
2073
2134
  }
2074
2135
 
2075
- async _pasteContent() {
2136
+ async _pasteContent( cursor ) {
2137
+
2076
2138
  let text = await navigator.clipboard.readText();
2077
- this.appendText(text);
2139
+ this.appendText( text, cursor );
2078
2140
  }
2079
2141
 
2080
- async _copyContent() {
2142
+ async _copyContent( cursor ) {
2081
2143
 
2082
- let cursor = this.cursors.children[ 0 ];
2083
2144
  let text_to_copy = "";
2084
2145
 
2085
2146
  if( !this.selection ) {
@@ -2109,9 +2170,8 @@ class CodeEditor {
2109
2170
  navigator.clipboard.writeText( text_to_copy ).then(() => console.log("Successfully copied"), (err) => console.error("Error"));
2110
2171
  }
2111
2172
 
2112
- async _cutContent() {
2173
+ async _cutContent( cursor ) {
2113
2174
 
2114
- let cursor = this.cursors.children[ 0 ];
2115
2175
  let lidx = cursor.line;
2116
2176
  let text_to_cut = "";
2117
2177
 
@@ -2121,9 +2181,9 @@ class CodeEditor {
2121
2181
  text_to_cut = "\n" + this.code.lines[ cursor.line ];
2122
2182
  this.code.lines.splice( lidx, 1 );
2123
2183
  this.processLines();
2124
- this.resetCursorPos( CodeEditor.CURSOR_LEFT );
2184
+ this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
2125
2185
  if( this.code.lines[ lidx ] == undefined )
2126
- this.lineUp();
2186
+ this.lineUp( cursor );
2127
2187
  }
2128
2188
  else {
2129
2189
 
@@ -2151,6 +2211,16 @@ class CodeEditor {
2151
2211
  navigator.clipboard.writeText( text_to_cut ).then(() => console.log("Successfully cut"), (err) => console.error("Error"));
2152
2212
  }
2153
2213
 
2214
+ _duplicateLine( lidx, cursor ) {
2215
+
2216
+ this.endSelection();
2217
+ this._addUndoStep( cursor, true );
2218
+ this.code.lines.splice( lidx, 0, this.code.lines[ lidx ] );
2219
+ this.lineDown( cursor );
2220
+ this.processLines();
2221
+ this.hideAutoCompleteBox();
2222
+ }
2223
+
2154
2224
  action( key, deleteSelection, fn ) {
2155
2225
 
2156
2226
  this.actions[ key ] = {
@@ -2618,7 +2688,13 @@ class CodeEditor {
2618
2688
  }
2619
2689
 
2620
2690
  _isCSSClass( token, prev, next ) {
2621
- return this.highlight == 'CSS' && prev == '.';
2691
+
2692
+ if( this.highlight != 'CSS' )
2693
+ return false;
2694
+
2695
+ return ( prev == '.' || prev == '::'
2696
+ || ( prev == ':' && next == '{' )
2697
+ || ( token[ 0 ] == '#' && prev != ':' ) );
2622
2698
  }
2623
2699
 
2624
2700
  isNumber( token ) {
@@ -2712,8 +2788,6 @@ class CodeEditor {
2712
2788
 
2713
2789
  lineUp( cursor, resetLeft ) {
2714
2790
 
2715
- cursor = cursor ?? this.cursors.children[ 0 ];
2716
-
2717
2791
  if( this.code.lines[ cursor.line - 1 ] == undefined )
2718
2792
  return false;
2719
2793
 
@@ -2725,8 +2799,6 @@ class CodeEditor {
2725
2799
  }
2726
2800
 
2727
2801
  lineDown( cursor, resetLeft ) {
2728
-
2729
- cursor = cursor ?? this.cursors.children[ 0 ];
2730
2802
 
2731
2803
  if( this.code.lines[ cursor.line + 1 ] == undefined )
2732
2804
  return false;
@@ -2804,10 +2876,23 @@ class CodeEditor {
2804
2876
  delete this._lastSelectionKeyDir;
2805
2877
  }
2806
2878
 
2879
+ selectAll( cursor ) {
2880
+
2881
+ this.resetCursorPos( CodeEditor.CURSOR_LEFT | CodeEditor.CURSOR_TOP, cursor );
2882
+ this.startSelection( cursor );
2883
+ const nlines = this.code.lines.length - 1;
2884
+ this.selection.toX = this.code.lines[ nlines ].length;
2885
+ this.selection.toY = nlines;
2886
+ this.cursorToPosition( cursor, this.selection.toX );
2887
+ this.cursorToLine( cursor, this.selection.toY );
2888
+ this.processSelection( null, true );
2889
+ this.hideAutoCompleteBox();
2890
+ }
2891
+
2807
2892
  cursorToRight( key, cursor ) {
2808
2893
 
2809
2894
  if( !key ) return;
2810
- cursor = cursor ?? this.cursors.children[ 0 ];
2895
+
2811
2896
  cursor._left += this.charWidth;
2812
2897
  cursor.style.left = "calc( " + cursor._left + "px + " + this.xPadding + " )";
2813
2898
  cursor.position++;
@@ -2826,7 +2911,7 @@ class CodeEditor {
2826
2911
  cursorToLeft( key, cursor ) {
2827
2912
 
2828
2913
  if( !key ) return;
2829
- cursor = cursor ?? this.cursors.children[ 0 ];
2914
+
2830
2915
  cursor._left -= this.charWidth;
2831
2916
  cursor._left = Math.max( cursor._left, 0 );
2832
2917
  cursor.style.left = "calc( " + cursor._left + "px + " + this.xPadding + " )";
@@ -2845,13 +2930,12 @@ class CodeEditor {
2845
2930
 
2846
2931
  cursorToTop( cursor, resetLeft = false ) {
2847
2932
 
2848
- cursor = cursor ?? this.cursors.children[ 0 ];
2849
2933
  cursor._top -= this.lineHeight;
2850
2934
  cursor._top = Math.max(cursor._top, 0);
2851
2935
  cursor.style.top = "calc(" + cursor._top + "px)";
2852
2936
  this.restartBlink();
2853
2937
 
2854
- if(resetLeft)
2938
+ if( resetLeft )
2855
2939
  this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
2856
2940
 
2857
2941
  doAsync(() => {
@@ -2863,12 +2947,12 @@ class CodeEditor {
2863
2947
 
2864
2948
  cursorToBottom( cursor, resetLeft = false ) {
2865
2949
 
2866
- cursor = cursor ?? this.cursors.children[ 0 ];
2867
2950
  cursor._top += this.lineHeight;
2868
2951
  cursor.style.top = "calc(" + cursor._top + "px)";
2952
+
2869
2953
  this.restartBlink();
2870
2954
 
2871
- if(resetLeft)
2955
+ if( resetLeft )
2872
2956
  this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
2873
2957
 
2874
2958
  doAsync(() => {
@@ -2880,10 +2964,11 @@ class CodeEditor {
2880
2964
 
2881
2965
  cursorToString( cursor, text, reverse ) {
2882
2966
 
2883
- if( !text.length ) return;
2884
- cursor = cursor ?? this.cursors.children[ 0 ];
2967
+ if( !text.length )
2968
+ return;
2969
+
2885
2970
  for( let char of text )
2886
- reverse ? this.cursorToLeft( char ) : this.cursorToRight( char );
2971
+ reverse ? this.cursorToLeft( char, cursor ) : this.cursorToRight( char, cursor );
2887
2972
  }
2888
2973
 
2889
2974
  cursorToPosition( cursor, position ) {
@@ -2903,17 +2988,16 @@ class CodeEditor {
2903
2988
 
2904
2989
  saveCursor( cursor, state = {} ) {
2905
2990
 
2906
- var cursor = cursor ?? this.cursors.children[ 0 ];
2907
2991
  state.top = cursor._top;
2908
2992
  state.left = cursor._left;
2909
2993
  state.line = cursor.line;
2910
2994
  state.position = cursor.position;
2995
+
2911
2996
  return state;
2912
2997
  }
2913
2998
 
2914
2999
  restoreCursor( cursor, state ) {
2915
3000
 
2916
- cursor = cursor ?? this.cursors.children[ 0 ];
2917
3001
  cursor.line = state.line ?? 0;
2918
3002
  cursor.position = state.position ?? 0;
2919
3003
 
@@ -2925,7 +3009,7 @@ class CodeEditor {
2925
3009
 
2926
3010
  resetCursorPos( flag, cursor ) {
2927
3011
 
2928
- cursor = cursor ?? this.cursors.children[ 0 ];
3012
+ cursor = cursor ?? this._getCurrentCursor();
2929
3013
 
2930
3014
  if( flag & CodeEditor.CURSOR_LEFT )
2931
3015
  {
@@ -2942,14 +3026,14 @@ class CodeEditor {
2942
3026
  }
2943
3027
  }
2944
3028
 
2945
- addSpaceTabs(n) {
3029
+ addSpaceTabs( n ) {
2946
3030
 
2947
3031
  for( var i = 0; i < n; ++i ) {
2948
3032
  this.actions[ 'Tab' ].callback();
2949
3033
  }
2950
3034
  }
2951
3035
 
2952
- addSpaces(n) {
3036
+ addSpaces( n ) {
2953
3037
 
2954
3038
  for( var i = 0; i < n; ++i ) {
2955
3039
  this.root.dispatchEvent( new CustomEvent( 'keydown', { 'detail': {
@@ -3109,14 +3193,11 @@ class CodeEditor {
3109
3193
 
3110
3194
  getCharAtPos( cursor, offset = 0 ) {
3111
3195
 
3112
- cursor = cursor ?? this.cursors.children[ 0 ];
3113
3196
  return this.code.lines[ cursor.line ][ cursor.position + offset ];
3114
3197
  }
3115
3198
 
3116
- getWordAtPos( cursor, loffset = 0, roffset ) {
3199
+ getWordAtPos( cursor, offset = 0 ) {
3117
3200
 
3118
- roffset = roffset ?? loffset;
3119
- cursor = cursor ?? this.cursors.children[ 0 ];
3120
3201
  const col = cursor.line;
3121
3202
  const words = this.code.lines[ col ];
3122
3203
 
@@ -3126,8 +3207,8 @@ class CodeEditor {
3126
3207
  return (exceptions.indexOf( char ) > - 1) || (code > 47 && code < 58) || (code > 64 && code < 91) || (code > 96 && code < 123);
3127
3208
  }
3128
3209
 
3129
- let from = cursor.position + roffset;
3130
- let to = cursor.position + loffset;
3210
+ let from = cursor.position + offset;
3211
+ let to = cursor.position + offset;
3131
3212
 
3132
3213
  // Check left ...
3133
3214
 
@@ -3146,7 +3227,7 @@ class CodeEditor {
3146
3227
  let word = words.substring( from, to );
3147
3228
  if( word == ' ' )
3148
3229
  {
3149
- if( loffset < 0 )
3230
+ if( offset < 0 )
3150
3231
  {
3151
3232
  while( words[ from - 1 ] != undefined && words[ from - 1 ] == ' ' )
3152
3233
  from--;
@@ -3319,8 +3400,10 @@ class CodeEditor {
3319
3400
 
3320
3401
  hideAutoCompleteBox() {
3321
3402
 
3403
+ const isActive = this.isAutoCompleteActive;
3322
3404
  this.isAutoCompleteActive = false;
3323
3405
  this.autocomplete.classList.remove( 'show' );
3406
+ return isActive != this.isAutoCompleteActive;
3324
3407
  }
3325
3408
 
3326
3409
  autoCompleteWord( cursor, suggestion ) {
@@ -3521,12 +3604,21 @@ class CodeEditor {
3521
3604
  return;
3522
3605
 
3523
3606
  this.codeScroller.scrollTo( 0, Math.max( line - 15 ) * this.lineHeight );
3607
+
3608
+ // Select line ?
3609
+ var cursor = this._getCurrentCursor( true );
3610
+ this.cursorToLine( cursor, line - 1, true );
3524
3611
  }
3525
3612
 
3526
3613
  _updateDataInfoPanel( signal, value ) {
3527
3614
 
3528
3615
  if( !this.skipCodeInfo )
3529
3616
  {
3617
+ if( this.cursors.childElementCount > 1 )
3618
+ {
3619
+ value = "";
3620
+ }
3621
+
3530
3622
  LX.emit( signal, value );
3531
3623
  }
3532
3624
  }
@@ -3541,7 +3633,7 @@ class CodeEditor {
3541
3633
  let line = this.code.childNodes[ old_local ];
3542
3634
 
3543
3635
  if( !line )
3544
- return;
3636
+ return;
3545
3637
 
3546
3638
  line.classList.remove( 'active-line' );
3547
3639
 
package/build/lexgui.css CHANGED
@@ -131,7 +131,7 @@ body.nocursor * {
131
131
  #global_search {
132
132
  position: absolute;
133
133
  background-color: var(--global-blur-background);
134
- -webkit-backdrop-filter: blur(12px);
134
+ --webkit-backdrop-filter: blur(12px);
135
135
  backdrop-filter: blur(12px);
136
136
  border-radius: 8px;
137
137
  border: 1px solid #d0d0ec6b;
@@ -142,6 +142,7 @@ body.nocursor * {
142
142
  flex-wrap: wrap;
143
143
  z-index: 105;
144
144
  -webkit-mask-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAA5JREFUeNpiYGBgAAgwAAAEAAGbA+oJAAAAAElFTkSuQmCC); /* this fixes the overflow:hidden in Chrome/Opera */
145
+ mask-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAA5JREFUeNpiYGBgAAgwAAAEAAGbA+oJAAAAAElFTkSuQmCC); /* this fixes the overflow:hidden in Chrome/Opera */
145
146
  /* box-shadow: 0 2px 10px black; */
146
147
  }
147
148
 
@@ -1195,6 +1196,7 @@ input::-webkit-inner-spin-button {
1195
1196
 
1196
1197
  /* Firefox */
1197
1198
  input[type=number] {
1199
+ appearance: textfield;
1198
1200
  -moz-appearance: textfield;
1199
1201
  }
1200
1202
 
@@ -2732,8 +2734,33 @@ ul.lexassetscontent {
2732
2734
  letter-spacing: 0em !important;
2733
2735
  }
2734
2736
 
2735
- .codebasearea .lexareatab {
2737
+ .codebasearea .lexareatabs {
2736
2738
  padding: 0px;
2739
+ margin: 0px;
2740
+ }
2741
+
2742
+ .codebasearea .lexareatab {
2743
+ padding: 5px;
2744
+ border-radius: 0px !important;
2745
+ margin: 0px !important;
2746
+ border: 1px solid #91909036;
2747
+ border-right: none;
2748
+ background-color: var(--global-color-primary) !important;
2749
+ transition: none;
2750
+ }
2751
+
2752
+ .codebasearea .lexareatab:hover {
2753
+ background-color: var(--global-color-secondary) !important;
2754
+ }
2755
+
2756
+ .codebasearea .lexareatab:last-child {
2757
+ border-right: 1px solid #91909036;
2758
+ }
2759
+
2760
+ .codebasearea .lexareatab.selected {
2761
+ background-color: var(--global-color-secondary) !important;
2762
+ border-top: 1px solid var(--global-selected);
2763
+ border-bottom: none;
2737
2764
  }
2738
2765
 
2739
2766
  .codebasearea .lexareatab i {
@@ -2792,7 +2819,7 @@ ul.lexassetscontent {
2792
2819
  width: 48px;
2793
2820
  height: calc(100% - 62px);
2794
2821
  background-color: var(--global-branch-darker);
2795
- margin-top: 26px;
2822
+ margin-top: 28px;
2796
2823
  overflow: hidden;
2797
2824
  position: absolute;
2798
2825
  }
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.22",
15
+ version: "0.1.23",
16
16
  ready: false,
17
17
  components: [], // specific pre-build components
18
18
  signals: {} // events and triggers
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  var LX = {
11
- version: "0.1.22",
11
+ version: "0.1.23",
12
12
  ready: false,
13
13
  components: [], // specific pre-build components
14
14
  signals: {} // events and triggers
package/changelog.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # lexgui.js changelog
2
2
 
3
+ ## 0.1.23
4
+
5
+ Begin integration of multiple cursors ("Ctrl+ArrowDown").
6
+ Code tabs have new VS Code-alike style.
7
+ Improved CSS highlighting.
8
+ Add Undo Steps to some actions that were missing.
9
+ When using Ctrl+G, the selected line is now highlighted.
10
+ General bug fixes.
11
+
3
12
  ## 0.1.22
4
13
 
5
14
  Added REDO using "Ctrl+Y".
@@ -2,6 +2,7 @@
2
2
  <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
3
3
  <head>
4
4
  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
5
6
  <title>LexGUI Code Editor Demo</title>
6
7
  <link rel="stylesheet" href="../build/lexgui.css">
7
8
  <link rel="icon" href="../images/icon.png">
@@ -20,14 +21,16 @@
20
21
  import { LX } from 'lexgui';
21
22
  import 'lexgui/components/codeeditor.js';
22
23
 
24
+ window.LX = LX;
25
+
23
26
  // init library and get main area
24
27
  let area = LX.init();
25
28
 
26
- const file_explorer = true;
29
+ const file_explorer = false;
27
30
 
28
31
  if( !file_explorer )
29
32
  {
30
- var [leftArea, rightArea] = area.split({ sizes:["55%","45%"] });
33
+ var [leftArea, rightArea] = area.split({ sizes:["40%","60%"] });
31
34
 
32
35
  var canvas = document.createElement('canvas');
33
36
  canvas.id = "mycanvas";
@@ -52,13 +55,13 @@
52
55
  file_explorer: file_explorer
53
56
  });
54
57
 
55
- editor.loadFile( "../data/js_sample.js" );
56
- editor.loadFile( "../data/json_sample.json" );
57
- editor.loadFile( "../data/css_sample.css" );
58
- editor.loadFile( "../data/cpp_sample.cpp" );
59
- editor.loadFile( "../data/xml_sample.xml" );
60
- editor.loadFile( "../data/python_sample.py" );
61
- editor.loadFile( "../data/rust_sample.rs" );
58
+ editor.loadFile( "../demo.js" );
59
+ // editor.loadFile( "../data/json_sample.json" );
60
+ // editor.loadFile( "../data/css_sample.css" );
61
+ // editor.loadFile( "../data/cpp_sample.cpp" );
62
+ // editor.loadFile( "../data/xml_sample.xml" );
63
+ // editor.loadFile( "../data/python_sample.py" );
64
+ // editor.loadFile( "../data/rust_sample.rs" );
62
65
  // editor.loadFile( "../localhost.bat" );
63
66
 
64
67
  if( !file_explorer )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lexgui",
3
- "version": "0.1.22",
3
+ "version": "0.1.23",
4
4
  "description": "JS library to create web graphical user interfaces",
5
5
  "type": "module",
6
6
  "main": "./build/lexgui.js",