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.
- package/build/extensions/codeeditor.js +305 -86
- package/build/extensions/timeline.js +40 -12
- package/build/lexgui.css +4 -1
- package/build/lexgui.js +67 -22
- package/build/lexgui.min.css +2 -2
- package/build/lexgui.min.js +1 -1
- package/build/lexgui.module.js +67 -22
- package/build/lexgui.module.min.js +1 -1
- package/changelog.md +43 -1
- package/examples/code-editor.html +1 -1
- package/package.json +1 -1
|
@@ -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.
|
|
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
|
-
|
|
562
|
+
// Vertical scroll
|
|
563
|
+
{
|
|
564
|
+
this.setScrollBarValue( 'vertical' );
|
|
532
565
|
|
|
533
|
-
|
|
566
|
+
const scrollTop = this.getScrollTop();
|
|
534
567
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
{
|
|
538
|
-
if( this.visibleLinesViewport.y < (this.code.lines.length - 1) )
|
|
568
|
+
// Scroll down...
|
|
569
|
+
if( scrollTop > lastScrollTopValue )
|
|
539
570
|
{
|
|
540
|
-
|
|
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.
|
|
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
|
-
|
|
551
|
-
else
|
|
552
|
-
{
|
|
553
|
-
const scrollUpBoundary = parseInt( this.code.style.top );
|
|
554
|
-
if( scrollTop < scrollUpBoundary )
|
|
583
|
+
// Scroll up...
|
|
584
|
+
else
|
|
555
585
|
{
|
|
556
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
]
|
|
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.
|
|
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.
|
|
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.
|
|
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:
|
|
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:
|
|
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
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
3114
|
+
if( useCommentBlock )
|
|
2975
3115
|
{
|
|
2976
|
-
|
|
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
|
-
|
|
3153
|
+
if( !( lang.singleLineComments ?? true ) )
|
|
2982
3154
|
{
|
|
2983
|
-
|
|
2984
|
-
|
|
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
|
|
3179
|
+
if( idx == -1 )
|
|
3004
3180
|
{
|
|
3005
|
-
|
|
3006
|
-
|
|
3181
|
+
return;
|
|
3182
|
+
}
|
|
3007
3183
|
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
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
|
-
|
|
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 = {
|