lexgui 0.7.5 → 0.7.7

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.
@@ -319,6 +319,25 @@ class CodeEditor {
319
319
 
320
320
  constructor( area, options = {} ) {
321
321
 
322
+ if( options.filesAsync )
323
+ {
324
+ options.files = [ ...options.filesAsync ];
325
+
326
+ return (async () => {
327
+ await this._init( area, options );
328
+ // Constructors return `this` implicitly, but this is an IIFE, so
329
+ // return `this` explicitly (else we'd return an empty object).
330
+ return this;
331
+ })();
332
+ }
333
+ else
334
+ {
335
+ this._init( area, options );
336
+ }
337
+ }
338
+
339
+ async _init( area, options ) {
340
+
322
341
  window.editor = this;
323
342
 
324
343
  CodeEditor.__instances.push( this );
@@ -328,6 +347,19 @@ class CodeEditor {
328
347
  this.skipTabs = options.skipTabs ?? false;
329
348
  this.useFileExplorer = ( options.fileExplorer ?? false ) && !this.skipTabs;
330
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;
331
363
 
332
364
  // File explorer
333
365
  if( this.useFileExplorer )
@@ -358,8 +390,7 @@ class CodeEditor {
358
390
  this.loadTab( event.node.id );
359
391
  break;
360
392
  case LX.TreeEvent.NODE_DELETED:
361
- this.tabs.delete( event.node.id );
362
- delete this.loadedTabs[ event.node.id ];
393
+ this.closeTab( event.node.id );
363
394
  break;
364
395
  // case LX.TreeEvent.NODE_CONTEXTMENU:
365
396
  // LX.addContextMenu( event.multiple ? "Selected Nodes" : event.node.id, event.value, m => {
@@ -528,36 +559,44 @@ class CodeEditor {
528
559
  return;
529
560
  }
530
561
 
531
- this.setScrollBarValue( 'vertical' );
562
+ // Vertical scroll
563
+ {
564
+ this.setScrollBarValue( 'vertical' );
532
565
 
533
- const scrollTop = this.getScrollTop();
566
+ const scrollTop = this.getScrollTop();
534
567
 
535
- // Scroll down...
536
- if( scrollTop > lastScrollTopValue )
537
- {
538
- if( this.visibleLinesViewport.y < (this.code.lines.length - 1) )
568
+ // Scroll down...
569
+ if( scrollTop > lastScrollTopValue )
539
570
  {
540
- const totalLinesInViewport = ( ( this.codeScroller.offsetHeight ) / this.lineHeight )|0;
541
- const scrollDownBoundary =
542
- ( Math.max( this.visibleLinesViewport.y - totalLinesInViewport, 0 ) - 1 ) * this.lineHeight;
543
-
544
- if( scrollTop >= scrollDownBoundary )
571
+ if( this.visibleLinesViewport.y < (this.code.lines.length - 1) )
545
572
  {
546
- this.processLines( CodeEditor.UPDATE_VISIBLE_LINES );
573
+ const totalLinesInViewport = ( ( this.codeScroller.offsetHeight ) / this.lineHeight )|0;
574
+ const scrollDownBoundary =
575
+ ( Math.max( this.visibleLinesViewport.y - totalLinesInViewport, 0 ) - 1 ) * this.lineHeight;
576
+
577
+ if( scrollTop >= scrollDownBoundary )
578
+ {
579
+ this.processLines( CodeEditor.UPDATE_VISIBLE_LINES );
580
+ }
547
581
  }
548
582
  }
549
- }
550
- // Scroll up...
551
- else
552
- {
553
- const scrollUpBoundary = parseInt( this.code.style.top );
554
- if( scrollTop < scrollUpBoundary )
583
+ // Scroll up...
584
+ else
555
585
  {
556
- this.processLines( CodeEditor.UPDATE_VISIBLE_LINES );
586
+ const scrollUpBoundary = parseInt( this.code.style.top );
587
+ if( scrollTop < scrollUpBoundary )
588
+ {
589
+ this.processLines( CodeEditor.UPDATE_VISIBLE_LINES );
590
+ }
557
591
  }
592
+
593
+ lastScrollTopValue = scrollTop;
558
594
  }
559
595
 
560
- lastScrollTopValue = scrollTop;
596
+ // Horizontal scroll
597
+ {
598
+ this.setScrollBarValue( 'horizontal' );
599
+ }
561
600
  });
562
601
 
563
602
  this.codeScroller.addEventListener( 'wheel', e => {
@@ -678,9 +717,6 @@ class CodeEditor {
678
717
 
679
718
  // Code
680
719
 
681
- this.highlight = options.highlight ?? 'Plain Text';
682
- this.onsave = options.onsave ?? ((code) => { console.log( code, "save" ) });
683
- this.onrun = options.onrun ?? ((code) => { this.runScript(code) });
684
720
  this.actions = {};
685
721
  this.cursorBlinkRate = 550;
686
722
  this.tabSpaces = 4;
@@ -765,14 +801,14 @@ class CodeEditor {
765
801
  var numCharsDeleted = 1;
766
802
 
767
803
  // Delete full word
768
- if( e.shiftKey )
804
+ if( e.ctrlKey )
769
805
  {
770
806
  const [word, from, to] = this.getWordAtPos( cursor, -1 );
771
807
 
772
808
  if( word.length > 1 )
773
809
  {
774
810
  deleteFromPosition = from;
775
- numCharsDeleted = word.length;
811
+ numCharsDeleted = word.length - ( to - cursor.position );
776
812
  }
777
813
  }
778
814
 
@@ -947,7 +983,7 @@ class CodeEditor {
947
983
 
948
984
  if( e.ctrlKey )
949
985
  {
950
- this.onrun( this.getText() );
986
+ this.onRun( this.getText() );
951
987
  return;
952
988
  }
953
989
 
@@ -1130,7 +1166,7 @@ class CodeEditor {
1130
1166
  if( e.metaKey ) // Apple devices (Command)
1131
1167
  {
1132
1168
  e.preventDefault();
1133
- this.actions[ 'End' ].callback( ln, cursor );
1169
+ this.actions[ 'End' ].callback( ln, cursor, e );
1134
1170
  }
1135
1171
  else if( e.ctrlKey ) // Next word
1136
1172
  {
@@ -1259,7 +1295,6 @@ class CodeEditor {
1259
1295
  if( options.allowAddScripts ?? true )
1260
1296
  {
1261
1297
  this.onCreateFile = options.onCreateFile;
1262
-
1263
1298
  this.addTab( "+", false, "Create file" );
1264
1299
  }
1265
1300
 
@@ -1267,11 +1302,14 @@ class CodeEditor {
1267
1302
  {
1268
1303
  console.assert( options.files.constructor === Array, "_files_ must be an Array!" );
1269
1304
  const numFiles = options.files.length;
1305
+ const loadAsync = ( options.filesAsync !== undefined );
1270
1306
  let filesLoaded = 0;
1271
-
1272
1307
  for( let url of options.files )
1273
1308
  {
1274
- this.loadFile( url, { callback: ( name, text ) => {
1309
+ const finalUrl = url.constructor === Array ? url[ 0 ] : url;
1310
+ const finalFileName = url.constructor === Array ? url[ 1 ] : undefined;
1311
+
1312
+ await this.loadFile( finalUrl, { filename: finalFileName, async: loadAsync, callback: ( name, text ) => {
1275
1313
  filesLoaded++;
1276
1314
  if( filesLoaded == numFiles )
1277
1315
  {
@@ -1279,17 +1317,21 @@ class CodeEditor {
1279
1317
 
1280
1318
  if( options.onFilesLoaded )
1281
1319
  {
1282
- options.onFilesLoaded( this, numFiles );
1320
+ options.onFilesLoaded( this, this.loadedTabs, numFiles );
1283
1321
  }
1284
1322
  }
1285
1323
  }});
1286
1324
  }
1287
1325
  }
1288
- else
1326
+ else if( options.defaultTab ?? true )
1289
1327
  {
1290
1328
  this.addTab( options.name || "untitled", true, options.title, { language: options.highlight ?? "Plain Text" } );
1291
1329
  onLoadAll();
1292
1330
  }
1331
+ else
1332
+ {
1333
+ onLoadAll();
1334
+ }
1293
1335
  }
1294
1336
 
1295
1337
  static getInstances()
@@ -1421,15 +1463,12 @@ class CodeEditor {
1421
1463
  const _innerAddTab = ( text, name, title ) => {
1422
1464
 
1423
1465
  // Remove Carriage Return in some cases and sub tabs using spaces
1424
- text = text.replaceAll( '\r', '' );
1425
- text = text.replaceAll( /\t|\\t/g, ' '.repeat( this.tabSpaces ) );
1466
+ text = text.replaceAll( '\r', '' ).replaceAll( /\t|\\t/g, ' '.repeat( this.tabSpaces ) );
1426
1467
 
1427
1468
  // Set current text and language
1428
-
1429
1469
  const lines = text.split( '\n' );
1430
1470
 
1431
1471
  // Add item in the explorer if used
1432
-
1433
1472
  if( this.useFileExplorer || this.skipTabs )
1434
1473
  {
1435
1474
  this._tabStorage[ name ] = {
@@ -1465,13 +1504,20 @@ class CodeEditor {
1465
1504
 
1466
1505
  if( file.constructor == String )
1467
1506
  {
1468
- let filename = file;
1469
-
1470
- LX.request({ url: filename, success: text => {
1471
- const name = filename.substring(filename.lastIndexOf( '/' ) + 1);
1472
- _innerAddTab( text, name, filename );
1473
- } });
1507
+ const filename = file;
1508
+ const name = options.filename ?? filename.substring(filename.lastIndexOf( '/' ) + 1);
1474
1509
 
1510
+ if( options.async ?? false )
1511
+ {
1512
+ const text = await this._requestFileAsync( filename, "text" );
1513
+ _innerAddTab( text, name, options.filename ?? filename );
1514
+ }
1515
+ else
1516
+ {
1517
+ LX.request({ url: filename, success: text => {
1518
+ _innerAddTab( text, name, options.filename ?? filename );
1519
+ } });
1520
+ }
1475
1521
  }
1476
1522
  else // File Blob
1477
1523
  {
@@ -1686,6 +1732,11 @@ class CodeEditor {
1686
1732
 
1687
1733
  let panel = new LX.Panel({ className: "lexcodetabinfo flex flex-row", height: "auto" });
1688
1734
 
1735
+ if( this.onCreateStatusPanel )
1736
+ {
1737
+ this.onCreateStatusPanel( panel, this );
1738
+ }
1739
+
1689
1740
  let leftStatusPanel = new LX.Panel( { id: "FontSizeZoomStatusComponent", height: "auto" } );
1690
1741
  leftStatusPanel.sameLine();
1691
1742
 
@@ -1831,10 +1882,18 @@ class CodeEditor {
1831
1882
 
1832
1883
  this.processFocus( false );
1833
1884
 
1834
- new LX.DropdownMenu( e.target, [
1885
+ if( this.onNewTab )
1886
+ {
1887
+ this.onNewTab( e );
1888
+ return;
1889
+ }
1890
+
1891
+ const dmOptions = this.newTabOptions ?? [
1835
1892
  { name: "Create file", icon: "FilePlus", callback: this._onCreateNewFile.bind( this ) },
1836
- { name: "Load file", icon: "FileUp", callback: this.loadTabFromFile.bind( this ) },
1837
- ], { side: "bottom", align: "start" });
1893
+ { name: "Load file", icon: "FileUp", disabled: !this.allowLoadingFiles, callback: this.loadTabFromFile.bind( this ) }
1894
+ ];
1895
+
1896
+ new LX.DropdownMenu( e.target, dmOptions, { side: "bottom", align: "start" });
1838
1897
  }
1839
1898
 
1840
1899
  _onCreateNewFile() {
@@ -1844,10 +1903,14 @@ class CodeEditor {
1844
1903
  if( this.onCreateFile )
1845
1904
  {
1846
1905
  options = this.onCreateFile( this );
1906
+ if( !options ) // Skip adding new file
1907
+ {
1908
+ return;
1909
+ }
1847
1910
  }
1848
1911
 
1849
1912
  const name = options.name ?? "unnamed.js";
1850
- this.addTab( name, true, name, { language: options.language ?? "JavaScript" } );
1913
+ this.addTab( name, true, name, { indexOffset: options.indexOffset, language: options.language ?? "JavaScript" } );
1851
1914
  }
1852
1915
 
1853
1916
  _onSelectTab( isNewTabButton, event, name ) {
@@ -1896,19 +1959,19 @@ class CodeEditor {
1896
1959
  }
1897
1960
 
1898
1961
  new LX.DropdownMenu( event.target, [
1899
- { name: "Close", kbd: "MWB", callback: () => { this.tabs.delete( name ) } },
1900
- { name: "Close Others", callback: () => {
1962
+ { name: "Close", kbd: "MWB", disabled: !this.allowClosingTabs, callback: () => { this.closeTab( name ) } },
1963
+ { name: "Close Others", disabled: !this.allowClosingTabs, callback: () => {
1901
1964
  for( const [ key, data ] of Object.entries( this.tabs.tabs ) )
1902
1965
  {
1903
1966
  if( key === '+' || key === name ) continue;
1904
- this.tabs.delete( key )
1967
+ this.closeTab( key )
1905
1968
  }
1906
1969
  } },
1907
- { name: "Close All", callback: () => {
1970
+ { name: "Close All", disabled: !this.allowClosingTabs, callback: () => {
1908
1971
  for( const [ key, data ] of Object.entries( this.tabs.tabs ) )
1909
1972
  {
1910
1973
  if( key === '+' ) continue;
1911
- this.tabs.delete( key )
1974
+ this.closeTab( key )
1912
1975
  }
1913
1976
  } },
1914
1977
  null,
@@ -1990,7 +2053,8 @@ class CodeEditor {
1990
2053
  icon: tabIcon,
1991
2054
  onSelect: this._onSelectTab.bind( this, isNewTabButton ),
1992
2055
  onContextMenu: this._onContextMenuTab.bind( this, isNewTabButton ),
1993
- allowDelete: true
2056
+ allowDelete: this.allowClosingTabs,
2057
+ indexOffset: options.indexOffset
1994
2058
  } );
1995
2059
  }
1996
2060
 
@@ -2013,6 +2077,12 @@ class CodeEditor {
2013
2077
  this.mustProcessLines = true;
2014
2078
  }
2015
2079
 
2080
+ if( options.codeLines )
2081
+ {
2082
+ code.lines = options.codeLines;
2083
+ this.mustProcessLines = true;
2084
+ }
2085
+
2016
2086
  this._processLinesIfNecessary();
2017
2087
 
2018
2088
  this._updateDataInfoPanel( "@tab-name", name );
@@ -2130,7 +2200,7 @@ class CodeEditor {
2130
2200
  icon: tabIcon,
2131
2201
  onSelect: this._onSelectTab.bind( this, isNewTabButton ),
2132
2202
  onContextMenu: this._onContextMenuTab.bind( this, isNewTabButton ),
2133
- allowDelete: true
2203
+ allowDelete: this.allowClosingTabs
2134
2204
  });
2135
2205
 
2136
2206
  // Move into the sizer..
@@ -2148,6 +2218,11 @@ class CodeEditor {
2148
2218
 
2149
2219
  closeTab( name, eraseAll ) {
2150
2220
 
2221
+ if( !this.allowClosingTabs )
2222
+ {
2223
+ return;
2224
+ }
2225
+
2151
2226
  this.tabs.delete( name );
2152
2227
 
2153
2228
  if( eraseAll )
@@ -2265,22 +2340,65 @@ class CodeEditor {
2265
2340
  e.preventDefault();
2266
2341
 
2267
2342
  if( !this.canOpenContextMenu )
2343
+ {
2268
2344
  return;
2345
+ }
2269
2346
 
2270
2347
  LX.addContextMenu( null, e, m => {
2271
2348
  m.add( "Copy", () => { this._copyContent( cursor ); } );
2349
+
2272
2350
  if( !this.disableEdition )
2273
2351
  {
2274
2352
  m.add( "Cut", () => { this._cutContent( cursor ); } );
2275
2353
  m.add( "Paste", () => { this._pasteContent( cursor ); } );
2354
+ }
2355
+
2356
+ if( !this.onContextMenu )
2357
+ {
2358
+ return;
2359
+ }
2360
+
2361
+ let content = null;
2362
+
2363
+ if( cursor.selection )
2364
+ {
2365
+ // Some selections don't depend on mouse up..
2366
+ if( cursor.selection ) cursor.selection.invertIfNecessary();
2367
+
2368
+ const separator = "_NEWLINE_";
2369
+ let code = this.code.lines.join( separator );
2370
+
2371
+ // Get linear start index
2372
+ let index = 0;
2373
+
2374
+ for( let i = 0; i <= cursor.selection.fromY; i++ )
2375
+ {
2376
+ index += ( i == cursor.selection.fromY ? cursor.selection.fromX : this.code.lines[ i ].length );
2377
+ }
2378
+
2379
+ index += cursor.selection.fromY * separator.length;
2380
+ const num_chars = cursor.selection.chars + ( cursor.selection.toY - cursor.selection.fromY ) * separator.length;
2381
+ const text = code.substr( index, num_chars );
2382
+ content = text.split( separator ).join('\n');
2383
+ }
2384
+
2385
+ const options = this.onContextMenu( this, content, e );
2386
+ if( options.length )
2387
+ {
2276
2388
  m.add( "" );
2277
- m.add( "Format/JSON", () => {
2278
- let json = this.toJSONFormat( this.getText() );
2279
- if( !json )
2280
- return;
2281
- this.code.lines = json.split( "\n" );
2282
- this.processLines();
2283
- } );
2389
+
2390
+ for( const o of options )
2391
+ {
2392
+ m.add( o.path, { disabled: o.disabled, callback: o.callback } );
2393
+ }
2394
+
2395
+ // m.add( "Format/JSON", () => {
2396
+ // let json = this.toJSONFormat( this.getText() );
2397
+ // if( !json )
2398
+ // return;
2399
+ // this.code.lines = json.split( "\n" );
2400
+ // this.processLines();
2401
+ // } );
2284
2402
  }
2285
2403
  });
2286
2404
 
@@ -2613,10 +2731,17 @@ class CodeEditor {
2613
2731
  e.preventDefault();
2614
2732
  this.selectAll();
2615
2733
  return true;
2734
+ case 'b': // k+b comment block
2735
+ e.preventDefault();
2736
+ if( this.state.keyChain == 'k' ) {
2737
+ this._commentLines( cursor, true );
2738
+ return true;
2739
+ }
2740
+ return false;
2616
2741
  case 'c': // k+c, comment line
2617
2742
  e.preventDefault();
2618
2743
  if( this.state.keyChain == 'k' ) {
2619
- this._commentLines();
2744
+ this._commentLines( cursor );
2620
2745
  return true;
2621
2746
  }
2622
2747
  return false;
@@ -2638,12 +2763,12 @@ class CodeEditor {
2638
2763
  return true;
2639
2764
  case 's': // save
2640
2765
  e.preventDefault();
2641
- this.onsave( this.getText() );
2766
+ this.onSave( this.getText() );
2642
2767
  return true;
2643
2768
  case 'u': // k+u, uncomment line
2644
2769
  e.preventDefault();
2645
2770
  if( this.state.keyChain == 'k' ) {
2646
- this._uncommentLines();
2771
+ this._uncommentLines( cursor );
2647
2772
  return true;
2648
2773
  }
2649
2774
  return false;
@@ -2663,6 +2788,13 @@ class CodeEditor {
2663
2788
  e.preventDefault();
2664
2789
  this._decreaseFontSize();
2665
2790
  return true;
2791
+ case ' ': // custom event
2792
+ if( this.onCtrlSpace )
2793
+ {
2794
+ e.preventDefault();
2795
+ this.onCtrlSpace( cursor );
2796
+ return true;
2797
+ }
2666
2798
  }
2667
2799
  }
2668
2800
 
@@ -2956,32 +3088,77 @@ class CodeEditor {
2956
3088
  this.hideAutoCompleteBox();
2957
3089
  }
2958
3090
 
2959
- _commentLines() {
3091
+ _commentLines( cursor, useCommentBlock ) {
3092
+
3093
+ const lang = CodeEditor.languages[ this.highlight ];
2960
3094
 
2961
3095
  this.state.keyChain = null;
2962
3096
 
3097
+ cursor = cursor ?? this.getCurrentCursor();
3098
+
2963
3099
  if( cursor.selection )
2964
3100
  {
2965
- var cursor = this.getCurrentCursor();
3101
+ if( !( ( useCommentBlock ? lang.blockComments : lang.singleLineComments ) ?? true ) )
3102
+ {
3103
+ return;
3104
+ }
3105
+
2966
3106
  this._addUndoStep( cursor, true );
2967
3107
 
2968
- const selectedLines = this.code.lines.slice( cursor.selection.fromY, cursor.selection.toY );
3108
+ const selectedLines = this.code.lines.slice( cursor.selection.fromY, cursor.selection.toY + 1 );
2969
3109
  const minIdx = Math.min(...selectedLines.map( v => {
2970
3110
  var idx = firstNonspaceIndex( v );
2971
3111
  return idx < 0 ? 1e10 : idx;
2972
3112
  } ));
2973
3113
 
2974
- for( var i = cursor.selection.fromY; i <= cursor.selection.toY; ++i )
3114
+ if( useCommentBlock )
2975
3115
  {
2976
- this._commentLine( cursor, i, minIdx );
3116
+ const tokens = ( lang.blockCommentsTokens ?? this.defaultBlockCommentTokens );
3117
+
3118
+ const fromString = this.code.lines[ cursor.selection.fromY ];
3119
+ let fromIdx = firstNonspaceIndex( fromString );
3120
+ if( fromIdx == -1 )
3121
+ {
3122
+ fromIdx = 0;
3123
+ }
3124
+
3125
+ this.code.lines[ cursor.selection.fromY ] = [
3126
+ fromString.substring( 0, fromIdx ),
3127
+ tokens[ 0 ] + " ",
3128
+ fromString.substring( fromIdx )
3129
+ ].join( '' );
3130
+
3131
+ this.code.lines[ cursor.selection.toY ] += " " + tokens[ 1 ];
3132
+
3133
+ cursor.selection.fromX += ( tokens[ 0 ].length + 1 );
3134
+ this._processSelection( cursor );
3135
+ }
3136
+ else
3137
+ {
3138
+ for( let i = cursor.selection.fromY; i <= cursor.selection.toY; ++i )
3139
+ {
3140
+ this._commentLine( cursor, i, minIdx, false );
3141
+ }
3142
+
3143
+ const token = ( lang.singleLineCommentToken ?? this.defaultSingleLineCommentToken ) + ' ';
3144
+ this.cursorToString( cursor, token );
3145
+
3146
+ cursor.selection.fromX += token.length;
3147
+ cursor.selection.toX += token.length;
3148
+ this._processSelection( cursor );
2977
3149
  }
2978
3150
  }
2979
3151
  else
2980
3152
  {
2981
- for( let cursor of this.cursors.children )
3153
+ if( !( lang.singleLineComments ?? true ) )
2982
3154
  {
2983
- this._addUndoStep( cursor, true );
2984
- this._commentLine( cursor, cursor.line );
3155
+ return;
3156
+ }
3157
+
3158
+ for( const cr of this.cursors.children )
3159
+ {
3160
+ this._addUndoStep( cr, true );
3161
+ this._commentLine( cr, cr.line );
2985
3162
  }
2986
3163
  }
2987
3164
 
@@ -2989,33 +3166,37 @@ class CodeEditor {
2989
3166
  this._hideActiveLine();
2990
3167
  }
2991
3168
 
2992
- _commentLine( cursor, line, minNonspaceIdx ) {
3169
+ _commentLine( cursor, line, minNonspaceIdx, updateCursor = true ) {
2993
3170
 
2994
3171
  const lang = CodeEditor.languages[ this.highlight ];
2995
-
2996
- if( !( lang.singleLineComments ?? true ))
3172
+ if( !( lang.singleLineComments ?? true ) )
2997
3173
  return;
2998
3174
 
2999
3175
  const token = ( lang.singleLineCommentToken ?? this.defaultSingleLineCommentToken ) + ' ';
3000
3176
  const string = this.code.lines[ line ];
3001
3177
 
3002
3178
  let idx = firstNonspaceIndex( string );
3003
- if( idx > -1 )
3179
+ if( idx == -1 )
3004
3180
  {
3005
- // Update idx using min of the selected lines (if necessary..)
3006
- idx = minNonspaceIdx ?? idx;
3181
+ return;
3182
+ }
3007
3183
 
3008
- this.code.lines[ line ] = [
3009
- string.substring( 0, idx ),
3010
- token,
3011
- string.substring( idx )
3012
- ].join( '' );
3184
+ // Update idx using min of the selected lines (if necessary..)
3185
+ idx = minNonspaceIdx ?? idx;
3186
+
3187
+ this.code.lines[ line ] = [
3188
+ string.substring( 0, idx ),
3189
+ token,
3190
+ string.substring( idx )
3191
+ ].join( '' );
3013
3192
 
3193
+ if( updateCursor )
3194
+ {
3014
3195
  this.cursorToString( cursor, token );
3015
3196
  }
3016
3197
  }
3017
3198
 
3018
- _uncommentLines() {
3199
+ _uncommentLines( cursor ) {
3019
3200
 
3020
3201
  this.state.keyChain = null;
3021
3202
 
@@ -4594,6 +4775,8 @@ class CodeEditor {
4594
4775
 
4595
4776
  if( state.selection )
4596
4777
  {
4778
+ this.endSelection();
4779
+
4597
4780
  this.startSelection( cursor );
4598
4781
 
4599
4782
  cursor.selection.load( state.selection );
@@ -4874,7 +5057,10 @@ class CodeEditor {
4874
5057
  }
4875
5058
  else
4876
5059
  {
4877
- this.codeScroller.scrollLeft += value;
5060
+ if( value )
5061
+ {
5062
+ this.codeScroller.scrollLeft += value;
5063
+ }
4878
5064
 
4879
5065
  const scrollBarWidth = this.hScrollbar.thumb.parentElement.offsetWidth;
4880
5066
  const scrollThumbWidth = this.hScrollbar.thumb.offsetWidth;
@@ -5084,6 +5270,9 @@ class CodeEditor {
5084
5270
  suggestions = suggestions.concat( otherValues.slice( 0, -1 ) );
5085
5271
  }
5086
5272
 
5273
+ // Add custom suggestions...
5274
+ suggestions = suggestions.concat( this.customSuggestions );
5275
+
5087
5276
  // Remove 1/2 char words and duplicates...
5088
5277
  suggestions = Array.from( new Set( suggestions )).filter( s => s.length > 2 && s.toLowerCase().includes( word.toLowerCase() ) );
5089
5278
 
@@ -5602,6 +5791,36 @@ s
5602
5791
  delete this._lastResult;
5603
5792
  delete this._scopeStack;
5604
5793
  }
5794
+
5795
+ async _requestFileAsync( url, dataType, nocache ) {
5796
+ return new Promise( (resolve, reject) => {
5797
+ dataType = dataType ?? "arraybuffer";
5798
+ const mimeType = dataType === "arraybuffer" ? "application/octet-stream" : undefined;
5799
+ var xhr = new XMLHttpRequest();
5800
+ xhr.open( 'GET', url, true );
5801
+ xhr.responseType = dataType;
5802
+ if( mimeType )
5803
+ xhr.overrideMimeType( mimeType );
5804
+ if( nocache )
5805
+ xhr.setRequestHeader('Cache-Control', 'no-cache');
5806
+ xhr.onload = function(load)
5807
+ {
5808
+ var response = this.response;
5809
+ if( this.status != 200)
5810
+ {
5811
+ var err = "Error " + this.status;
5812
+ reject(err);
5813
+ return;
5814
+ }
5815
+ resolve( response );
5816
+ };
5817
+ xhr.onerror = function(err) {
5818
+ reject(err);
5819
+ };
5820
+ xhr.send();
5821
+ return xhr;
5822
+ });
5823
+ }
5605
5824
  }
5606
5825
 
5607
5826
  CodeEditor.languages = {