lexgui 0.7.6 → 0.7.8

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.
@@ -347,6 +347,20 @@ class CodeEditor {
347
347
  this.skipTabs = options.skipTabs ?? false;
348
348
  this.useFileExplorer = ( options.fileExplorer ?? false ) && !this.skipTabs;
349
349
  this.useAutoComplete = options.autocomplete ?? true;
350
+ this.allowClosingTabs = options.allowClosingTabs ?? true;
351
+ this.allowLoadingFiles = options.allowLoadingFiles ?? true;
352
+ this.highlight = options.highlight ?? 'Plain Text';
353
+ this.newTabOptions = options.newTabOptions;
354
+ this.customSuggestions = options.customSuggestions ?? [];
355
+
356
+ // Editor callbacks
357
+ this.onSave = options.onSave ?? options.onsave; // LEGACY onsave
358
+ this.onRun = options.onRun ?? options.onrun; // LEGACY onrun
359
+ this.onCtrlSpace = options.onCtrlSpace;
360
+ this.onCreateStatusPanel = options.onCreateStatusPanel;
361
+ this.onContextMenu = options.onContextMenu;
362
+ this.onNewTab = options.onNewTab;
363
+ this.onSelectTab = options.onSelectTab;
350
364
 
351
365
  // File explorer
352
366
  if( this.useFileExplorer )
@@ -377,8 +391,7 @@ class CodeEditor {
377
391
  this.loadTab( event.node.id );
378
392
  break;
379
393
  case LX.TreeEvent.NODE_DELETED:
380
- this.tabs.delete( event.node.id );
381
- delete this.loadedTabs[ event.node.id ];
394
+ this.closeTab( event.node.id );
382
395
  break;
383
396
  // case LX.TreeEvent.NODE_CONTEXTMENU:
384
397
  // LX.addContextMenu( event.multiple ? "Selected Nodes" : event.node.id, event.value, m => {
@@ -547,36 +560,44 @@ class CodeEditor {
547
560
  return;
548
561
  }
549
562
 
550
- this.setScrollBarValue( 'vertical' );
563
+ // Vertical scroll
564
+ {
565
+ this.setScrollBarValue( 'vertical' );
551
566
 
552
- const scrollTop = this.getScrollTop();
567
+ const scrollTop = this.getScrollTop();
553
568
 
554
- // Scroll down...
555
- if( scrollTop > lastScrollTopValue )
556
- {
557
- if( this.visibleLinesViewport.y < (this.code.lines.length - 1) )
569
+ // Scroll down...
570
+ if( scrollTop > lastScrollTopValue )
558
571
  {
559
- const totalLinesInViewport = ( ( this.codeScroller.offsetHeight ) / this.lineHeight )|0;
560
- const scrollDownBoundary =
561
- ( Math.max( this.visibleLinesViewport.y - totalLinesInViewport, 0 ) - 1 ) * this.lineHeight;
562
-
563
- if( scrollTop >= scrollDownBoundary )
572
+ if( this.visibleLinesViewport.y < (this.code.lines.length - 1) )
564
573
  {
565
- this.processLines( CodeEditor.UPDATE_VISIBLE_LINES );
574
+ const totalLinesInViewport = ( ( this.codeScroller.offsetHeight ) / this.lineHeight )|0;
575
+ const scrollDownBoundary =
576
+ ( Math.max( this.visibleLinesViewport.y - totalLinesInViewport, 0 ) - 1 ) * this.lineHeight;
577
+
578
+ if( scrollTop >= scrollDownBoundary )
579
+ {
580
+ this.processLines( CodeEditor.UPDATE_VISIBLE_LINES );
581
+ }
566
582
  }
567
583
  }
568
- }
569
- // Scroll up...
570
- else
571
- {
572
- const scrollUpBoundary = parseInt( this.code.style.top );
573
- if( scrollTop < scrollUpBoundary )
584
+ // Scroll up...
585
+ else
574
586
  {
575
- this.processLines( CodeEditor.UPDATE_VISIBLE_LINES );
587
+ const scrollUpBoundary = parseInt( this.code.style.top );
588
+ if( scrollTop < scrollUpBoundary )
589
+ {
590
+ this.processLines( CodeEditor.UPDATE_VISIBLE_LINES );
591
+ }
576
592
  }
593
+
594
+ lastScrollTopValue = scrollTop;
577
595
  }
578
596
 
579
- lastScrollTopValue = scrollTop;
597
+ // Horizontal scroll
598
+ {
599
+ this.setScrollBarValue( 'horizontal' );
600
+ }
580
601
  });
581
602
 
582
603
  this.codeScroller.addEventListener( 'wheel', e => {
@@ -651,16 +672,18 @@ class CodeEditor {
651
672
 
652
673
  // Add search LINE box
653
674
  {
654
- var box = document.createElement( 'div' );
655
- box.className = "searchbox gotoline";
675
+ const box = document.createElement( 'div' );
676
+ box.className = "searchbox";
656
677
 
657
- var searchPanel = new LX.Panel();
678
+ const searchPanel = new LX.Panel();
658
679
  box.appendChild( searchPanel.root );
659
680
 
681
+ searchPanel.sameLine( 2 );
660
682
  searchPanel.addText( null, "", ( value, event ) => {
661
683
  input.value = ":" + value.replaceAll( ':', '' );
662
684
  this.goToLine( input.value.slice( 1 ) );
663
685
  }, { placeholder: "Go to line", trigger: "input" } );
686
+ searchPanel.addButton( null, "x", this.hideSearchLineBox.bind( this ), { icon: "X", title: "Close", tooltip: true } );
664
687
 
665
688
  let input = box.querySelector( 'input' );
666
689
  input.addEventListener( 'keyup', e => {
@@ -697,9 +720,6 @@ class CodeEditor {
697
720
 
698
721
  // Code
699
722
 
700
- this.highlight = options.highlight ?? 'Plain Text';
701
- this.onsave = options.onsave ?? ((code) => { console.log( code, "save" ) });
702
- this.onrun = options.onrun ?? ((code) => { this.runScript(code) });
703
723
  this.actions = {};
704
724
  this.cursorBlinkRate = 550;
705
725
  this.tabSpaces = 4;
@@ -784,14 +804,14 @@ class CodeEditor {
784
804
  var numCharsDeleted = 1;
785
805
 
786
806
  // Delete full word
787
- if( e.shiftKey )
807
+ if( e.ctrlKey )
788
808
  {
789
809
  const [word, from, to] = this.getWordAtPos( cursor, -1 );
790
810
 
791
811
  if( word.length > 1 )
792
812
  {
793
813
  deleteFromPosition = from;
794
- numCharsDeleted = word.length;
814
+ numCharsDeleted = word.length - ( to - cursor.position );
795
815
  }
796
816
  }
797
817
 
@@ -966,7 +986,7 @@ class CodeEditor {
966
986
 
967
987
  if( e.ctrlKey )
968
988
  {
969
- this.onrun( this.getText() );
989
+ this.onRun( this.getText() );
970
990
  return;
971
991
  }
972
992
 
@@ -1149,7 +1169,7 @@ class CodeEditor {
1149
1169
  if( e.metaKey ) // Apple devices (Command)
1150
1170
  {
1151
1171
  e.preventDefault();
1152
- this.actions[ 'End' ].callback( ln, cursor );
1172
+ this.actions[ 'End' ].callback( ln, cursor, e );
1153
1173
  }
1154
1174
  else if( e.ctrlKey ) // Next word
1155
1175
  {
@@ -1290,7 +1310,7 @@ class CodeEditor {
1290
1310
  for( let url of options.files )
1291
1311
  {
1292
1312
  const finalUrl = url.constructor === Array ? url[ 0 ] : url;
1293
- const finalFileName = url.constructor === Array ? url[ 1 ] : url;
1313
+ const finalFileName = url.constructor === Array ? url[ 1 ] : undefined;
1294
1314
 
1295
1315
  await this.loadFile( finalUrl, { filename: finalFileName, async: loadAsync, callback: ( name, text ) => {
1296
1316
  filesLoaded++;
@@ -1300,17 +1320,28 @@ class CodeEditor {
1300
1320
 
1301
1321
  if( options.onFilesLoaded )
1302
1322
  {
1303
- options.onFilesLoaded( this.loadedTabs, numFiles );
1323
+ options.onFilesLoaded( this, this.loadedTabs, numFiles );
1304
1324
  }
1305
1325
  }
1306
1326
  }});
1307
1327
  }
1308
1328
  }
1309
- else
1329
+ else if( options.defaultTab ?? true )
1310
1330
  {
1311
1331
  this.addTab( options.name || "untitled", true, options.title, { language: options.highlight ?? "Plain Text" } );
1312
1332
  onLoadAll();
1313
1333
  }
1334
+ else
1335
+ {
1336
+ onLoadAll();
1337
+ }
1338
+ }
1339
+
1340
+ // Clear signals
1341
+ clear() {
1342
+ console.assert( this.rightStatusPanel && this.leftStatusPanel, "No panels to clear." );
1343
+ this.rightStatusPanel.clear();
1344
+ this.leftStatusPanel.clear();
1314
1345
  }
1315
1346
 
1316
1347
  static getInstances()
@@ -1711,7 +1742,12 @@ class CodeEditor {
1711
1742
 
1712
1743
  let panel = new LX.Panel({ className: "lexcodetabinfo flex flex-row", height: "auto" });
1713
1744
 
1714
- let leftStatusPanel = new LX.Panel( { id: "FontSizeZoomStatusComponent", height: "auto" } );
1745
+ if( this.onCreateStatusPanel )
1746
+ {
1747
+ this.onCreateStatusPanel( panel, this );
1748
+ }
1749
+
1750
+ let leftStatusPanel = this.leftStatusPanel = new LX.Panel( { id: "FontSizeZoomStatusComponent", height: "auto" } );
1715
1751
  leftStatusPanel.sameLine();
1716
1752
 
1717
1753
  if( this.skipTabs )
@@ -1725,7 +1761,7 @@ class CodeEditor {
1725
1761
  leftStatusPanel.endLine( "justify-start" );
1726
1762
  panel.attach( leftStatusPanel.root );
1727
1763
 
1728
- let rightStatusPanel = new LX.Panel( { height: "auto" } );
1764
+ let rightStatusPanel = this.rightStatusPanel = new LX.Panel( { height: "auto" } );
1729
1765
  rightStatusPanel.sameLine();
1730
1766
  rightStatusPanel.addLabel( this.code?.title ?? "", { id: "EditorFilenameStatusComponent", fit: true, signal: "@tab-name" });
1731
1767
  rightStatusPanel.addButton( null, "Ln 1, Col 1", this.showSearchLineBox.bind( this ), { id: "EditorSelectionStatusComponent", fit: true, signal: "@cursor-data" });
@@ -1856,10 +1892,18 @@ class CodeEditor {
1856
1892
 
1857
1893
  this.processFocus( false );
1858
1894
 
1859
- new LX.DropdownMenu( e.target, [
1895
+ if( this.onNewTab )
1896
+ {
1897
+ this.onNewTab( e );
1898
+ return;
1899
+ }
1900
+
1901
+ const dmOptions = this.newTabOptions ?? [
1860
1902
  { name: "Create file", icon: "FilePlus", callback: this._onCreateNewFile.bind( this ) },
1861
- { name: "Load file", icon: "FileUp", callback: this.loadTabFromFile.bind( this ) },
1862
- ], { side: "bottom", align: "start" });
1903
+ { name: "Load file", icon: "FileUp", disabled: !this.allowLoadingFiles, callback: this.loadTabFromFile.bind( this ) }
1904
+ ];
1905
+
1906
+ new LX.DropdownMenu( e.target, dmOptions, { side: "bottom", align: "start" });
1863
1907
  }
1864
1908
 
1865
1909
  _onCreateNewFile() {
@@ -1869,10 +1913,14 @@ class CodeEditor {
1869
1913
  if( this.onCreateFile )
1870
1914
  {
1871
1915
  options = this.onCreateFile( this );
1916
+ if( !options ) // Skip adding new file
1917
+ {
1918
+ return;
1919
+ }
1872
1920
  }
1873
1921
 
1874
1922
  const name = options.name ?? "unnamed.js";
1875
- this.addTab( name, true, name, { language: options.language ?? "JavaScript" } );
1923
+ this.addTab( name, true, name, { indexOffset: options.indexOffset, language: options.language ?? "JavaScript" } );
1876
1924
  }
1877
1925
 
1878
1926
  _onSelectTab( isNewTabButton, event, name ) {
@@ -1890,9 +1938,16 @@ class CodeEditor {
1890
1938
 
1891
1939
  this._removeSecondaryCursors();
1892
1940
 
1893
- var cursor = this.getCurrentCursor( true );
1894
- this.saveCursor( cursor, this.code.cursorState );
1941
+ const cursor = this.getCurrentCursor( true );
1942
+ const lastCode = this.code;
1943
+
1944
+ if( lastCode )
1945
+ {
1946
+ this.saveCursor( cursor, lastCode.cursorState );
1947
+ }
1948
+
1895
1949
  this.code = this.loadedTabs[ name ];
1950
+
1896
1951
  this.restoreCursor( cursor, this.code.cursorState );
1897
1952
 
1898
1953
  this.endSelection();
@@ -1911,6 +1966,11 @@ class CodeEditor {
1911
1966
  }
1912
1967
 
1913
1968
  this.processLines();
1969
+
1970
+ if( !isNewTabButton && this.onSelectTab )
1971
+ {
1972
+ this.onSelectTab( name, this );
1973
+ }
1914
1974
  }
1915
1975
 
1916
1976
  _onContextMenuTab( isNewTabButton, event, name, ) {
@@ -1921,19 +1981,19 @@ class CodeEditor {
1921
1981
  }
1922
1982
 
1923
1983
  new LX.DropdownMenu( event.target, [
1924
- { name: "Close", kbd: "MWB", callback: () => { this.tabs.delete( name ) } },
1925
- { name: "Close Others", callback: () => {
1984
+ { name: "Close", kbd: "MWB", disabled: !this.allowClosingTabs, callback: () => { this.closeTab( name ) } },
1985
+ { name: "Close Others", disabled: !this.allowClosingTabs, callback: () => {
1926
1986
  for( const [ key, data ] of Object.entries( this.tabs.tabs ) )
1927
1987
  {
1928
1988
  if( key === '+' || key === name ) continue;
1929
- this.tabs.delete( key )
1989
+ this.closeTab( key )
1930
1990
  }
1931
1991
  } },
1932
- { name: "Close All", callback: () => {
1992
+ { name: "Close All", disabled: !this.allowClosingTabs, callback: () => {
1933
1993
  for( const [ key, data ] of Object.entries( this.tabs.tabs ) )
1934
1994
  {
1935
1995
  if( key === '+' ) continue;
1936
- this.tabs.delete( key )
1996
+ this.closeTab( key )
1937
1997
  }
1938
1998
  } },
1939
1999
  null,
@@ -1998,6 +2058,10 @@ class CodeEditor {
1998
2058
  this.loadedTabs[ name ] = code;
1999
2059
  this.openedTabs[ name ] = code;
2000
2060
 
2061
+ const lastCode = this.code;
2062
+
2063
+ this.code = code;
2064
+
2001
2065
  const tabIcon = this._getFileIcon( name );
2002
2066
 
2003
2067
  if( this.useFileExplorer && !isNewTabButton )
@@ -2015,7 +2079,8 @@ class CodeEditor {
2015
2079
  icon: tabIcon,
2016
2080
  onSelect: this._onSelectTab.bind( this, isNewTabButton ),
2017
2081
  onContextMenu: this._onContextMenuTab.bind( this, isNewTabButton ),
2018
- allowDelete: true
2082
+ allowDelete: this.allowClosingTabs,
2083
+ indexOffset: options.indexOffset
2019
2084
  } );
2020
2085
  }
2021
2086
 
@@ -2024,20 +2089,29 @@ class CodeEditor {
2024
2089
 
2025
2090
  this.endSelection();
2026
2091
 
2027
- if( selected )
2092
+ if( options.language )
2028
2093
  {
2029
- this.code = code;
2030
- this.resetCursorPos( CodeEditor.CURSOR_LEFT_TOP );
2094
+ code.languageOverride = options.language;
2095
+ this._changeLanguage( code.languageOverride );
2031
2096
  this.mustProcessLines = true;
2032
2097
  }
2033
2098
 
2034
- if( options.language )
2099
+ if( options.codeLines )
2035
2100
  {
2036
- code.languageOverride = options.language;
2037
- this._changeLanguage( code.languageOverride );
2101
+ code.lines = options.codeLines;
2038
2102
  this.mustProcessLines = true;
2039
2103
  }
2040
2104
 
2105
+ if( selected )
2106
+ {
2107
+ this.resetCursorPos( CodeEditor.CURSOR_LEFT_TOP );
2108
+ this.mustProcessLines = true;
2109
+ }
2110
+ else
2111
+ {
2112
+ this.code = lastCode;
2113
+ }
2114
+
2041
2115
  this._processLinesIfNecessary();
2042
2116
 
2043
2117
  this._updateDataInfoPanel( "@tab-name", name );
@@ -2155,7 +2229,7 @@ class CodeEditor {
2155
2229
  icon: tabIcon,
2156
2230
  onSelect: this._onSelectTab.bind( this, isNewTabButton ),
2157
2231
  onContextMenu: this._onContextMenuTab.bind( this, isNewTabButton ),
2158
- allowDelete: true
2232
+ allowDelete: this.allowClosingTabs
2159
2233
  });
2160
2234
 
2161
2235
  // Move into the sizer..
@@ -2173,6 +2247,11 @@ class CodeEditor {
2173
2247
 
2174
2248
  closeTab( name, eraseAll ) {
2175
2249
 
2250
+ if( !this.allowClosingTabs )
2251
+ {
2252
+ return;
2253
+ }
2254
+
2176
2255
  this.tabs.delete( name );
2177
2256
 
2178
2257
  if( eraseAll )
@@ -2290,22 +2369,65 @@ class CodeEditor {
2290
2369
  e.preventDefault();
2291
2370
 
2292
2371
  if( !this.canOpenContextMenu )
2372
+ {
2293
2373
  return;
2374
+ }
2294
2375
 
2295
2376
  LX.addContextMenu( null, e, m => {
2296
2377
  m.add( "Copy", () => { this._copyContent( cursor ); } );
2378
+
2297
2379
  if( !this.disableEdition )
2298
2380
  {
2299
2381
  m.add( "Cut", () => { this._cutContent( cursor ); } );
2300
2382
  m.add( "Paste", () => { this._pasteContent( cursor ); } );
2383
+ }
2384
+
2385
+ if( !this.onContextMenu )
2386
+ {
2387
+ return;
2388
+ }
2389
+
2390
+ let content = null;
2391
+
2392
+ if( cursor.selection )
2393
+ {
2394
+ // Some selections don't depend on mouse up..
2395
+ if( cursor.selection ) cursor.selection.invertIfNecessary();
2396
+
2397
+ const separator = "_NEWLINE_";
2398
+ let code = this.code.lines.join( separator );
2399
+
2400
+ // Get linear start index
2401
+ let index = 0;
2402
+
2403
+ for( let i = 0; i <= cursor.selection.fromY; i++ )
2404
+ {
2405
+ index += ( i == cursor.selection.fromY ? cursor.selection.fromX : this.code.lines[ i ].length );
2406
+ }
2407
+
2408
+ index += cursor.selection.fromY * separator.length;
2409
+ const num_chars = cursor.selection.chars + ( cursor.selection.toY - cursor.selection.fromY ) * separator.length;
2410
+ const text = code.substr( index, num_chars );
2411
+ content = text.split( separator ).join('\n');
2412
+ }
2413
+
2414
+ const options = this.onContextMenu( this, content, e );
2415
+ if( options.length )
2416
+ {
2301
2417
  m.add( "" );
2302
- m.add( "Format/JSON", () => {
2303
- let json = this.toJSONFormat( this.getText() );
2304
- if( !json )
2305
- return;
2306
- this.code.lines = json.split( "\n" );
2307
- this.processLines();
2308
- } );
2418
+
2419
+ for( const o of options )
2420
+ {
2421
+ m.add( o.path, { disabled: o.disabled, callback: o.callback } );
2422
+ }
2423
+
2424
+ // m.add( "Format/JSON", () => {
2425
+ // let json = this.toJSONFormat( this.getText() );
2426
+ // if( !json )
2427
+ // return;
2428
+ // this.code.lines = json.split( "\n" );
2429
+ // this.processLines();
2430
+ // } );
2309
2431
  }
2310
2432
  });
2311
2433
 
@@ -2638,10 +2760,17 @@ class CodeEditor {
2638
2760
  e.preventDefault();
2639
2761
  this.selectAll();
2640
2762
  return true;
2763
+ case 'b': // k+b comment block
2764
+ e.preventDefault();
2765
+ if( this.state.keyChain == 'k' ) {
2766
+ this._commentLines( cursor, true );
2767
+ return true;
2768
+ }
2769
+ return false;
2641
2770
  case 'c': // k+c, comment line
2642
2771
  e.preventDefault();
2643
2772
  if( this.state.keyChain == 'k' ) {
2644
- this._commentLines();
2773
+ this._commentLines( cursor );
2645
2774
  return true;
2646
2775
  }
2647
2776
  return false;
@@ -2663,12 +2792,12 @@ class CodeEditor {
2663
2792
  return true;
2664
2793
  case 's': // save
2665
2794
  e.preventDefault();
2666
- this.onsave( this.getText() );
2795
+ this.onSave( this.getText() );
2667
2796
  return true;
2668
2797
  case 'u': // k+u, uncomment line
2669
2798
  e.preventDefault();
2670
2799
  if( this.state.keyChain == 'k' ) {
2671
- this._uncommentLines();
2800
+ this._uncommentLines( cursor );
2672
2801
  return true;
2673
2802
  }
2674
2803
  return false;
@@ -2688,6 +2817,13 @@ class CodeEditor {
2688
2817
  e.preventDefault();
2689
2818
  this._decreaseFontSize();
2690
2819
  return true;
2820
+ case ' ': // custom event
2821
+ if( this.onCtrlSpace )
2822
+ {
2823
+ e.preventDefault();
2824
+ this.onCtrlSpace( cursor );
2825
+ return true;
2826
+ }
2691
2827
  }
2692
2828
  }
2693
2829
 
@@ -2981,32 +3117,77 @@ class CodeEditor {
2981
3117
  this.hideAutoCompleteBox();
2982
3118
  }
2983
3119
 
2984
- _commentLines() {
3120
+ _commentLines( cursor, useCommentBlock ) {
3121
+
3122
+ const lang = CodeEditor.languages[ this.highlight ];
2985
3123
 
2986
3124
  this.state.keyChain = null;
2987
3125
 
3126
+ cursor = cursor ?? this.getCurrentCursor();
3127
+
2988
3128
  if( cursor.selection )
2989
3129
  {
2990
- var cursor = this.getCurrentCursor();
3130
+ if( !( ( useCommentBlock ? lang.blockComments : lang.singleLineComments ) ?? true ) )
3131
+ {
3132
+ return;
3133
+ }
3134
+
2991
3135
  this._addUndoStep( cursor, true );
2992
3136
 
2993
- const selectedLines = this.code.lines.slice( cursor.selection.fromY, cursor.selection.toY );
3137
+ const selectedLines = this.code.lines.slice( cursor.selection.fromY, cursor.selection.toY + 1 );
2994
3138
  const minIdx = Math.min(...selectedLines.map( v => {
2995
3139
  var idx = firstNonspaceIndex( v );
2996
3140
  return idx < 0 ? 1e10 : idx;
2997
3141
  } ));
2998
3142
 
2999
- for( var i = cursor.selection.fromY; i <= cursor.selection.toY; ++i )
3143
+ if( useCommentBlock )
3000
3144
  {
3001
- this._commentLine( cursor, i, minIdx );
3145
+ const tokens = ( lang.blockCommentsTokens ?? this.defaultBlockCommentTokens );
3146
+
3147
+ const fromString = this.code.lines[ cursor.selection.fromY ];
3148
+ let fromIdx = firstNonspaceIndex( fromString );
3149
+ if( fromIdx == -1 )
3150
+ {
3151
+ fromIdx = 0;
3152
+ }
3153
+
3154
+ this.code.lines[ cursor.selection.fromY ] = [
3155
+ fromString.substring( 0, fromIdx ),
3156
+ tokens[ 0 ] + " ",
3157
+ fromString.substring( fromIdx )
3158
+ ].join( '' );
3159
+
3160
+ this.code.lines[ cursor.selection.toY ] += " " + tokens[ 1 ];
3161
+
3162
+ cursor.selection.fromX += ( tokens[ 0 ].length + 1 );
3163
+ this._processSelection( cursor );
3164
+ }
3165
+ else
3166
+ {
3167
+ for( let i = cursor.selection.fromY; i <= cursor.selection.toY; ++i )
3168
+ {
3169
+ this._commentLine( cursor, i, minIdx, false );
3170
+ }
3171
+
3172
+ const token = ( lang.singleLineCommentToken ?? this.defaultSingleLineCommentToken ) + ' ';
3173
+ this.cursorToString( cursor, token );
3174
+
3175
+ cursor.selection.fromX += token.length;
3176
+ cursor.selection.toX += token.length;
3177
+ this._processSelection( cursor );
3002
3178
  }
3003
3179
  }
3004
3180
  else
3005
3181
  {
3006
- for( let cursor of this.cursors.children )
3182
+ if( !( lang.singleLineComments ?? true ) )
3007
3183
  {
3008
- this._addUndoStep( cursor, true );
3009
- this._commentLine( cursor, cursor.line );
3184
+ return;
3185
+ }
3186
+
3187
+ for( const cr of this.cursors.children )
3188
+ {
3189
+ this._addUndoStep( cr, true );
3190
+ this._commentLine( cr, cr.line );
3010
3191
  }
3011
3192
  }
3012
3193
 
@@ -3014,33 +3195,37 @@ class CodeEditor {
3014
3195
  this._hideActiveLine();
3015
3196
  }
3016
3197
 
3017
- _commentLine( cursor, line, minNonspaceIdx ) {
3198
+ _commentLine( cursor, line, minNonspaceIdx, updateCursor = true ) {
3018
3199
 
3019
3200
  const lang = CodeEditor.languages[ this.highlight ];
3020
-
3021
- if( !( lang.singleLineComments ?? true ))
3201
+ if( !( lang.singleLineComments ?? true ) )
3022
3202
  return;
3023
3203
 
3024
3204
  const token = ( lang.singleLineCommentToken ?? this.defaultSingleLineCommentToken ) + ' ';
3025
3205
  const string = this.code.lines[ line ];
3026
3206
 
3027
3207
  let idx = firstNonspaceIndex( string );
3028
- if( idx > -1 )
3208
+ if( idx == -1 )
3029
3209
  {
3030
- // Update idx using min of the selected lines (if necessary..)
3031
- idx = minNonspaceIdx ?? idx;
3210
+ return;
3211
+ }
3032
3212
 
3033
- this.code.lines[ line ] = [
3034
- string.substring( 0, idx ),
3035
- token,
3036
- string.substring( idx )
3037
- ].join( '' );
3213
+ // Update idx using min of the selected lines (if necessary..)
3214
+ idx = minNonspaceIdx ?? idx;
3215
+
3216
+ this.code.lines[ line ] = [
3217
+ string.substring( 0, idx ),
3218
+ token,
3219
+ string.substring( idx )
3220
+ ].join( '' );
3038
3221
 
3222
+ if( updateCursor )
3223
+ {
3039
3224
  this.cursorToString( cursor, token );
3040
3225
  }
3041
3226
  }
3042
3227
 
3043
- _uncommentLines() {
3228
+ _uncommentLines( cursor ) {
3044
3229
 
3045
3230
  this.state.keyChain = null;
3046
3231
 
@@ -4002,7 +4187,7 @@ class CodeEditor {
4002
4187
  const customStringKeys = Object.assign( {}, this.stringKeys );
4003
4188
  const lineNumber = this._currentLineNumber;
4004
4189
  const tokenStartIndex = this._currentTokenPositions[ tokenIndex ];
4005
- const inBlockComment = ( this._buildingBlockComment ?? this._inBlockCommentSection( lineNumber, tokenStartIndex, token.length ) !== undefined )
4190
+ const inBlockComment = ( this._buildingBlockComment ?? this._inBlockCommentSection( lineNumber, tokenStartIndex, token.length ) ) !== undefined;
4006
4191
 
4007
4192
  var usePreviousTokenToCheckString = false;
4008
4193
 
@@ -4619,6 +4804,8 @@ class CodeEditor {
4619
4804
 
4620
4805
  if( state.selection )
4621
4806
  {
4807
+ this.endSelection();
4808
+
4622
4809
  this.startSelection( cursor );
4623
4810
 
4624
4811
  cursor.selection.load( state.selection );
@@ -4899,7 +5086,10 @@ class CodeEditor {
4899
5086
  }
4900
5087
  else
4901
5088
  {
4902
- this.codeScroller.scrollLeft += value;
5089
+ if( value )
5090
+ {
5091
+ this.codeScroller.scrollLeft += value;
5092
+ }
4903
5093
 
4904
5094
  const scrollBarWidth = this.hScrollbar.thumb.parentElement.offsetWidth;
4905
5095
  const scrollThumbWidth = this.hScrollbar.thumb.offsetWidth;
@@ -5109,6 +5299,9 @@ class CodeEditor {
5109
5299
  suggestions = suggestions.concat( otherValues.slice( 0, -1 ) );
5110
5300
  }
5111
5301
 
5302
+ // Add custom suggestions...
5303
+ suggestions = suggestions.concat( this.customSuggestions );
5304
+
5112
5305
  // Remove 1/2 char words and duplicates...
5113
5306
  suggestions = Array.from( new Set( suggestions )).filter( s => s.length > 2 && s.toLowerCase().includes( word.toLowerCase() ) );
5114
5307