lexgui 0.7.12 → 0.7.14
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 +465 -233
- package/build/lexgui.css +2 -1
- package/build/lexgui.js +80 -19
- package/build/lexgui.min.css +1 -1
- package/build/lexgui.min.js +1 -1
- package/build/lexgui.module.js +104 -43
- package/build/lexgui.module.min.js +1 -1
- package/changelog.md +30 -1
- package/examples/editor.html +31 -4
- package/package.json +1 -1
|
@@ -356,6 +356,7 @@ class CodeEditor
|
|
|
356
356
|
this.highlight = options.highlight ?? 'Plain Text';
|
|
357
357
|
this.newTabOptions = options.newTabOptions;
|
|
358
358
|
this.customSuggestions = options.customSuggestions ?? [];
|
|
359
|
+
this.explorerName = options.explorerName ?? "EXPLORER";
|
|
359
360
|
|
|
360
361
|
// Editor callbacks
|
|
361
362
|
this.onSave = options.onSave ?? options.onsave; // LEGACY onsave
|
|
@@ -375,45 +376,43 @@ class CodeEditor
|
|
|
375
376
|
|
|
376
377
|
let panel = new LX.Panel();
|
|
377
378
|
|
|
378
|
-
panel.addTitle(
|
|
379
|
+
panel.addTitle( this.explorerName );
|
|
379
380
|
|
|
380
|
-
let sceneData =
|
|
381
|
-
'id': 'WORKSPACE',
|
|
382
|
-
'skipVisibility': true,
|
|
383
|
-
'children': []
|
|
384
|
-
};
|
|
381
|
+
let sceneData = [];
|
|
385
382
|
|
|
386
383
|
this.explorer = panel.addTree( null, sceneData, {
|
|
387
384
|
filter: false,
|
|
388
385
|
rename: false,
|
|
389
386
|
skipDefaultIcon: true,
|
|
390
|
-
onevent: (event) =>
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
387
|
+
onevent: (event) =>
|
|
388
|
+
{
|
|
389
|
+
switch( event.type )
|
|
390
|
+
{
|
|
391
|
+
// case LX.TreeEvent.NODE_SELECTED:
|
|
392
|
+
// if( !this.tabs.tabDOMs[ event.node.id ] ) break;
|
|
393
|
+
case LX.TreeEvent.NODE_DBLCLICKED:
|
|
394
|
+
this.loadTab( event.node.id );
|
|
395
|
+
break;
|
|
396
|
+
case LX.TreeEvent.NODE_DELETED:
|
|
397
|
+
this.closeTab( event.node.id );
|
|
398
|
+
break;
|
|
399
|
+
// case LX.TreeEvent.NODE_CONTEXTMENU:
|
|
400
|
+
// LX.addContextMenu( event.multiple ? "Selected Nodes" : event.node.id, event.value, m => {
|
|
401
|
+
//
|
|
402
|
+
// });
|
|
403
|
+
// break;
|
|
404
|
+
// case LX.TreeEvent.NODE_DRAGGED:
|
|
405
|
+
// console.log(event.node.id + " is now child of " + event.value.id);
|
|
406
|
+
// break;
|
|
408
407
|
}
|
|
409
408
|
}
|
|
410
409
|
});
|
|
411
410
|
|
|
412
411
|
this.addExplorerItem = function( item )
|
|
413
412
|
{
|
|
414
|
-
if( !this.explorer.innerTree.data.
|
|
413
|
+
if( !this.explorer.innerTree.data.find( ( value, index ) => value.id === item.id ) )
|
|
415
414
|
{
|
|
416
|
-
this.explorer.innerTree.data.
|
|
415
|
+
this.explorer.innerTree.data.push( item );
|
|
417
416
|
}
|
|
418
417
|
};
|
|
419
418
|
|
|
@@ -487,6 +486,17 @@ class CodeEditor
|
|
|
487
486
|
|
|
488
487
|
this.codeArea.root.classList.add( 'lexcodearea' );
|
|
489
488
|
|
|
489
|
+
const codeResizeObserver = new ResizeObserver( entries => {
|
|
490
|
+
|
|
491
|
+
if( !this.code )
|
|
492
|
+
{
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
this.resize();
|
|
497
|
+
});
|
|
498
|
+
codeResizeObserver.observe( this.codeArea.root );
|
|
499
|
+
|
|
490
500
|
// Full editor
|
|
491
501
|
area.root.classList.add('codebasearea');
|
|
492
502
|
|
|
@@ -894,11 +904,13 @@ class CodeEditor
|
|
|
894
904
|
}
|
|
895
905
|
else
|
|
896
906
|
{
|
|
897
|
-
const indentSpaces = this.tabSpaces - (cursor.position % this.tabSpaces);
|
|
898
|
-
this._addSpaces( indentSpaces );
|
|
907
|
+
const indentSpaces = this.tabSpaces - ( cursor.position % this.tabSpaces );
|
|
908
|
+
this._addSpaces( cursor, indentSpaces );
|
|
899
909
|
}
|
|
900
910
|
}
|
|
901
|
-
},
|
|
911
|
+
}, ( cursor, e ) => {
|
|
912
|
+
return e.shiftKey || ( cursor.selection && !cursor.selection.sameLine() );
|
|
913
|
+
});
|
|
902
914
|
|
|
903
915
|
this.action( 'Home', false, ( ln, cursor, e ) => {
|
|
904
916
|
|
|
@@ -910,7 +922,7 @@ class CodeEditor
|
|
|
910
922
|
const prestring = this.code.lines[ ln ].substring( 0, idx );
|
|
911
923
|
let lastX = cursor.position;
|
|
912
924
|
|
|
913
|
-
this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
|
|
925
|
+
this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor, true );
|
|
914
926
|
if( idx > 0 )
|
|
915
927
|
{
|
|
916
928
|
this.cursorToString( cursor, prestring );
|
|
@@ -921,7 +933,6 @@ class CodeEditor
|
|
|
921
933
|
idx = 0;
|
|
922
934
|
}
|
|
923
935
|
|
|
924
|
-
this.setScrollLeft( 0 );
|
|
925
936
|
this.mergeCursors( ln );
|
|
926
937
|
|
|
927
938
|
if( e.shiftKey && !e.cancelShift )
|
|
@@ -946,14 +957,17 @@ class CodeEditor
|
|
|
946
957
|
{
|
|
947
958
|
this._processSelection( cursor, e );
|
|
948
959
|
}
|
|
949
|
-
}
|
|
960
|
+
}
|
|
961
|
+
else if( !e.keepSelection )
|
|
962
|
+
{
|
|
950
963
|
this.endSelection();
|
|
964
|
+
}
|
|
951
965
|
});
|
|
952
966
|
|
|
953
|
-
this.action( 'End', false, ( ln, cursor, e ) =>
|
|
954
|
-
|
|
955
|
-
if( ( e.shiftKey || e._shiftKey ) && !e.cancelShift )
|
|
956
|
-
|
|
967
|
+
this.action( 'End', false, ( ln, cursor, e ) =>
|
|
968
|
+
{
|
|
969
|
+
if( ( e.shiftKey || e._shiftKey ) && !e.cancelShift )
|
|
970
|
+
{
|
|
957
971
|
var string = this.code.lines[ ln ].substring( cursor.position );
|
|
958
972
|
if( !cursor.selection )
|
|
959
973
|
this.startSelection( cursor );
|
|
@@ -1072,11 +1086,14 @@ class CodeEditor
|
|
|
1072
1086
|
const letter = this.getCharAtPos( cursor );
|
|
1073
1087
|
|
|
1074
1088
|
// Go to end of line if out of range
|
|
1075
|
-
if( !letter || !canGoDown )
|
|
1076
|
-
|
|
1089
|
+
if( !letter || !canGoDown )
|
|
1090
|
+
{
|
|
1091
|
+
this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
|
|
1092
|
+
this.cursorToRight( this.code.lines[ cursor.line ], cursor );
|
|
1077
1093
|
}
|
|
1078
1094
|
|
|
1079
|
-
if( e.shiftKey )
|
|
1095
|
+
if( e.shiftKey )
|
|
1096
|
+
{
|
|
1080
1097
|
this._processSelection( cursor, e );
|
|
1081
1098
|
}
|
|
1082
1099
|
}
|
|
@@ -1142,8 +1159,8 @@ class CodeEditor
|
|
|
1142
1159
|
else {
|
|
1143
1160
|
cursor.selection.invertIfNecessary();
|
|
1144
1161
|
this.resetCursorPos( CodeEditor.CURSOR_LEFT_TOP, cursor );
|
|
1145
|
-
this.cursorToLine( cursor, cursor.selection.fromY
|
|
1146
|
-
this.cursorToPosition( cursor, cursor.selection.fromX );
|
|
1162
|
+
this.cursorToLine( cursor, cursor.selection.fromY );
|
|
1163
|
+
this.cursorToPosition( cursor, cursor.selection.fromX, true );
|
|
1147
1164
|
this.endSelection();
|
|
1148
1165
|
}
|
|
1149
1166
|
}
|
|
@@ -1225,7 +1242,7 @@ class CodeEditor
|
|
|
1225
1242
|
cursor.selection.invertIfNecessary();
|
|
1226
1243
|
this.resetCursorPos( CodeEditor.CURSOR_LEFT_TOP, cursor );
|
|
1227
1244
|
this.cursorToLine( cursor, cursor.selection.toY );
|
|
1228
|
-
this.cursorToPosition( cursor, cursor.selection.toX );
|
|
1245
|
+
this.cursorToPosition( cursor, cursor.selection.toX, true );
|
|
1229
1246
|
this.endSelection( cursor );
|
|
1230
1247
|
}
|
|
1231
1248
|
}
|
|
@@ -1256,8 +1273,8 @@ class CodeEditor
|
|
|
1256
1273
|
this.loadedTabs = { };
|
|
1257
1274
|
this.openedTabs = { };
|
|
1258
1275
|
|
|
1259
|
-
const onLoadAll = () =>
|
|
1260
|
-
|
|
1276
|
+
const onLoadAll = async () =>
|
|
1277
|
+
{
|
|
1261
1278
|
// Create inspector panel when the initial state is complete
|
|
1262
1279
|
// and we have at least 1 tab opened
|
|
1263
1280
|
this.statusPanel = this._createStatusPanel( options );
|
|
@@ -1266,44 +1283,46 @@ class CodeEditor
|
|
|
1266
1283
|
area.attach( this.statusPanel );
|
|
1267
1284
|
}
|
|
1268
1285
|
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
if( savedFontSize )
|
|
1274
|
-
{
|
|
1275
|
-
this._setFontSize( parseInt( savedFontSize ) );
|
|
1276
|
-
}
|
|
1277
|
-
else // Use default size
|
|
1278
|
-
{
|
|
1279
|
-
const r = document.querySelector( ':root' );
|
|
1280
|
-
const s = getComputedStyle( r );
|
|
1281
|
-
this.fontSize = parseInt( s.getPropertyValue( "--code-editor-font-size" ) );
|
|
1282
|
-
this.charWidth = this._measureChar( "a", true );
|
|
1283
|
-
this.processLines();
|
|
1284
|
-
}
|
|
1286
|
+
if( document.fonts.status == "loading" )
|
|
1287
|
+
{
|
|
1288
|
+
await document.fonts.ready;
|
|
1289
|
+
}
|
|
1285
1290
|
|
|
1286
|
-
|
|
1291
|
+
// Load any font size from local storage
|
|
1292
|
+
const savedFontSize = window.localStorage.getItem( "lexcodeeditor-font-size" );
|
|
1293
|
+
if( savedFontSize )
|
|
1294
|
+
{
|
|
1295
|
+
this._setFontSize( parseInt( savedFontSize ) );
|
|
1296
|
+
}
|
|
1297
|
+
else // Use default size
|
|
1298
|
+
{
|
|
1299
|
+
const r = document.querySelector( ':root' );
|
|
1300
|
+
const s = getComputedStyle( r );
|
|
1301
|
+
this.fontSize = parseInt( s.getPropertyValue( "--code-editor-font-size" ) );
|
|
1302
|
+
this.charWidth = this._measureChar();
|
|
1303
|
+
this.processLines();
|
|
1304
|
+
}
|
|
1287
1305
|
|
|
1288
|
-
|
|
1289
|
-
LX.doAsync( () => {
|
|
1290
|
-
this._verticalTopOffset = this.tabs?.root.getBoundingClientRect().height ?? 0;
|
|
1291
|
-
this._verticalBottomOffset = this.statusPanel?.root.getBoundingClientRect().height ?? 0;
|
|
1292
|
-
this._fullVerticalOffset = this._verticalTopOffset + this._verticalBottomOffset;
|
|
1306
|
+
LX.emit( "@font-size", this.fontSize );
|
|
1293
1307
|
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
this.codeArea.root.style.height = `calc(100% - ${ this._fullVerticalOffset }px)`;
|
|
1300
|
-
}, 50 );
|
|
1308
|
+
// Get final sizes for editor elements based on Tabs and status bar offsets
|
|
1309
|
+
LX.doAsync( () => {
|
|
1310
|
+
this._verticalTopOffset = this.tabs?.root.getBoundingClientRect().height ?? 0;
|
|
1311
|
+
this._verticalBottomOffset = this.statusPanel?.root.getBoundingClientRect().height ?? 0;
|
|
1312
|
+
this._fullVerticalOffset = this._verticalTopOffset + this._verticalBottomOffset;
|
|
1301
1313
|
|
|
1302
|
-
|
|
1303
|
-
{
|
|
1304
|
-
|
|
1305
|
-
}
|
|
1306
|
-
|
|
1314
|
+
this.gutter.style.marginTop = `${ this._verticalTopOffset }px`;
|
|
1315
|
+
this.gutter.style.height = `calc(100% - ${ this._fullVerticalOffset }px)`;
|
|
1316
|
+
this.vScrollbar.root.style.marginTop = `${ this._verticalTopOffset }px`;
|
|
1317
|
+
this.vScrollbar.root.style.height = `calc(100% - ${ this._fullVerticalOffset }px)`;
|
|
1318
|
+
this.hScrollbar.root.style.bottom = `${ this._verticalBottomOffset }px`;
|
|
1319
|
+
this.codeArea.root.style.height = `calc(100% - ${ this._fullVerticalOffset }px)`;
|
|
1320
|
+
}, 50 );
|
|
1321
|
+
|
|
1322
|
+
if( options.callback )
|
|
1323
|
+
{
|
|
1324
|
+
options.callback.call( this, this );
|
|
1325
|
+
}
|
|
1307
1326
|
|
|
1308
1327
|
window.editor = this;
|
|
1309
1328
|
};
|
|
@@ -1339,13 +1358,13 @@ class CodeEditor
|
|
|
1339
1358
|
}});
|
|
1340
1359
|
}
|
|
1341
1360
|
}
|
|
1342
|
-
else if( options.defaultTab ?? true )
|
|
1343
|
-
{
|
|
1344
|
-
this.addTab( options.name || "untitled", true, options.title, { language: options.highlight ?? "Plain Text" } );
|
|
1345
|
-
onLoadAll();
|
|
1346
|
-
}
|
|
1347
1361
|
else
|
|
1348
1362
|
{
|
|
1363
|
+
if( options.defaultTab ?? true )
|
|
1364
|
+
{
|
|
1365
|
+
this.addTab( options.name || "untitled", true, options.title, { language: options.highlight ?? "Plain Text" } );
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1349
1368
|
onLoadAll();
|
|
1350
1369
|
}
|
|
1351
1370
|
}
|
|
@@ -1400,7 +1419,7 @@ class CodeEditor
|
|
|
1400
1419
|
let lastLine = newLines.pop();
|
|
1401
1420
|
|
|
1402
1421
|
this.cursorToLine( cursor, newLines.length ); // Already substracted 1
|
|
1403
|
-
this.cursorToPosition( cursor, lastLine.length );
|
|
1422
|
+
this.cursorToPosition( cursor, lastLine.length, true );
|
|
1404
1423
|
|
|
1405
1424
|
this.mustProcessLines = true;
|
|
1406
1425
|
|
|
@@ -1724,7 +1743,7 @@ class CodeEditor
|
|
|
1724
1743
|
// Update explorer icon
|
|
1725
1744
|
if( this.useFileExplorer )
|
|
1726
1745
|
{
|
|
1727
|
-
const item = this.explorer.innerTree.data.
|
|
1746
|
+
const item = this.explorer.innerTree.data.filter( (v) => v.id === this.code.tabName )[ 0 ];
|
|
1728
1747
|
console.assert( item != undefined );
|
|
1729
1748
|
item.icon = icon;
|
|
1730
1749
|
this.explorer.innerTree.frefresh( this.code.tabName );
|
|
@@ -1810,7 +1829,8 @@ class CodeEditor
|
|
|
1810
1829
|
for( const lang of Object.keys( CodeEditor.languages ) )
|
|
1811
1830
|
{
|
|
1812
1831
|
m.add( lang, v => {
|
|
1813
|
-
this._changeLanguage( v, null, true )
|
|
1832
|
+
this._changeLanguage( v, null, true );
|
|
1833
|
+
this.processLines();
|
|
1814
1834
|
} );
|
|
1815
1835
|
}
|
|
1816
1836
|
});
|
|
@@ -2075,7 +2095,7 @@ class CodeEditor
|
|
|
2075
2095
|
});
|
|
2076
2096
|
code.addEventListener( 'dragleave', function(e) {
|
|
2077
2097
|
e.preventDefault();
|
|
2078
|
-
this.parentElement.remove( 'dragging' );
|
|
2098
|
+
this.parentElement.classList.remove( 'dragging' );
|
|
2079
2099
|
});
|
|
2080
2100
|
code.addEventListener( 'drop', e => {
|
|
2081
2101
|
e.preventDefault();
|
|
@@ -2133,7 +2153,7 @@ class CodeEditor
|
|
|
2133
2153
|
|
|
2134
2154
|
if( selected )
|
|
2135
2155
|
{
|
|
2136
|
-
this.resetCursorPos( CodeEditor.CURSOR_LEFT_TOP );
|
|
2156
|
+
this.resetCursorPos( CodeEditor.CURSOR_LEFT_TOP, undefined, true );
|
|
2137
2157
|
this.mustProcessLines = true;
|
|
2138
2158
|
}
|
|
2139
2159
|
else
|
|
@@ -2201,7 +2221,7 @@ class CodeEditor
|
|
|
2201
2221
|
this.code = code;
|
|
2202
2222
|
this.mustProcessLines = true;
|
|
2203
2223
|
|
|
2204
|
-
this.resetCursorPos( CodeEditor.CURSOR_LEFT_TOP );
|
|
2224
|
+
this.resetCursorPos( CodeEditor.CURSOR_LEFT_TOP, undefined, true );
|
|
2205
2225
|
this.processLines();
|
|
2206
2226
|
this._changeLanguageFromExtension( LX.getExtension( name ) );
|
|
2207
2227
|
this._processLinesIfNecessary();
|
|
@@ -2246,6 +2266,9 @@ class CodeEditor
|
|
|
2246
2266
|
return;
|
|
2247
2267
|
}
|
|
2248
2268
|
|
|
2269
|
+
// Reset visibility
|
|
2270
|
+
code.style.display = "block";
|
|
2271
|
+
|
|
2249
2272
|
this.openedTabs[ name ] = code;
|
|
2250
2273
|
|
|
2251
2274
|
const isNewTabButton = ( name === '+' );
|
|
@@ -2268,10 +2291,11 @@ class CodeEditor
|
|
|
2268
2291
|
|
|
2269
2292
|
// Select as current...
|
|
2270
2293
|
this.code = code;
|
|
2271
|
-
this.resetCursorPos( CodeEditor.CURSOR_LEFT_TOP );
|
|
2272
|
-
this.processLines();
|
|
2294
|
+
this.resetCursorPos( CodeEditor.CURSOR_LEFT_TOP, undefined, true );
|
|
2273
2295
|
this._changeLanguageFromExtension( LX.getExtension( name ) );
|
|
2274
2296
|
this._updateDataInfoPanel( "@tab-name", code.tabName );
|
|
2297
|
+
|
|
2298
|
+
this.processLines();
|
|
2275
2299
|
}
|
|
2276
2300
|
|
|
2277
2301
|
closeTab( name, eraseAll )
|
|
@@ -2355,8 +2379,8 @@ class CodeEditor
|
|
|
2355
2379
|
}
|
|
2356
2380
|
}
|
|
2357
2381
|
|
|
2382
|
+
this._mouseDown = true;
|
|
2358
2383
|
this.lastMouseDown = LX.getTime();
|
|
2359
|
-
this.state.selectingText = true;
|
|
2360
2384
|
this.endSelection();
|
|
2361
2385
|
this.processClick( e );
|
|
2362
2386
|
}
|
|
@@ -2368,8 +2392,11 @@ class CodeEditor
|
|
|
2368
2392
|
|
|
2369
2393
|
else if( e.type == 'mousemove' )
|
|
2370
2394
|
{
|
|
2371
|
-
if( this.
|
|
2395
|
+
if( this._mouseDown )
|
|
2396
|
+
{
|
|
2397
|
+
this.state.selectingText = true;
|
|
2372
2398
|
this.processSelections( e );
|
|
2399
|
+
}
|
|
2373
2400
|
}
|
|
2374
2401
|
|
|
2375
2402
|
else if ( e.type == 'click' ) // trip
|
|
@@ -2388,7 +2415,7 @@ class CodeEditor
|
|
|
2388
2415
|
case LX.MOUSE_TRIPLE_CLICK:
|
|
2389
2416
|
this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
|
|
2390
2417
|
e._shiftKey = true;
|
|
2391
|
-
this.actions['End'].callback(cursor.line, cursor, e);
|
|
2418
|
+
this.actions['End'].callback( cursor.line, cursor, e );
|
|
2392
2419
|
this._tripleClickSelection = true;
|
|
2393
2420
|
break;
|
|
2394
2421
|
}
|
|
@@ -2477,8 +2504,16 @@ class CodeEditor
|
|
|
2477
2504
|
if( cursor.selection )
|
|
2478
2505
|
{
|
|
2479
2506
|
cursor.selection.invertIfNecessary();
|
|
2507
|
+
|
|
2508
|
+
// If single line selection and no chars selected, remove selection
|
|
2509
|
+
const deltaY = cursor.selection.toY - cursor.selection.fromY;
|
|
2510
|
+
if( cursor.selection.chars == 0 && deltaY == 0 )
|
|
2511
|
+
{
|
|
2512
|
+
this.endSelection();
|
|
2513
|
+
}
|
|
2480
2514
|
}
|
|
2481
2515
|
|
|
2516
|
+
this._mouseDown = false;
|
|
2482
2517
|
this.state.selectingText = false;
|
|
2483
2518
|
delete this._lastSelectionKeyDir;
|
|
2484
2519
|
}
|
|
@@ -2502,7 +2537,7 @@ class CodeEditor
|
|
|
2502
2537
|
{
|
|
2503
2538
|
// Make sure we only keep the main cursor..
|
|
2504
2539
|
this._removeSecondaryCursors();
|
|
2505
|
-
this.cursorToLine( cursor, ln
|
|
2540
|
+
this.cursorToLine( cursor, ln );
|
|
2506
2541
|
this.cursorToPosition( cursor, string.length );
|
|
2507
2542
|
}
|
|
2508
2543
|
|
|
@@ -2641,7 +2676,7 @@ class CodeEditor
|
|
|
2641
2676
|
|
|
2642
2677
|
if( isVisible )
|
|
2643
2678
|
{
|
|
2644
|
-
domEl.style.width = ( stringWidth || 8 ) + "px";
|
|
2679
|
+
domEl.style.width = ( stringWidth || ( deltaY == 0 ? 0 : 8 ) ) + "px";
|
|
2645
2680
|
domEl._top = i * this.lineHeight;
|
|
2646
2681
|
domEl.style.top = domEl._top + "px";
|
|
2647
2682
|
}
|
|
@@ -2760,10 +2795,15 @@ class CodeEditor
|
|
|
2760
2795
|
|
|
2761
2796
|
cursorOffset.x += ( cursor.position - lastProcessedCursor.position );
|
|
2762
2797
|
cursorOffset.y += ( cursor.line - lastProcessedCursor.line );
|
|
2798
|
+
|
|
2799
|
+
// Set active line in case it's blurred
|
|
2800
|
+
if( !cursor.selection )
|
|
2801
|
+
{
|
|
2802
|
+
cursor.line = cursor.line;
|
|
2803
|
+
}
|
|
2763
2804
|
}
|
|
2764
2805
|
|
|
2765
2806
|
// Clear tmp
|
|
2766
|
-
|
|
2767
2807
|
delete this._lastProcessedCursorIndex;
|
|
2768
2808
|
}
|
|
2769
2809
|
|
|
@@ -2865,6 +2905,7 @@ class CodeEditor
|
|
|
2865
2905
|
async _processKeyAtCursor( e, key, cursor )
|
|
2866
2906
|
{
|
|
2867
2907
|
const skipUndo = e.detail.skipUndo ?? false;
|
|
2908
|
+
const skipDeleteSelection = e.detail.skipDeleteSelection ?? false;
|
|
2868
2909
|
|
|
2869
2910
|
// keys with length > 1 are probably special keys
|
|
2870
2911
|
if( key.length > 1 && this.specialKeys.indexOf( key ) == -1 )
|
|
@@ -2902,33 +2943,19 @@ class CodeEditor
|
|
|
2902
2943
|
}
|
|
2903
2944
|
}
|
|
2904
2945
|
}
|
|
2905
|
-
|
|
2906
2946
|
else if( e.altKey )
|
|
2907
2947
|
{
|
|
2908
|
-
switch( key )
|
|
2909
|
-
|
|
2948
|
+
switch( key )
|
|
2949
|
+
{
|
|
2950
|
+
case 'd': // duplicate line
|
|
2910
2951
|
e.preventDefault();
|
|
2911
2952
|
this._duplicateLine( lidx, cursor );
|
|
2912
2953
|
return;
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
return;
|
|
2916
|
-
this._addUndoStep( cursor, true );
|
|
2917
|
-
swapArrayElements( this.code.lines, lidx - 1, lidx );
|
|
2918
|
-
this.lineUp( cursor );
|
|
2919
|
-
this.processLine( lidx - 1 );
|
|
2920
|
-
this.processLine( lidx );
|
|
2921
|
-
this.hideAutoCompleteBox();
|
|
2954
|
+
case 'ArrowUp':
|
|
2955
|
+
this.swapLines( lidx, -1, cursor );
|
|
2922
2956
|
return;
|
|
2923
2957
|
case 'ArrowDown':
|
|
2924
|
-
|
|
2925
|
-
return;
|
|
2926
|
-
this._addUndoStep( cursor, true );
|
|
2927
|
-
swapArrayElements( this.code.lines, lidx, lidx + 1 );
|
|
2928
|
-
this.lineDown( cursor );
|
|
2929
|
-
this.processLine( lidx );
|
|
2930
|
-
this.processLine( lidx + 1 );
|
|
2931
|
-
this.hideAutoCompleteBox();
|
|
2958
|
+
this.swapLines( lidx, 1, cursor );
|
|
2932
2959
|
return;
|
|
2933
2960
|
}
|
|
2934
2961
|
}
|
|
@@ -2943,7 +2970,9 @@ class CodeEditor
|
|
|
2943
2970
|
e.preventDefault();
|
|
2944
2971
|
|
|
2945
2972
|
if( this._actionMustDelete( cursor, this.actions[ key ], e ) )
|
|
2973
|
+
{
|
|
2946
2974
|
this.actions['Backspace'].callback( lidx, cursor, e );
|
|
2975
|
+
}
|
|
2947
2976
|
|
|
2948
2977
|
return this.actions[ key ].callback( lidx, cursor, e );
|
|
2949
2978
|
}
|
|
@@ -2971,7 +3000,7 @@ class CodeEditor
|
|
|
2971
3000
|
// Until this point, if there was a selection, we need
|
|
2972
3001
|
// to delete the content..
|
|
2973
3002
|
|
|
2974
|
-
if( cursor.selection )
|
|
3003
|
+
if( cursor.selection && !skipDeleteSelection )
|
|
2975
3004
|
{
|
|
2976
3005
|
this.actions['Backspace'].callback( lidx, cursor, e );
|
|
2977
3006
|
lidx = cursor.line;
|
|
@@ -3305,20 +3334,20 @@ class CodeEditor
|
|
|
3305
3334
|
}
|
|
3306
3335
|
}
|
|
3307
3336
|
|
|
3308
|
-
action( key, deleteSelection, fn,
|
|
3337
|
+
action( key, deleteSelection, fn, eventSkipDeleteFn )
|
|
3309
3338
|
{
|
|
3310
3339
|
this.actions[ key ] = {
|
|
3311
3340
|
"key": key,
|
|
3312
3341
|
"callback": fn,
|
|
3313
3342
|
"deleteSelection": deleteSelection,
|
|
3314
|
-
"
|
|
3343
|
+
"eventSkipDeleteFn": eventSkipDeleteFn
|
|
3315
3344
|
};
|
|
3316
3345
|
}
|
|
3317
3346
|
|
|
3318
3347
|
_actionMustDelete( cursor, action, e )
|
|
3319
3348
|
{
|
|
3320
3349
|
return cursor.selection && action.deleteSelection &&
|
|
3321
|
-
( action.
|
|
3350
|
+
!( action.eventSkipDeleteFn ? action.eventSkipDeleteFn( cursor, e ) : false );
|
|
3322
3351
|
}
|
|
3323
3352
|
|
|
3324
3353
|
scanWordSuggestions()
|
|
@@ -4077,7 +4106,7 @@ class CodeEditor
|
|
|
4077
4106
|
charCounter += t.length;
|
|
4078
4107
|
};
|
|
4079
4108
|
|
|
4080
|
-
let iter = lineString.matchAll(/(<!--|-->|\*\/|\/\*|::|[\[\](){}<>.,;:*"'
|
|
4109
|
+
let iter = lineString.matchAll(/(<!--|-->|\*\/|\/\*|::|[\[\](){}<>.,;:*"'`%@$!/= ])/g);
|
|
4081
4110
|
let subtokens = iter.next();
|
|
4082
4111
|
if( subtokens.value )
|
|
4083
4112
|
{
|
|
@@ -4237,13 +4266,29 @@ class CodeEditor
|
|
|
4237
4266
|
usePreviousTokenToCheckString = true;
|
|
4238
4267
|
customStringKeys['@['] = ']';
|
|
4239
4268
|
}
|
|
4269
|
+
else if( highlight == 'javascript' || highlight == 'typescript' )
|
|
4270
|
+
{
|
|
4271
|
+
customStringKeys["@`"] = "`";
|
|
4272
|
+
}
|
|
4240
4273
|
|
|
4241
4274
|
// Manage strings
|
|
4242
4275
|
this._stringEnded = false;
|
|
4243
4276
|
|
|
4244
4277
|
if( usePreviousTokenToCheckString || ( !inBlockComment && ( lang.tags ?? false ? ( this._enclosedByTokens( token, tokenIndex, '<', '>' ) ) : true ) ) )
|
|
4245
4278
|
{
|
|
4246
|
-
const _checkIfStringEnded = t => {
|
|
4279
|
+
const _checkIfStringEnded = ( t ) => {
|
|
4280
|
+
|
|
4281
|
+
if( this._stringInterpolation )
|
|
4282
|
+
{
|
|
4283
|
+
if( token == "$" && next == "{" )
|
|
4284
|
+
{
|
|
4285
|
+
delete this._stringInterpolation;
|
|
4286
|
+
this._stringInterpolationOpened = true;
|
|
4287
|
+
this._stringEnded = true;
|
|
4288
|
+
return;
|
|
4289
|
+
}
|
|
4290
|
+
}
|
|
4291
|
+
|
|
4247
4292
|
const idx = Object.values( customStringKeys ).indexOf( t );
|
|
4248
4293
|
this._stringEnded = (idx > -1) && (idx == Object.values(customStringKeys).indexOf( customStringKeys[ '@' + this._buildingString ] ));
|
|
4249
4294
|
};
|
|
@@ -4257,12 +4302,23 @@ class CodeEditor
|
|
|
4257
4302
|
// Start new string
|
|
4258
4303
|
this._buildingString = ( usePreviousTokenToCheckString ? ctxData.prevWithSpaces : token );
|
|
4259
4304
|
|
|
4305
|
+
if( ( highlight == 'javascript' || highlight == 'typescript' ) && token == "`" )
|
|
4306
|
+
{
|
|
4307
|
+
this._stringInterpolation = true;
|
|
4308
|
+
}
|
|
4309
|
+
|
|
4260
4310
|
// Check if string ended in same token using next...
|
|
4261
4311
|
if( usePreviousTokenToCheckString )
|
|
4262
4312
|
{
|
|
4263
4313
|
_checkIfStringEnded( ctxData.nextWithSpaces );
|
|
4264
4314
|
}
|
|
4265
4315
|
}
|
|
4316
|
+
else if( this._stringInterpolationOpened && prev == "}" )
|
|
4317
|
+
{
|
|
4318
|
+
delete this._stringInterpolationOpened;
|
|
4319
|
+
this._stringInterpolation = true;
|
|
4320
|
+
this._buildingString = "`";
|
|
4321
|
+
}
|
|
4266
4322
|
}
|
|
4267
4323
|
|
|
4268
4324
|
// Update context data for next tests
|
|
@@ -4284,6 +4340,16 @@ class CodeEditor
|
|
|
4284
4340
|
// Get highlighting class based on language common and specific rules
|
|
4285
4341
|
let tokenClass = this._getTokenHighlighting( ctxData, highlight );
|
|
4286
4342
|
|
|
4343
|
+
if( this._stringInterpolationOpened && this._pendingString )
|
|
4344
|
+
{
|
|
4345
|
+
this._pendingString = this._pendingString.substring( 0, this._pendingString.indexOf( "$" ) );
|
|
4346
|
+
|
|
4347
|
+
if( ctxData.tokens[ tokenIndex + 1 ] == "{" )
|
|
4348
|
+
{
|
|
4349
|
+
ctxData.tokens[ tokenIndex + 1 ] = "${";
|
|
4350
|
+
}
|
|
4351
|
+
}
|
|
4352
|
+
|
|
4287
4353
|
// We finished constructing a string
|
|
4288
4354
|
if( this._buildingString && ( this._stringEnded || isLastToken ) && !inBlockComment )
|
|
4289
4355
|
{
|
|
@@ -4502,35 +4568,43 @@ class CodeEditor
|
|
|
4502
4568
|
const tokenSet = new Set( this._getTokensFromLine( text, true ) );
|
|
4503
4569
|
const scores = {};
|
|
4504
4570
|
|
|
4505
|
-
|
|
4506
|
-
{
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
if( tokenSet.has( kw ) ) scores[ lang ]++;
|
|
4516
|
-
}
|
|
4571
|
+
// Check strong indicators first
|
|
4572
|
+
const strongIndicators = {
|
|
4573
|
+
"JavaScript": ["import ", "export default", "console.", "=>", "document.", "window."],
|
|
4574
|
+
"TypeScript": ["import ", "export default", "console.", "=>", "document.", "window."],
|
|
4575
|
+
"C++": ["#include", "::", "std::", "template <", "using namespace"],
|
|
4576
|
+
"Python": ["def ", "import ", "print(", "self", "None", "True", "False"],
|
|
4577
|
+
"HTML": ["<html", "<div", "<body", "<script", "<style"],
|
|
4578
|
+
"CSS": ["@media"],
|
|
4579
|
+
"Markdown": ["#", "##", "###", "](", "![", "**"],
|
|
4580
|
+
};
|
|
4517
4581
|
|
|
4518
|
-
for(
|
|
4582
|
+
for( const [ lang, indicators ] of Object.entries( strongIndicators ) )
|
|
4519
4583
|
{
|
|
4520
|
-
|
|
4521
|
-
|
|
4584
|
+
scores[ lang ] = scores[ lang ] ?? 0;
|
|
4585
|
+
for( const key of indicators )
|
|
4586
|
+
{
|
|
4587
|
+
if( text.includes( key ) ) scores[ lang ] += 20;
|
|
4588
|
+
}
|
|
4522
4589
|
}
|
|
4523
4590
|
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4591
|
+
// Check groups' words now with less importance score
|
|
4592
|
+
const groups = [
|
|
4593
|
+
CodeEditor.keywords,
|
|
4594
|
+
CodeEditor.statements,
|
|
4595
|
+
CodeEditor.utils,
|
|
4596
|
+
CodeEditor.types,
|
|
4597
|
+
CodeEditor.builtIn,
|
|
4598
|
+
];
|
|
4529
4599
|
|
|
4530
|
-
for(
|
|
4600
|
+
for( const group of groups )
|
|
4531
4601
|
{
|
|
4532
|
-
for( let
|
|
4533
|
-
|
|
4602
|
+
for( let [ lang, wordList ] of Object.entries( group ) )
|
|
4603
|
+
{
|
|
4604
|
+
scores[ lang ] = scores[ lang ] ?? 0;
|
|
4605
|
+
for( let kw of wordList )
|
|
4606
|
+
if( tokenSet.has( kw ) ) scores[ lang ]++;
|
|
4607
|
+
}
|
|
4534
4608
|
}
|
|
4535
4609
|
|
|
4536
4610
|
const sorted = Object.entries( scores ).sort( ( a, b ) => b[ 1 ] - a[ 1 ] );
|
|
@@ -4561,6 +4635,33 @@ class CodeEditor
|
|
|
4561
4635
|
return true;
|
|
4562
4636
|
}
|
|
4563
4637
|
|
|
4638
|
+
swapLines( lidx, offset, cursor )
|
|
4639
|
+
{
|
|
4640
|
+
if( this.code.lines[ lidx + offset ] == undefined )
|
|
4641
|
+
{
|
|
4642
|
+
return;
|
|
4643
|
+
}
|
|
4644
|
+
|
|
4645
|
+
this._addUndoStep( cursor, true );
|
|
4646
|
+
|
|
4647
|
+
swapArrayElements( this.code.lines, lidx + offset, lidx );
|
|
4648
|
+
|
|
4649
|
+
// Process both lines
|
|
4650
|
+
this.processLine( lidx + offset );
|
|
4651
|
+
this.processLine( lidx );
|
|
4652
|
+
|
|
4653
|
+
if( offset > 0 )
|
|
4654
|
+
{
|
|
4655
|
+
this.lineDown( cursor );
|
|
4656
|
+
}
|
|
4657
|
+
else
|
|
4658
|
+
{
|
|
4659
|
+
this.lineUp( cursor );
|
|
4660
|
+
}
|
|
4661
|
+
|
|
4662
|
+
this.hideAutoCompleteBox();
|
|
4663
|
+
}
|
|
4664
|
+
|
|
4564
4665
|
restartBlink()
|
|
4565
4666
|
{
|
|
4566
4667
|
if( !this.code ) return;
|
|
@@ -4635,11 +4736,13 @@ class CodeEditor
|
|
|
4635
4736
|
LX.deleteElement( this.selections[ cursor.name ] );
|
|
4636
4737
|
delete this.selections[ cursor.name ];
|
|
4637
4738
|
delete cursor.selection;
|
|
4739
|
+
cursor.line = cursor.line; // Update current line
|
|
4638
4740
|
}
|
|
4639
4741
|
else
|
|
4640
4742
|
{
|
|
4641
4743
|
for( let cursor of this.cursors.children )
|
|
4642
4744
|
{
|
|
4745
|
+
cursor.line = cursor.line; // Update current line
|
|
4643
4746
|
LX.deleteElement( this.selections[ cursor.name ] );
|
|
4644
4747
|
delete this.selections[ cursor.name ];
|
|
4645
4748
|
delete cursor.selection;
|
|
@@ -4655,7 +4758,7 @@ class CodeEditor
|
|
|
4655
4758
|
this._removeSecondaryCursors();
|
|
4656
4759
|
|
|
4657
4760
|
var cursor = this.getCurrentCursor();
|
|
4658
|
-
this.resetCursorPos( CodeEditor.CURSOR_LEFT_TOP, cursor );
|
|
4761
|
+
this.resetCursorPos( CodeEditor.CURSOR_LEFT_TOP, cursor, true );
|
|
4659
4762
|
|
|
4660
4763
|
this.startSelection( cursor );
|
|
4661
4764
|
|
|
@@ -4671,43 +4774,49 @@ class CodeEditor
|
|
|
4671
4774
|
this.hideAutoCompleteBox();
|
|
4672
4775
|
}
|
|
4673
4776
|
|
|
4674
|
-
cursorToRight(
|
|
4777
|
+
cursorToRight( text, cursor )
|
|
4675
4778
|
{
|
|
4676
|
-
if( !
|
|
4779
|
+
if( !text || !text.length ) return;
|
|
4677
4780
|
|
|
4678
|
-
|
|
4781
|
+
const chars = text.length;
|
|
4782
|
+
const offset = chars * this.charWidth;
|
|
4783
|
+
|
|
4784
|
+
cursor._left += offset;
|
|
4679
4785
|
cursor.style.left = `calc( ${ cursor._left }px + ${ this.xPadding } )`;
|
|
4680
|
-
cursor.position
|
|
4786
|
+
cursor.position += chars;
|
|
4681
4787
|
|
|
4682
4788
|
this.restartBlink();
|
|
4683
4789
|
|
|
4684
|
-
|
|
4685
|
-
const currentScrollLeft = this.getScrollLeft();
|
|
4686
|
-
var viewportSizeX = ( this.codeScroller.clientWidth + currentScrollLeft ) - CodeEditor.LINE_GUTTER_WIDTH; // Gutter offset
|
|
4687
|
-
if( (cursor.position * this.charWidth) >= viewportSizeX )
|
|
4688
|
-
{
|
|
4689
|
-
this.setScrollLeft( currentScrollLeft + this.charWidth );
|
|
4690
|
-
}
|
|
4790
|
+
this.updateScrollLeft( cursor );
|
|
4691
4791
|
}
|
|
4692
4792
|
|
|
4693
|
-
cursorToLeft(
|
|
4793
|
+
cursorToLeft( text, cursor )
|
|
4694
4794
|
{
|
|
4695
|
-
if( !
|
|
4795
|
+
if( !text || !text.length ) return;
|
|
4696
4796
|
|
|
4697
|
-
|
|
4797
|
+
const chars = text.length;
|
|
4798
|
+
const offset = chars * this.charWidth;
|
|
4799
|
+
|
|
4800
|
+
cursor._left -= offset;
|
|
4698
4801
|
cursor._left = Math.max( cursor._left, 0 );
|
|
4699
4802
|
cursor.style.left = `calc( ${ cursor._left }px + ${ this.xPadding } )`;
|
|
4700
|
-
cursor.position
|
|
4803
|
+
cursor.position -= chars;
|
|
4701
4804
|
cursor.position = Math.max( cursor.position, 0 );
|
|
4702
|
-
this.restartBlink();
|
|
4703
4805
|
|
|
4704
|
-
|
|
4806
|
+
this.restartBlink();
|
|
4705
4807
|
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
if( ( ( cursor.position - 1 ) * this.charWidth ) < viewportSizeX )
|
|
4808
|
+
// Check if we need to add scroll; if not then we might have to reduce it
|
|
4809
|
+
if( !this.updateScrollLeft( cursor ) )
|
|
4709
4810
|
{
|
|
4710
|
-
|
|
4811
|
+
const leftMargin = this.charWidth;
|
|
4812
|
+
const cursorX = ( cursor.position * this.charWidth );
|
|
4813
|
+
const currentScrollLeft = this.getScrollLeft();
|
|
4814
|
+
|
|
4815
|
+
if( cursorX < ( currentScrollLeft + leftMargin ) )
|
|
4816
|
+
{
|
|
4817
|
+
const scroll = Math.max( cursorX - leftMargin, 0 );
|
|
4818
|
+
this.setScrollLeft( scroll );
|
|
4819
|
+
}
|
|
4711
4820
|
}
|
|
4712
4821
|
}
|
|
4713
4822
|
|
|
@@ -4720,7 +4829,7 @@ class CodeEditor
|
|
|
4720
4829
|
|
|
4721
4830
|
if( resetLeft )
|
|
4722
4831
|
{
|
|
4723
|
-
this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
|
|
4832
|
+
this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor, true );
|
|
4724
4833
|
}
|
|
4725
4834
|
|
|
4726
4835
|
const currentScrollTop = this.getScrollTop();
|
|
@@ -4740,7 +4849,7 @@ class CodeEditor
|
|
|
4740
4849
|
|
|
4741
4850
|
if( resetLeft )
|
|
4742
4851
|
{
|
|
4743
|
-
this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
|
|
4852
|
+
this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor, true );
|
|
4744
4853
|
}
|
|
4745
4854
|
|
|
4746
4855
|
const currentScrollTop = this.getScrollTop();
|
|
@@ -4759,17 +4868,26 @@ class CodeEditor
|
|
|
4759
4868
|
return;
|
|
4760
4869
|
}
|
|
4761
4870
|
|
|
4762
|
-
|
|
4871
|
+
if( reverse )
|
|
4872
|
+
{
|
|
4873
|
+
this.cursorToLeft( text, cursor )
|
|
4874
|
+
}
|
|
4875
|
+
else
|
|
4763
4876
|
{
|
|
4764
|
-
|
|
4877
|
+
this.cursorToRight( text, cursor );
|
|
4765
4878
|
}
|
|
4766
4879
|
}
|
|
4767
4880
|
|
|
4768
|
-
cursorToPosition( cursor, position )
|
|
4881
|
+
cursorToPosition( cursor, position, updateScroll = false )
|
|
4769
4882
|
{
|
|
4770
4883
|
cursor.position = position;
|
|
4771
4884
|
cursor._left = position * this.charWidth;
|
|
4772
4885
|
cursor.style.left = `calc( ${ cursor._left }px + ${ this.xPadding } )`;
|
|
4886
|
+
|
|
4887
|
+
if( updateScroll )
|
|
4888
|
+
{
|
|
4889
|
+
this.updateScrollLeft( cursor );
|
|
4890
|
+
}
|
|
4773
4891
|
}
|
|
4774
4892
|
|
|
4775
4893
|
cursorToLine( cursor, line, resetLeft = false )
|
|
@@ -4860,22 +4978,32 @@ class CodeEditor
|
|
|
4860
4978
|
LX.deleteElement( cursor );
|
|
4861
4979
|
}
|
|
4862
4980
|
|
|
4863
|
-
resetCursorPos( flag, cursor )
|
|
4981
|
+
resetCursorPos( flag, cursor, resetScroll = false )
|
|
4864
4982
|
{
|
|
4865
4983
|
cursor = cursor ?? this.getCurrentCursor();
|
|
4866
4984
|
|
|
4867
4985
|
if( flag & CodeEditor.CURSOR_LEFT )
|
|
4868
4986
|
{
|
|
4869
4987
|
cursor._left = 0;
|
|
4870
|
-
cursor.style.left = "calc(" +
|
|
4988
|
+
cursor.style.left = "calc(" + this.xPadding + ")";
|
|
4871
4989
|
cursor.position = 0;
|
|
4990
|
+
|
|
4991
|
+
if( resetScroll )
|
|
4992
|
+
{
|
|
4993
|
+
this.setScrollLeft( 0 );
|
|
4994
|
+
}
|
|
4872
4995
|
}
|
|
4873
4996
|
|
|
4874
4997
|
if( flag & CodeEditor.CURSOR_TOP )
|
|
4875
4998
|
{
|
|
4876
4999
|
cursor._top = 0;
|
|
4877
|
-
cursor.style.top =
|
|
5000
|
+
cursor.style.top = "0px";
|
|
4878
5001
|
cursor.line = 0;
|
|
5002
|
+
|
|
5003
|
+
if( resetScroll )
|
|
5004
|
+
{
|
|
5005
|
+
this.setScrollTop( 0 );
|
|
5006
|
+
}
|
|
4879
5007
|
}
|
|
4880
5008
|
}
|
|
4881
5009
|
|
|
@@ -4964,8 +5092,56 @@ class CodeEditor
|
|
|
4964
5092
|
}
|
|
4965
5093
|
}
|
|
4966
5094
|
|
|
4967
|
-
_addSpaces( n )
|
|
5095
|
+
_addSpaces( cursor, n )
|
|
4968
5096
|
{
|
|
5097
|
+
if( cursor.selection && !cursor.selection.sameLine() )
|
|
5098
|
+
{
|
|
5099
|
+
cursor.selection.invertIfNecessary();
|
|
5100
|
+
|
|
5101
|
+
for( let lidx = cursor.selection.fromY; lidx <= cursor.selection.toY; ++lidx )
|
|
5102
|
+
{
|
|
5103
|
+
// Remove indentation
|
|
5104
|
+
let lineStart = firstNonspaceIndex( this.code.lines[ lidx ] );
|
|
5105
|
+
|
|
5106
|
+
// Only tabs/spaces in the line...
|
|
5107
|
+
if( lineStart == -1 )
|
|
5108
|
+
{
|
|
5109
|
+
lineStart = this.code.lines[ lidx ].length;
|
|
5110
|
+
}
|
|
5111
|
+
|
|
5112
|
+
let indentSpaces = lineStart % this.tabSpaces;
|
|
5113
|
+
indentSpaces = indentSpaces == 0 ? this.tabSpaces : this.tabSpaces - indentSpaces;
|
|
5114
|
+
|
|
5115
|
+
const spacesString = " ".repeat( indentSpaces );
|
|
5116
|
+
|
|
5117
|
+
this.code.lines[ lidx ] = [
|
|
5118
|
+
this.code.lines[ lidx ].slice( 0, lineStart ),
|
|
5119
|
+
spacesString,
|
|
5120
|
+
this.code.lines[ lidx ].slice( lineStart )
|
|
5121
|
+
].join('');
|
|
5122
|
+
|
|
5123
|
+
this.processLine( lidx );
|
|
5124
|
+
|
|
5125
|
+
if( cursor.line === lidx )
|
|
5126
|
+
{
|
|
5127
|
+
this.cursorToString( cursor, spacesString );
|
|
5128
|
+
}
|
|
5129
|
+
|
|
5130
|
+
if( cursor.selection.fromY === lidx )
|
|
5131
|
+
{
|
|
5132
|
+
cursor.selection.fromX = Math.max( cursor.selection.fromX + indentSpaces, 0 );
|
|
5133
|
+
}
|
|
5134
|
+
if( cursor.selection.toY === lidx )
|
|
5135
|
+
{
|
|
5136
|
+
cursor.selection.toX = Math.max( cursor.selection.toX + indentSpaces, 0 );
|
|
5137
|
+
}
|
|
5138
|
+
|
|
5139
|
+
this._processSelection( cursor, undefined, true );
|
|
5140
|
+
}
|
|
5141
|
+
|
|
5142
|
+
return;
|
|
5143
|
+
}
|
|
5144
|
+
|
|
4969
5145
|
for( var i = 0; i < n; ++i )
|
|
4970
5146
|
{
|
|
4971
5147
|
this.root.dispatchEvent( new CustomEvent( 'keydown', { 'detail': {
|
|
@@ -4978,41 +5154,79 @@ class CodeEditor
|
|
|
4978
5154
|
|
|
4979
5155
|
_removeSpaces( cursor )
|
|
4980
5156
|
{
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
// Remove indentation
|
|
4984
|
-
let lineStart = firstNonspaceIndex( this.code.lines[ lidx ] );
|
|
4985
|
-
|
|
4986
|
-
// Nothing to remove... we are at the start of the line
|
|
4987
|
-
if( lineStart == 0 )
|
|
5157
|
+
if( cursor.selection )
|
|
4988
5158
|
{
|
|
4989
|
-
|
|
5159
|
+
cursor.selection.invertIfNecessary();
|
|
4990
5160
|
}
|
|
4991
5161
|
|
|
4992
|
-
|
|
4993
|
-
|
|
5162
|
+
const lCount = cursor.selection ? 1 + ( cursor.selection.toY - cursor.selection.fromY ) : 1;
|
|
5163
|
+
|
|
5164
|
+
for( let i = 0; i < lCount; ++i )
|
|
4994
5165
|
{
|
|
4995
|
-
|
|
4996
|
-
}
|
|
5166
|
+
const lidx = ( cursor.selection ? cursor.selection.fromY : cursor.line ) + i;
|
|
4997
5167
|
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
const newStart = Math.max( lineStart - indentSpaces, 0 );
|
|
5168
|
+
// Remove indentation
|
|
5169
|
+
let lineStart = firstNonspaceIndex( this.code.lines[ lidx ] );
|
|
5001
5170
|
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5171
|
+
// Nothing to remove... we are at the start of the line
|
|
5172
|
+
if( lineStart == 0 )
|
|
5173
|
+
{
|
|
5174
|
+
continue;
|
|
5175
|
+
}
|
|
5006
5176
|
|
|
5007
|
-
|
|
5177
|
+
// Only tabs/spaces in the line...
|
|
5178
|
+
if( lineStart == -1 )
|
|
5179
|
+
{
|
|
5180
|
+
lineStart = this.code.lines[ lidx ].length;
|
|
5181
|
+
}
|
|
5008
5182
|
|
|
5009
|
-
|
|
5183
|
+
let indentSpaces = lineStart % this.tabSpaces;
|
|
5184
|
+
indentSpaces = indentSpaces == 0 ? this.tabSpaces : indentSpaces;
|
|
5185
|
+
const newStart = Math.max( lineStart - indentSpaces, 0 );
|
|
5010
5186
|
|
|
5011
|
-
|
|
5187
|
+
this.code.lines[ lidx ] = [
|
|
5188
|
+
this.code.lines[ lidx ].slice( 0, newStart ),
|
|
5189
|
+
this.code.lines[ lidx ].slice( lineStart )
|
|
5190
|
+
].join('');
|
|
5191
|
+
|
|
5192
|
+
this.processLine( lidx );
|
|
5193
|
+
|
|
5194
|
+
if( cursor.line === lidx )
|
|
5195
|
+
{
|
|
5196
|
+
this.cursorToString( cursor, " ".repeat( indentSpaces ), true );
|
|
5197
|
+
}
|
|
5198
|
+
|
|
5199
|
+
if( cursor.selection )
|
|
5200
|
+
{
|
|
5201
|
+
if( cursor.selection.fromY === lidx )
|
|
5202
|
+
{
|
|
5203
|
+
cursor.selection.fromX = Math.max( cursor.selection.fromX - indentSpaces, 0 );
|
|
5204
|
+
}
|
|
5205
|
+
if( cursor.selection.toY === lidx )
|
|
5206
|
+
{
|
|
5207
|
+
cursor.selection.toX = Math.max( cursor.selection.toX - indentSpaces, 0 );
|
|
5208
|
+
}
|
|
5209
|
+
|
|
5210
|
+
this._processSelection( cursor, undefined, true );
|
|
5211
|
+
}
|
|
5212
|
+
}
|
|
5213
|
+
}
|
|
5214
|
+
|
|
5215
|
+
updateScrollLeft( cursor )
|
|
5216
|
+
{
|
|
5217
|
+
cursor = cursor ?? this.getCurrentCursor();
|
|
5218
|
+
|
|
5219
|
+
const rightMargin = this.charWidth;
|
|
5220
|
+
const cursorX = ( cursor.position * this.charWidth );
|
|
5221
|
+
const currentScrollLeft = this.getScrollLeft();
|
|
5222
|
+
const viewportSizeX = this.codeScroller.clientWidth - CodeEditor.LINE_GUTTER_WIDTH; // Gutter offset
|
|
5223
|
+
const viewportX = viewportSizeX + currentScrollLeft;
|
|
5224
|
+
|
|
5225
|
+
if( cursorX >= ( viewportX - rightMargin ) )
|
|
5012
5226
|
{
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5227
|
+
const scroll = Math.max( cursorX - ( viewportSizeX - rightMargin ), 0 );
|
|
5228
|
+
this.setScrollLeft( scroll );
|
|
5229
|
+
return true;
|
|
5016
5230
|
}
|
|
5017
5231
|
}
|
|
5018
5232
|
|
|
@@ -5034,7 +5248,7 @@ class CodeEditor
|
|
|
5034
5248
|
LX.doAsync( () => {
|
|
5035
5249
|
this.codeScroller.scrollLeft = value;
|
|
5036
5250
|
this.setScrollBarValue( 'horizontal', 0 );
|
|
5037
|
-
},
|
|
5251
|
+
}, 10 );
|
|
5038
5252
|
}
|
|
5039
5253
|
|
|
5040
5254
|
setScrollTop( value )
|
|
@@ -5043,7 +5257,7 @@ class CodeEditor
|
|
|
5043
5257
|
LX.doAsync( () => {
|
|
5044
5258
|
this.codeScroller.scrollTop = value;
|
|
5045
5259
|
this.setScrollBarValue( 'vertical' );
|
|
5046
|
-
},
|
|
5260
|
+
}, 10 );
|
|
5047
5261
|
}
|
|
5048
5262
|
|
|
5049
5263
|
resize( flag = CodeEditor.RESIZE_SCROLLBAR_H_V, pMaxLength, onResize )
|
|
@@ -5130,14 +5344,16 @@ class CodeEditor
|
|
|
5130
5344
|
{
|
|
5131
5345
|
if( type == 'vertical' )
|
|
5132
5346
|
{
|
|
5133
|
-
const scrollBarHeight = this.vScrollbar.thumb.parentElement.offsetHeight;
|
|
5134
|
-
const scrollThumbHeight = this.vScrollbar.thumb.offsetHeight;
|
|
5135
|
-
|
|
5136
5347
|
const scrollHeight = this.codeScroller.scrollHeight - this.codeScroller.clientHeight;
|
|
5137
|
-
const currentScroll = this.codeScroller.scrollTop;
|
|
5138
5348
|
|
|
5139
|
-
|
|
5140
|
-
|
|
5349
|
+
if( scrollHeight > 0 )
|
|
5350
|
+
{
|
|
5351
|
+
const scrollBarHeight = this.vScrollbar.thumb.parentElement.offsetHeight;
|
|
5352
|
+
const scrollThumbHeight = this.vScrollbar.thumb.offsetHeight;
|
|
5353
|
+
const currentScroll = this.codeScroller.scrollTop;
|
|
5354
|
+
this.vScrollbar.thumb._top = ( currentScroll / scrollHeight ) * ( scrollBarHeight - scrollThumbHeight );
|
|
5355
|
+
this.vScrollbar.thumb.style.top = this.vScrollbar.thumb._top + "px";
|
|
5356
|
+
}
|
|
5141
5357
|
}
|
|
5142
5358
|
else
|
|
5143
5359
|
{
|
|
@@ -5146,14 +5362,16 @@ class CodeEditor
|
|
|
5146
5362
|
this.codeScroller.scrollLeft += value;
|
|
5147
5363
|
}
|
|
5148
5364
|
|
|
5149
|
-
|
|
5150
|
-
const scrollThumbWidth = this.hScrollbar.thumb.offsetWidth;
|
|
5151
|
-
|
|
5365
|
+
// Only when scroll is needed
|
|
5152
5366
|
const scrollWidth = this.codeScroller.scrollWidth - this.codeScroller.clientWidth;
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5367
|
+
if( scrollWidth > 0 )
|
|
5368
|
+
{
|
|
5369
|
+
const scrollBarWidth = this.hScrollbar.thumb.parentElement.offsetWidth;
|
|
5370
|
+
const scrollThumbWidth = this.hScrollbar.thumb.offsetWidth;
|
|
5371
|
+
const currentScroll = this.codeScroller.scrollLeft;
|
|
5372
|
+
this.hScrollbar.thumb._left = ( currentScroll / scrollWidth ) * ( scrollBarWidth - scrollThumbWidth );
|
|
5373
|
+
this.hScrollbar.thumb.style.left = this.hScrollbar.thumb._left + "px";
|
|
5374
|
+
}
|
|
5157
5375
|
}
|
|
5158
5376
|
}
|
|
5159
5377
|
|
|
@@ -5175,7 +5393,6 @@ class CodeEditor
|
|
|
5175
5393
|
const currentScroll = (this.hScrollbar.thumb._left * scrollWidth) / ( scrollBarWidth - scrollThumbWidth );
|
|
5176
5394
|
this.codeScroller.scrollLeft = currentScroll;
|
|
5177
5395
|
|
|
5178
|
-
|
|
5179
5396
|
this._discardScroll = true;
|
|
5180
5397
|
}
|
|
5181
5398
|
|
|
@@ -5253,7 +5470,7 @@ class CodeEditor
|
|
|
5253
5470
|
return [ word, from, to ];
|
|
5254
5471
|
}
|
|
5255
5472
|
|
|
5256
|
-
_measureChar( char = "
|
|
5473
|
+
_measureChar( char = "M", useFloating = true, getBB = false )
|
|
5257
5474
|
{
|
|
5258
5475
|
const parentContainer = LX.makeContainer( null, "lexcodeeditor", "", document.body );
|
|
5259
5476
|
const container = LX.makeContainer( null, "code", "", parentContainer );
|
|
@@ -5440,14 +5657,16 @@ class CodeEditor
|
|
|
5440
5657
|
return;
|
|
5441
5658
|
}
|
|
5442
5659
|
|
|
5660
|
+
const maxX = this.codeScroller.clientWidth - 256; // Viewport - box width
|
|
5661
|
+
|
|
5443
5662
|
// Select always first option
|
|
5444
5663
|
this.autocomplete.firstChild.classList.add( 'selected' );
|
|
5445
5664
|
|
|
5446
5665
|
// Show box
|
|
5447
5666
|
this.autocomplete.classList.toggle( 'show', true );
|
|
5448
5667
|
this.autocomplete.classList.toggle( 'no-scrollbar', !( this.autocomplete.scrollHeight > this.autocomplete.offsetHeight ) );
|
|
5449
|
-
this.autocomplete.style.left = (cursor._left + CodeEditor.LINE_GUTTER_WIDTH - this.getScrollLeft())
|
|
5450
|
-
this.autocomplete.style.top = (cursor._top +
|
|
5668
|
+
this.autocomplete.style.left = `${ Math.min( cursor._left + CodeEditor.LINE_GUTTER_WIDTH - this.getScrollLeft(), maxX ) }px`;
|
|
5669
|
+
this.autocomplete.style.top = `${ ( cursor._top + this._verticalTopOffset + this.lineHeight - this.getScrollTop() ) }px`;
|
|
5451
5670
|
|
|
5452
5671
|
this.isAutoCompleteActive = true;
|
|
5453
5672
|
}
|
|
@@ -5528,21 +5747,33 @@ class CodeEditor
|
|
|
5528
5747
|
|
|
5529
5748
|
const [ word, idx ] = this._getSelectedAutoComplete();
|
|
5530
5749
|
const offset = dir == 'down' ? 1 : -1;
|
|
5750
|
+
const fIdx = idx + offset;
|
|
5751
|
+
|
|
5752
|
+
const autocompleteRowHeight = 22;
|
|
5753
|
+
const autocompleteHeight = 132;
|
|
5531
5754
|
|
|
5532
5755
|
if( dir == 'down' )
|
|
5533
5756
|
{
|
|
5534
|
-
if(
|
|
5757
|
+
if( fIdx >= this.autocomplete.childElementCount ) return;
|
|
5758
|
+
|
|
5759
|
+
if( ( ( idx + offset * 2 ) * autocompleteRowHeight ) - this.autocomplete.scrollTop > autocompleteHeight )
|
|
5760
|
+
{
|
|
5761
|
+
this.autocomplete.scrollTop += autocompleteRowHeight;
|
|
5762
|
+
}
|
|
5535
5763
|
}
|
|
5536
5764
|
else if( dir == 'up')
|
|
5537
5765
|
{
|
|
5538
|
-
if(
|
|
5539
|
-
}
|
|
5766
|
+
if( fIdx < 0 ) return;
|
|
5540
5767
|
|
|
5541
|
-
|
|
5768
|
+
if( ( fIdx * autocompleteRowHeight ) < this.autocomplete.scrollTop )
|
|
5769
|
+
{
|
|
5770
|
+
this.autocomplete.scrollTop -= autocompleteRowHeight;
|
|
5771
|
+
}
|
|
5772
|
+
}
|
|
5542
5773
|
|
|
5543
5774
|
// Remove selected from the current word and add it to the next one
|
|
5544
|
-
this.autocomplete.childNodes[ idx ].classList.remove('selected');
|
|
5545
|
-
this.autocomplete.childNodes[ idx + offset ].classList.add('selected');
|
|
5775
|
+
this.autocomplete.childNodes[ idx ].classList.remove( 'selected' );
|
|
5776
|
+
this.autocomplete.childNodes[ idx + offset ].classList.add( 'selected' );
|
|
5546
5777
|
}
|
|
5547
5778
|
|
|
5548
5779
|
showSearchBox( clear )
|
|
@@ -5844,7 +6075,7 @@ class CodeEditor
|
|
|
5844
6075
|
this.fontSize = size;
|
|
5845
6076
|
const r = document.querySelector( ':root' );
|
|
5846
6077
|
r.style.setProperty( "--code-editor-font-size", `${ this.fontSize }px` );
|
|
5847
|
-
this.charWidth = this._measureChar(
|
|
6078
|
+
this.charWidth = this._measureChar();
|
|
5848
6079
|
|
|
5849
6080
|
window.localStorage.setItem( "lexcodeeditor-font-size", this.fontSize );
|
|
5850
6081
|
|
|
@@ -5959,9 +6190,9 @@ CodeEditor.declarationKeywords =
|
|
|
5959
6190
|
CodeEditor.keywords =
|
|
5960
6191
|
{
|
|
5961
6192
|
'JavaScript': ['var', 'let', 'const', 'this', 'in', 'of', 'true', 'false', 'new', 'function', 'NaN', 'static', 'class', 'constructor', 'null', 'typeof', 'debugger', 'abstract',
|
|
5962
|
-
'arguments', 'extends', 'instanceof', 'Infinity'],
|
|
6193
|
+
'arguments', 'extends', 'instanceof', 'Infinity', 'get'],
|
|
5963
6194
|
'TypeScript': ['var', 'let', 'const', 'this', 'in', 'of', 'true', 'false', 'new', 'function', 'class', 'extends', 'instanceof', 'Infinity', 'private', 'public', 'protected', 'interface',
|
|
5964
|
-
'enum', 'type'],
|
|
6195
|
+
'enum', 'type', 'get'],
|
|
5965
6196
|
'C': ['int', 'float', 'double', 'long', 'short', 'char', 'const', 'void', 'true', 'false', 'auto', 'struct', 'typedef', 'signed', 'volatile', 'unsigned', 'static', 'extern', 'enum', 'register',
|
|
5966
6197
|
'union'],
|
|
5967
6198
|
'C++': [...CodeEditor.nativeTypes["C++"], 'const', 'static_cast', 'dynamic_cast', 'new', 'delete', 'true', 'false', 'auto', 'class', 'struct', 'typedef', 'nullptr',
|
|
@@ -6011,7 +6242,8 @@ CodeEditor.types =
|
|
|
6011
6242
|
'JavaScript': ['Object', 'String', 'Function', 'Boolean', 'Symbol', 'Error', 'Number', 'TextEncoder', 'TextDecoder', 'Array', 'ArrayBuffer', 'InputEvent', 'MouseEvent',
|
|
6012
6243
|
'Int8Array', 'Int16Array', 'Int32Array', 'Float32Array', 'Float64Array', 'Element'],
|
|
6013
6244
|
'TypeScript': ['arguments', 'constructor', 'null', 'typeof', 'debugger', 'abstract', 'Object', 'string', 'String', 'Function', 'Boolean', 'boolean', 'Error', 'Number', 'number', 'TextEncoder',
|
|
6014
|
-
'TextDecoder', 'Array', 'ArrayBuffer', 'InputEvent', 'MouseEvent', 'Int8Array', 'Int16Array', 'Int32Array', 'Float32Array', 'Float64Array', 'Element'
|
|
6245
|
+
'TextDecoder', 'Array', 'ArrayBuffer', 'InputEvent', 'MouseEvent', 'Int8Array', 'Int16Array', 'Int32Array', 'Float32Array', 'Float64Array', 'Element', 'bigint', 'unknown', 'any',
|
|
6246
|
+
'Record'],
|
|
6015
6247
|
'Rust': ['u128'],
|
|
6016
6248
|
'Python': ['int', 'type', 'float', 'map', 'list', 'ArithmeticError', 'AssertionError', 'AttributeError', 'Exception', 'EOFError', 'FloatingPointError', 'GeneratorExit',
|
|
6017
6249
|
'ImportError', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'NotImplementedError', 'OSError',
|
|
@@ -6034,8 +6266,8 @@ CodeEditor.builtIn =
|
|
|
6034
6266
|
|
|
6035
6267
|
CodeEditor.statements =
|
|
6036
6268
|
{
|
|
6037
|
-
'JavaScript': ['for', 'if', 'else', 'case', 'switch', 'return', 'while', 'continue', 'break', 'do', 'import', 'from', 'throw', 'async', 'try', 'catch', 'await'],
|
|
6038
|
-
'TypeScript': ['for', 'if', 'else', 'case', 'switch', 'return', 'while', 'continue', 'break', 'do', 'import', 'from', 'throw', 'async', 'try', 'catch', 'await', 'as'],
|
|
6269
|
+
'JavaScript': ['for', 'if', 'else', 'case', 'switch', 'return', 'while', 'continue', 'break', 'do', 'import', 'default', 'export', 'from', 'throw', 'async', 'try', 'catch', 'await', 'as'],
|
|
6270
|
+
'TypeScript': ['for', 'if', 'else', 'case', 'switch', 'return', 'while', 'continue', 'break', 'do', 'import', 'default', 'export', 'from', 'throw', 'async', 'try', 'catch', 'await', 'as'],
|
|
6039
6271
|
'CSS': ['@', 'import'],
|
|
6040
6272
|
'C': ['for', 'if', 'else', 'return', 'continue', 'break', 'case', 'switch', 'while', 'using', 'default', 'goto', 'do'],
|
|
6041
6273
|
'C++': ['std', 'for', 'if', 'else', 'return', 'continue', 'break', 'case', 'switch', 'while', 'using', 'glm', 'spdlog', 'default'],
|