lexgui 0.1.22 → 0.1.24
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/components/codeeditor.js +489 -223
- package/build/lexgui.css +51 -12
- package/build/lexgui.js +1 -1
- package/build/lexgui.module.js +1 -1
- package/changelog.md +40 -14
- package/examples/code_editor.html +11 -8
- package/package.json +1 -1
|
@@ -201,8 +201,8 @@ class CodeEditor {
|
|
|
201
201
|
static WORD_TYPE_METHOD = 0;
|
|
202
202
|
static WORD_TYPE_CLASS = 1;
|
|
203
203
|
|
|
204
|
-
static CODE_MAX_FONT_SIZE =
|
|
205
|
-
static CODE_MIN_FONT_SIZE =
|
|
204
|
+
static CODE_MAX_FONT_SIZE = 22;
|
|
205
|
+
static CODE_MIN_FONT_SIZE = 9;
|
|
206
206
|
|
|
207
207
|
/**
|
|
208
208
|
* @param {*} options
|
|
@@ -333,42 +333,15 @@ class CodeEditor {
|
|
|
333
333
|
|
|
334
334
|
// Add main cursor
|
|
335
335
|
{
|
|
336
|
-
|
|
337
|
-
cursor.className = "cursor";
|
|
338
|
-
cursor.innerHTML = " ";
|
|
339
|
-
cursor._left = 0;
|
|
340
|
-
cursor.style.left = this.xPadding;
|
|
341
|
-
cursor._top = 0;
|
|
342
|
-
cursor.style.top = cursor._top + "px";
|
|
343
|
-
cursor._position = 0;
|
|
344
|
-
cursor._line = 0;
|
|
345
|
-
cursor.print = (function() { console.log( this.line, this.position ) }).bind( cursor );
|
|
336
|
+
this._addCursor( 0, 0, true, true );
|
|
346
337
|
|
|
347
338
|
Object.defineProperty( this, 'line', {
|
|
348
|
-
get: (v) => { return
|
|
339
|
+
get: (v) => { return this._getCurrentCursor().line }
|
|
349
340
|
} );
|
|
350
|
-
|
|
341
|
+
|
|
351
342
|
Object.defineProperty( this, 'position', {
|
|
352
|
-
get: (v) => { return
|
|
353
|
-
} );
|
|
354
|
-
|
|
355
|
-
Object.defineProperty( cursor, 'line', {
|
|
356
|
-
get: (v) => { return this._line },
|
|
357
|
-
set: (v) => {
|
|
358
|
-
this._line = v;
|
|
359
|
-
this._setActiveLine( v );
|
|
360
|
-
}
|
|
361
|
-
} );
|
|
362
|
-
|
|
363
|
-
Object.defineProperty( cursor, 'position', {
|
|
364
|
-
get: (v) => { return this._position },
|
|
365
|
-
set: (v) => {
|
|
366
|
-
this._position = v;
|
|
367
|
-
this._updateDataInfoPanel( "@cursor-pos", "Col " + v );
|
|
368
|
-
}
|
|
343
|
+
get: (v) => { return this._getCurrentCursor().position }
|
|
369
344
|
} );
|
|
370
|
-
|
|
371
|
-
this.cursors.appendChild( cursor );
|
|
372
345
|
}
|
|
373
346
|
|
|
374
347
|
// Scroll stuff
|
|
@@ -534,6 +507,7 @@ class CodeEditor {
|
|
|
534
507
|
this.tabSpaces = 4;
|
|
535
508
|
this.maxUndoSteps = 16;
|
|
536
509
|
this.lineHeight = 20;
|
|
510
|
+
this.charWidth = 7; // To update later depending on size..
|
|
537
511
|
this.defaultSingleLineCommentToken = '//';
|
|
538
512
|
this.defaultBlockCommentTokens = [ '/*', '*/' ];
|
|
539
513
|
this._lastTime = null;
|
|
@@ -624,6 +598,7 @@ class CodeEditor {
|
|
|
624
598
|
};
|
|
625
599
|
this.statementsAndDeclarations = {
|
|
626
600
|
'JavaScript': ['for', 'if', 'else', 'case', 'switch', 'return', 'while', 'continue', 'break', 'do', 'import', 'from', 'throw', 'async', 'try', 'catch', 'await'],
|
|
601
|
+
'CSS': ['@', 'import'],
|
|
627
602
|
'C++': ['std', 'for', 'if', 'else', 'return', 'continue', 'break', 'case', 'switch', 'while', 'using', 'glm', 'spdlog'],
|
|
628
603
|
'GLSL': ['for', 'if', 'else', 'return', 'continue', 'break'],
|
|
629
604
|
'WGSL': ['const','for', 'if', 'else', 'return', 'continue', 'break', 'storage', 'read', 'uniform'],
|
|
@@ -659,6 +634,7 @@ class CodeEditor {
|
|
|
659
634
|
this.action( 'Escape', false, ( ln, cursor, e ) => {
|
|
660
635
|
this.hideAutoCompleteBox();
|
|
661
636
|
this.hideSearchBox();
|
|
637
|
+
this._removeSecondaryCursors();
|
|
662
638
|
});
|
|
663
639
|
|
|
664
640
|
this.action( 'Backspace', false, ( ln, cursor, e ) => {
|
|
@@ -704,7 +680,7 @@ class CodeEditor {
|
|
|
704
680
|
}
|
|
705
681
|
else if( this.code.lines[ ln - 1 ] != undefined ) {
|
|
706
682
|
|
|
707
|
-
this.lineUp();
|
|
683
|
+
this.lineUp( cursor );
|
|
708
684
|
e.cancelShift = true;
|
|
709
685
|
this.actions[ 'End' ].callback( cursor.line, cursor, e );
|
|
710
686
|
// Move line on top
|
|
@@ -759,18 +735,22 @@ class CodeEditor {
|
|
|
759
735
|
const prestring = this.code.lines[ ln ].substring( 0, idx );
|
|
760
736
|
let lastX = cursor.position;
|
|
761
737
|
|
|
762
|
-
this.resetCursorPos( CodeEditor.CURSOR_LEFT );
|
|
738
|
+
this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
|
|
763
739
|
if(idx > 0) this.cursorToString( cursor, prestring );
|
|
764
740
|
this.setScrollLeft( 0 );
|
|
765
741
|
|
|
742
|
+
// Merge cursors
|
|
743
|
+
this.mergeCursors( ln );
|
|
744
|
+
|
|
766
745
|
if( e.shiftKey && !e.cancelShift )
|
|
767
746
|
{
|
|
768
747
|
// Get last selection range
|
|
769
748
|
if( this.selection )
|
|
770
|
-
|
|
749
|
+
lastX += this.selection.chars;
|
|
771
750
|
|
|
772
751
|
if( !this.selection )
|
|
773
752
|
this.startSelection( cursor );
|
|
753
|
+
|
|
774
754
|
var string = this.code.lines[ ln ].substring( idx, lastX );
|
|
775
755
|
if( this.selection.sameLine() )
|
|
776
756
|
this.selection.selectInline( idx, cursor.line, this.measureString( string ) );
|
|
@@ -793,18 +773,21 @@ class CodeEditor {
|
|
|
793
773
|
this.selection.selectInline(cursor.position, cursor.line, this.measureString( string ));
|
|
794
774
|
else
|
|
795
775
|
{
|
|
796
|
-
this.resetCursorPos( CodeEditor.CURSOR_LEFT );
|
|
776
|
+
this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
|
|
797
777
|
this.cursorToString( cursor, this.code.lines[ ln ] );
|
|
798
778
|
this.processSelection( e );
|
|
799
779
|
}
|
|
800
780
|
} else if( !e.keepSelection )
|
|
801
781
|
this.endSelection();
|
|
802
782
|
|
|
803
|
-
this.resetCursorPos( CodeEditor.CURSOR_LEFT );
|
|
783
|
+
this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
|
|
804
784
|
this.cursorToString( cursor, this.code.lines[ ln ] );
|
|
805
785
|
|
|
806
786
|
const last_char = ( this.code.clientWidth / this.charWidth )|0;
|
|
807
787
|
this.setScrollLeft( cursor.position >= last_char ? ( cursor.position - last_char ) * this.charWidth : 0 );
|
|
788
|
+
|
|
789
|
+
// Merge cursors
|
|
790
|
+
this.mergeCursors( ln );
|
|
808
791
|
});
|
|
809
792
|
|
|
810
793
|
this.action( 'Enter', true, ( ln, cursor, e ) => {
|
|
@@ -830,6 +813,7 @@ class CodeEditor {
|
|
|
830
813
|
this.code.lines.splice( cursor.line + 1, 0, "" );
|
|
831
814
|
this.code.lines[cursor.line + 1] = this.code.lines[ ln ].substr( cursor.position ); // new line (below)
|
|
832
815
|
this.code.lines[ ln ] = this.code.lines[ ln ].substr( 0, cursor.position ); // line above
|
|
816
|
+
|
|
833
817
|
this.lineDown( cursor, true );
|
|
834
818
|
|
|
835
819
|
// Check indentation
|
|
@@ -856,7 +840,7 @@ class CodeEditor {
|
|
|
856
840
|
if( !this.selection )
|
|
857
841
|
this.startSelection( cursor );
|
|
858
842
|
|
|
859
|
-
this.lineUp();
|
|
843
|
+
this.lineUp( cursor );
|
|
860
844
|
|
|
861
845
|
var letter = this.getCharAtPos( cursor );
|
|
862
846
|
if( !letter ) {
|
|
@@ -867,7 +851,7 @@ class CodeEditor {
|
|
|
867
851
|
|
|
868
852
|
} else {
|
|
869
853
|
this.endSelection();
|
|
870
|
-
this.lineUp();
|
|
854
|
+
this.lineUp( cursor );
|
|
871
855
|
// Go to end of line if out of line
|
|
872
856
|
var letter = this.getCharAtPos( cursor );
|
|
873
857
|
if( !letter ) this.actions['End'].callback( cursor.line, cursor, e );
|
|
@@ -888,24 +872,20 @@ class CodeEditor {
|
|
|
888
872
|
if( e.shiftKey ) {
|
|
889
873
|
if( !this.selection )
|
|
890
874
|
this.startSelection( cursor );
|
|
875
|
+
} else {
|
|
876
|
+
this.endSelection();
|
|
877
|
+
}
|
|
891
878
|
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
879
|
+
const canGoDown = this.lineDown( cursor );
|
|
880
|
+
const letter = this.getCharAtPos( cursor );
|
|
881
|
+
|
|
882
|
+
// Go to end of line if out of range
|
|
883
|
+
if( !letter || !canGoDown ) {
|
|
884
|
+
this.actions[ 'End' ].callback( cursor.line, cursor, e );
|
|
885
|
+
}
|
|
898
886
|
|
|
887
|
+
if( e.shiftKey ) {
|
|
899
888
|
this.processSelection( e );
|
|
900
|
-
} else {
|
|
901
|
-
|
|
902
|
-
if( this.code.lines[ ln + 1 ] == undefined )
|
|
903
|
-
return;
|
|
904
|
-
this.endSelection();
|
|
905
|
-
this.lineDown( cursor );
|
|
906
|
-
// Go to end of line if out of line
|
|
907
|
-
var letter = this.getCharAtPos( cursor );
|
|
908
|
-
if( !letter ) this.actions['End'].callback(cursor.line, cursor, e);
|
|
909
889
|
}
|
|
910
890
|
}
|
|
911
891
|
// Move down autocomplete selection
|
|
@@ -960,7 +940,7 @@ class CodeEditor {
|
|
|
960
940
|
}
|
|
961
941
|
else {
|
|
962
942
|
this.selection.invertIfNecessary();
|
|
963
|
-
this.resetCursorPos( CodeEditor.CURSOR_LEFT | CodeEditor.CURSOR_TOP );
|
|
943
|
+
this.resetCursorPos( CodeEditor.CURSOR_LEFT | CodeEditor.CURSOR_TOP, cursor );
|
|
964
944
|
this.cursorToLine( cursor, this.selection.fromY, true );
|
|
965
945
|
this.cursorToPosition( cursor, this.selection.fromX );
|
|
966
946
|
this.endSelection();
|
|
@@ -986,7 +966,7 @@ class CodeEditor {
|
|
|
986
966
|
|
|
987
967
|
// Nothing to do..
|
|
988
968
|
if( cursor.line == this.code.lines.length - 1 &&
|
|
989
|
-
cursor.position == this.code.lines[ cursor.line
|
|
969
|
+
cursor.position == this.code.lines[ cursor.line ].length )
|
|
990
970
|
return;
|
|
991
971
|
|
|
992
972
|
if( e.metaKey ) { // Apple devices (Command)
|
|
@@ -1002,7 +982,7 @@ class CodeEditor {
|
|
|
1002
982
|
// Selections...
|
|
1003
983
|
if( e.shiftKey ) { if( !this.selection ) this.startSelection( cursor ); }
|
|
1004
984
|
else this.endSelection();
|
|
1005
|
-
this.cursorToString( cursor, substr);
|
|
985
|
+
this.cursorToString( cursor, substr );
|
|
1006
986
|
if( e.shiftKey ) this.processSelection( e );
|
|
1007
987
|
} else {
|
|
1008
988
|
var letter = this.getCharAtPos( cursor );
|
|
@@ -1020,7 +1000,7 @@ class CodeEditor {
|
|
|
1020
1000
|
else
|
|
1021
1001
|
{
|
|
1022
1002
|
this.selection.invertIfNecessary();
|
|
1023
|
-
this.resetCursorPos( CodeEditor.CURSOR_LEFT | CodeEditor.CURSOR_TOP );
|
|
1003
|
+
this.resetCursorPos( CodeEditor.CURSOR_LEFT | CodeEditor.CURSOR_TOP, cursor );
|
|
1024
1004
|
this.cursorToLine( cursor, this.selection.toY );
|
|
1025
1005
|
this.cursorToPosition( cursor, this.selection.toX );
|
|
1026
1006
|
this.endSelection();
|
|
@@ -1106,7 +1086,8 @@ class CodeEditor {
|
|
|
1106
1086
|
}
|
|
1107
1087
|
|
|
1108
1088
|
getText( min ) {
|
|
1109
|
-
|
|
1089
|
+
|
|
1090
|
+
return this.code.lines.join( min ? ' ' : '\n' );
|
|
1110
1091
|
}
|
|
1111
1092
|
|
|
1112
1093
|
// This can be used to empty all text...
|
|
@@ -1115,7 +1096,9 @@ class CodeEditor {
|
|
|
1115
1096
|
let new_lines = text.split( '\n' );
|
|
1116
1097
|
this.code.lines = [].concat( new_lines );
|
|
1117
1098
|
|
|
1118
|
-
|
|
1099
|
+
this._removeSecondaryCursors();
|
|
1100
|
+
|
|
1101
|
+
let cursor = this._getCurrentCursor( true );
|
|
1119
1102
|
let lastLine = new_lines.pop();
|
|
1120
1103
|
|
|
1121
1104
|
this.cursorToLine( cursor, new_lines.length ); // Already substracted 1
|
|
@@ -1128,9 +1111,8 @@ class CodeEditor {
|
|
|
1128
1111
|
}
|
|
1129
1112
|
}
|
|
1130
1113
|
|
|
1131
|
-
appendText( text ) {
|
|
1114
|
+
appendText( text, cursor ) {
|
|
1132
1115
|
|
|
1133
|
-
let cursor = this.cursors.children[ 0 ];
|
|
1134
1116
|
let lidx = cursor.line;
|
|
1135
1117
|
|
|
1136
1118
|
if( this.selection ) {
|
|
@@ -1234,8 +1216,82 @@ class CodeEditor {
|
|
|
1234
1216
|
}
|
|
1235
1217
|
}
|
|
1236
1218
|
|
|
1219
|
+
_addCursor( line = 0, position = 0, force, isMain = false ) {
|
|
1220
|
+
|
|
1221
|
+
// If cursor in that position exists, remove it instead..
|
|
1222
|
+
const exists = Array.from( this.cursors.children ).find( v => v.position == position && v.line == line );
|
|
1223
|
+
if( exists && !force )
|
|
1224
|
+
{
|
|
1225
|
+
if( !exists.isMain )
|
|
1226
|
+
exists.remove();
|
|
1227
|
+
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
let cursor = document.createElement( 'div' );
|
|
1232
|
+
cursor.className = "cursor";
|
|
1233
|
+
cursor.innerHTML = " ";
|
|
1234
|
+
cursor.isMain = isMain;
|
|
1235
|
+
cursor._left = position * this.charWidth;
|
|
1236
|
+
cursor.style.left = "calc( " + cursor._left + "px + " + this.xPadding + " )";
|
|
1237
|
+
cursor._top = line * this.lineHeight;
|
|
1238
|
+
cursor.style.top = cursor._top + "px";
|
|
1239
|
+
cursor._position = position;
|
|
1240
|
+
cursor._line = line;
|
|
1241
|
+
cursor.print = (function() { console.log( this._line, this._position ) }).bind( cursor );
|
|
1242
|
+
cursor.isLast = (function() { return this.cursors.lastChild == cursor; }).bind( this );
|
|
1243
|
+
|
|
1244
|
+
Object.defineProperty( cursor, 'line', {
|
|
1245
|
+
get: (v) => { return cursor._line },
|
|
1246
|
+
set: (v) => {
|
|
1247
|
+
cursor._line = v;
|
|
1248
|
+
if( cursor.isMain ) this._setActiveLine( v );
|
|
1249
|
+
}
|
|
1250
|
+
} );
|
|
1251
|
+
|
|
1252
|
+
Object.defineProperty( cursor, 'position', {
|
|
1253
|
+
get: (v) => { return cursor._position },
|
|
1254
|
+
set: (v) => {
|
|
1255
|
+
cursor._position = v;
|
|
1256
|
+
if( cursor.isMain ) this._updateDataInfoPanel( "@cursor-pos", "Col " + v );
|
|
1257
|
+
}
|
|
1258
|
+
} );
|
|
1259
|
+
|
|
1260
|
+
this.cursors.appendChild( cursor );
|
|
1261
|
+
|
|
1262
|
+
return cursor;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
_getCurrentCursor( removeOthers ) {
|
|
1266
|
+
|
|
1267
|
+
if( removeOthers )
|
|
1268
|
+
{
|
|
1269
|
+
this._removeSecondaryCursors();
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
return this.cursors.children[ 0 ];
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
_removeSecondaryCursors() {
|
|
1276
|
+
|
|
1277
|
+
while( this.cursors.childElementCount > 1 )
|
|
1278
|
+
this.cursors.lastChild.remove();
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
_logCursors() {
|
|
1282
|
+
|
|
1283
|
+
for( let cursor of this.cursors.children )
|
|
1284
|
+
{
|
|
1285
|
+
cursor.print();
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1237
1289
|
_addUndoStep( cursor, force, deleteRedo = true ) {
|
|
1238
1290
|
|
|
1291
|
+
// Only the mainc cursor stores undo steps
|
|
1292
|
+
if( !cursor.isMain )
|
|
1293
|
+
return;
|
|
1294
|
+
|
|
1239
1295
|
const d = new Date();
|
|
1240
1296
|
const current = d.getTime();
|
|
1241
1297
|
|
|
@@ -1244,7 +1300,7 @@ class CodeEditor {
|
|
|
1244
1300
|
if( !this._lastTime ) {
|
|
1245
1301
|
this._lastTime = current;
|
|
1246
1302
|
} else {
|
|
1247
|
-
if( ( current - this._lastTime ) >
|
|
1303
|
+
if( ( current - this._lastTime ) > 2000 ){
|
|
1248
1304
|
this._lastTime = null;
|
|
1249
1305
|
} else {
|
|
1250
1306
|
// If time not enough, reset timer
|
|
@@ -1260,28 +1316,80 @@ class CodeEditor {
|
|
|
1260
1316
|
this.code.redoSteps.length = 0;
|
|
1261
1317
|
}
|
|
1262
1318
|
|
|
1263
|
-
var cursor = cursor ?? this.cursors.children[ 0 ];
|
|
1264
|
-
|
|
1265
1319
|
this.code.undoSteps.push( {
|
|
1266
1320
|
lines: LX.deepCopy( this.code.lines ),
|
|
1267
|
-
|
|
1268
|
-
line: cursor.line,
|
|
1269
|
-
position: cursor.position
|
|
1321
|
+
cursors: this.saveCursors()
|
|
1270
1322
|
} );
|
|
1271
1323
|
}
|
|
1272
1324
|
|
|
1325
|
+
_doUndo( cursor ) {
|
|
1326
|
+
|
|
1327
|
+
if( !this.code.undoSteps.length )
|
|
1328
|
+
return;
|
|
1329
|
+
|
|
1330
|
+
this._addRedoStep( cursor );
|
|
1331
|
+
|
|
1332
|
+
// Extract info from the last code state
|
|
1333
|
+
const step = this.code.undoSteps.pop();
|
|
1334
|
+
|
|
1335
|
+
// Set old state lines
|
|
1336
|
+
this.code.lines = step.lines;
|
|
1337
|
+
this.processLines();
|
|
1338
|
+
|
|
1339
|
+
this._removeSecondaryCursors();
|
|
1340
|
+
|
|
1341
|
+
for( let i = 0; i < step.cursors.length; ++i )
|
|
1342
|
+
{
|
|
1343
|
+
var currentCursor = this.cursors.children[ i ];
|
|
1344
|
+
|
|
1345
|
+
// Generate new if needed
|
|
1346
|
+
if( !currentCursor )
|
|
1347
|
+
currentCursor = this._addCursor();
|
|
1348
|
+
|
|
1349
|
+
this.restoreCursor( currentCursor, step.cursors[ i ] );
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1273
1353
|
_addRedoStep( cursor ) {
|
|
1274
1354
|
|
|
1275
|
-
|
|
1355
|
+
// Only the mainc cursor stores redo steps
|
|
1356
|
+
if( !cursor.isMain )
|
|
1357
|
+
return;
|
|
1276
1358
|
|
|
1277
1359
|
this.code.redoSteps.push( {
|
|
1278
1360
|
lines: LX.deepCopy( this.code.lines ),
|
|
1279
|
-
|
|
1280
|
-
line: cursor.line,
|
|
1281
|
-
position: cursor.position
|
|
1361
|
+
cursors: this.saveCursors()
|
|
1282
1362
|
} );
|
|
1283
1363
|
}
|
|
1284
1364
|
|
|
1365
|
+
_doRedo( cursor ) {
|
|
1366
|
+
|
|
1367
|
+
if( !this.code.redoSteps.length )
|
|
1368
|
+
return;
|
|
1369
|
+
|
|
1370
|
+
this._addUndoStep( cursor, true, false);
|
|
1371
|
+
|
|
1372
|
+
// Extract info from the next saved code state
|
|
1373
|
+
const step = this.code.redoSteps.pop();
|
|
1374
|
+
|
|
1375
|
+
// Set old state lines
|
|
1376
|
+
this.code.lines = step.lines;
|
|
1377
|
+
this.processLines();
|
|
1378
|
+
|
|
1379
|
+
this._removeSecondaryCursors();
|
|
1380
|
+
|
|
1381
|
+
for( let i = 0; i < step.cursors.length; ++i )
|
|
1382
|
+
{
|
|
1383
|
+
var currentCursor = this.cursors.children[ i ];
|
|
1384
|
+
|
|
1385
|
+
// Generate new if needed
|
|
1386
|
+
if( !currentCursor )
|
|
1387
|
+
currentCursor = this._addCursor();
|
|
1388
|
+
|
|
1389
|
+
this.restoreCursor( currentCursor, step.cursors[ i ] );
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1285
1393
|
_changeLanguage( lang ) {
|
|
1286
1394
|
|
|
1287
1395
|
this.code.language = lang;
|
|
@@ -1374,7 +1482,7 @@ class CodeEditor {
|
|
|
1374
1482
|
ext == 'css' ? "fa-solid fa-hashtag dodgerblue" :
|
|
1375
1483
|
ext == 'xml' ? "fa-solid fa-rss orange" :
|
|
1376
1484
|
ext == 'bat' ? "fa-brands fa-windows lightblue" :
|
|
1377
|
-
[ 'js', 'py', 'json', 'cpp', 'rs' ].indexOf( ext ) > -1 ? "images/" + ext + ".png" :
|
|
1485
|
+
[ 'js', 'py', 'json', 'cpp', 'rs', 'md' ].indexOf( ext ) > -1 ? "images/" + ext + ".png" :
|
|
1378
1486
|
!isNewTabButton ? "fa-solid fa-align-left gray" : undefined;
|
|
1379
1487
|
}
|
|
1380
1488
|
|
|
@@ -1388,7 +1496,7 @@ class CodeEditor {
|
|
|
1388
1496
|
});
|
|
1389
1497
|
}
|
|
1390
1498
|
|
|
1391
|
-
_onSelectTab( isNewTabButton, event, name
|
|
1499
|
+
_onSelectTab( isNewTabButton, event, name ) {
|
|
1392
1500
|
|
|
1393
1501
|
if( isNewTabButton )
|
|
1394
1502
|
{
|
|
@@ -1396,7 +1504,9 @@ class CodeEditor {
|
|
|
1396
1504
|
return;
|
|
1397
1505
|
}
|
|
1398
1506
|
|
|
1399
|
-
|
|
1507
|
+
this._removeSecondaryCursors();
|
|
1508
|
+
var cursor = this._getCurrentCursor( true );
|
|
1509
|
+
|
|
1400
1510
|
this.saveCursor( cursor, this.code.cursorState );
|
|
1401
1511
|
|
|
1402
1512
|
this.code = this.loadedTabs[ name ];
|
|
@@ -1573,21 +1683,18 @@ class CodeEditor {
|
|
|
1573
1683
|
}
|
|
1574
1684
|
}
|
|
1575
1685
|
|
|
1576
|
-
processMouse(e) {
|
|
1686
|
+
processMouse( e ) {
|
|
1577
1687
|
|
|
1578
|
-
if( !e.target.classList.contains('code') ) return;
|
|
1688
|
+
if( !e.target.classList.contains('code') && !e.target.classList.contains('codetabsarea') ) return;
|
|
1579
1689
|
if( !this.code ) return;
|
|
1580
1690
|
|
|
1581
|
-
var cursor = this.
|
|
1691
|
+
var cursor = this._getCurrentCursor();
|
|
1582
1692
|
var code_rect = this.code.getBoundingClientRect();
|
|
1583
1693
|
var mouse_pos = [(e.clientX - code_rect.x), (e.clientY - code_rect.y)];
|
|
1584
1694
|
|
|
1585
1695
|
// Discard out of lines click...
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
var ln = (mouse_pos[1] / this.lineHeight)|0;
|
|
1589
|
-
if(this.code.lines[ ln ] == undefined) return;
|
|
1590
|
-
}
|
|
1696
|
+
var ln = ( mouse_pos[ 1 ] / this.lineHeight ) | 0;
|
|
1697
|
+
if( ln < 0 ) return;
|
|
1591
1698
|
|
|
1592
1699
|
if( e.type == 'mousedown' )
|
|
1593
1700
|
{
|
|
@@ -1630,7 +1737,7 @@ class CodeEditor {
|
|
|
1630
1737
|
{
|
|
1631
1738
|
case LX.MOUSE_DOUBLE_CLICK:
|
|
1632
1739
|
const [word, from, to] = this.getWordAtPos( cursor );
|
|
1633
|
-
this.resetCursorPos( CodeEditor.CURSOR_LEFT );
|
|
1740
|
+
this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
|
|
1634
1741
|
this.cursorToPosition( cursor, from );
|
|
1635
1742
|
this.startSelection( cursor );
|
|
1636
1743
|
this.selection.selectInline( from, cursor.line, this.measureString( word ) );
|
|
@@ -1638,7 +1745,7 @@ class CodeEditor {
|
|
|
1638
1745
|
break;
|
|
1639
1746
|
// Select entire line
|
|
1640
1747
|
case LX.MOUSE_TRIPLE_CLICK:
|
|
1641
|
-
this.resetCursorPos( CodeEditor.CURSOR_LEFT );
|
|
1748
|
+
this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
|
|
1642
1749
|
e._shiftKey = true;
|
|
1643
1750
|
this.actions['End'].callback(cursor.line, cursor, e);
|
|
1644
1751
|
this._tripleClickSelection = true;
|
|
@@ -1654,11 +1761,11 @@ class CodeEditor {
|
|
|
1654
1761
|
return;
|
|
1655
1762
|
|
|
1656
1763
|
LX.addContextMenu( null, e, m => {
|
|
1657
|
-
m.add( "Copy", () => { this._copyContent(); } );
|
|
1764
|
+
m.add( "Copy", () => { this._copyContent( cursor ); } );
|
|
1658
1765
|
if( !this.disableEdition )
|
|
1659
1766
|
{
|
|
1660
|
-
m.add( "Cut", () => { this._cutContent(); } );
|
|
1661
|
-
m.add( "Paste", () => { this._pasteContent(); } );
|
|
1767
|
+
m.add( "Cut", () => { this._cutContent( cursor ); } );
|
|
1768
|
+
m.add( "Paste", () => { this._pasteContent( cursor ); } );
|
|
1662
1769
|
m.add( "" );
|
|
1663
1770
|
m.add( "Format/JSON", () => {
|
|
1664
1771
|
let json = this.toJSONFormat( this.getText() );
|
|
@@ -1690,26 +1797,40 @@ class CodeEditor {
|
|
|
1690
1797
|
|
|
1691
1798
|
processClick( e ) {
|
|
1692
1799
|
|
|
1693
|
-
var cursor = this.
|
|
1800
|
+
var cursor = this._getCurrentCursor();
|
|
1694
1801
|
var code_rect = this.codeScroller.getBoundingClientRect();
|
|
1695
1802
|
var position = [( e.clientX - code_rect.x ) + this.getScrollLeft(), (e.clientY - code_rect.y) + this.getScrollTop()];
|
|
1696
1803
|
var ln = (position[ 1 ] / this.lineHeight)|0;
|
|
1697
1804
|
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1805
|
+
// Check out of range line
|
|
1806
|
+
const outOfRange = ln > this.code.lines.length - 1;
|
|
1807
|
+
ln = Math.min( ln, this.code.lines.length - 1 );
|
|
1808
|
+
|
|
1703
1809
|
var ch = ( ( position[ 0 ] - parseInt( this.xPadding ) + 3) / this.charWidth )|0;
|
|
1704
|
-
var string = this.code.lines[ ln ].slice( 0, ch );
|
|
1705
|
-
|
|
1810
|
+
var string = outOfRange ? this.code.lines[ ln ] : this.code.lines[ ln ].slice( 0, ch );
|
|
1811
|
+
|
|
1812
|
+
// Move main cursor there...
|
|
1813
|
+
if( !e.altKey )
|
|
1814
|
+
{
|
|
1815
|
+
// Make sure we only keep the main cursor..
|
|
1816
|
+
this._removeSecondaryCursors();
|
|
1817
|
+
|
|
1818
|
+
this.cursorToLine( cursor, ln, true );
|
|
1819
|
+
this.cursorToPosition( cursor, string.length );
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
// Add new cursor
|
|
1823
|
+
else
|
|
1824
|
+
{
|
|
1825
|
+
this._addCursor( ln, string.length );
|
|
1826
|
+
}
|
|
1706
1827
|
|
|
1707
1828
|
this.hideAutoCompleteBox();
|
|
1708
1829
|
}
|
|
1709
1830
|
|
|
1710
1831
|
processSelection( e, keep_range, flags = CodeEditor.SELECTION_X_Y ) {
|
|
1711
1832
|
|
|
1712
|
-
var cursor = this.
|
|
1833
|
+
var cursor = this._getCurrentCursor();
|
|
1713
1834
|
const isMouseEvent = e && ( e.constructor == MouseEvent );
|
|
1714
1835
|
|
|
1715
1836
|
if( isMouseEvent ) this.processClick( e );
|
|
@@ -1864,11 +1985,113 @@ class CodeEditor {
|
|
|
1864
1985
|
}
|
|
1865
1986
|
|
|
1866
1987
|
async processKey( e ) {
|
|
1988
|
+
|
|
1989
|
+
const numCursors = this.cursors.childElementCount;
|
|
1867
1990
|
|
|
1868
1991
|
if( !this.code || e.srcElement.constructor != HTMLDivElement )
|
|
1992
|
+
return;
|
|
1993
|
+
|
|
1994
|
+
const key = e.key ?? e.detail.key;
|
|
1995
|
+
const target = e.detail.targetCursor;
|
|
1996
|
+
|
|
1997
|
+
if( target !== undefined )
|
|
1998
|
+
{
|
|
1999
|
+
this.processKeyAtTargetCursor( e, key, target );
|
|
2000
|
+
return;
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
// By cursor keys
|
|
2004
|
+
|
|
2005
|
+
this._lastProcessedCursorIndex = null;
|
|
2006
|
+
|
|
2007
|
+
var lastProcessedCursor = null;
|
|
2008
|
+
var cursorOffset = new LX.vec2( 0, 0 );
|
|
2009
|
+
|
|
2010
|
+
for( var i = 0; i < numCursors; i++ )
|
|
2011
|
+
{
|
|
2012
|
+
let cursor = this.cursors.children[ i ];
|
|
2013
|
+
|
|
2014
|
+
// We could delete secondary cursor while iterating..
|
|
2015
|
+
if( !cursor )
|
|
2016
|
+
break;
|
|
2017
|
+
|
|
2018
|
+
// Arrows don't modify code lines.. And only add offset if in the same line
|
|
2019
|
+
if( lastProcessedCursor && lastProcessedCursor.line == cursor.line && !key.includes( 'Arrow' ) )
|
|
2020
|
+
{
|
|
2021
|
+
cursor.position += cursorOffset.x;
|
|
2022
|
+
cursor.line += cursorOffset.y;
|
|
2023
|
+
|
|
2024
|
+
this.relocateCursors();
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
lastProcessedCursor = this.saveCursor( cursor );
|
|
2028
|
+
this._lastProcessedCursorIndex = i;
|
|
2029
|
+
|
|
2030
|
+
this._processKeyAtCursor( e, key, cursor );
|
|
2031
|
+
|
|
2032
|
+
cursorOffset.x += ( cursor.position - lastProcessedCursor.position );
|
|
2033
|
+
cursorOffset.y += ( cursor.line - lastProcessedCursor.line );
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
// Global keys
|
|
2037
|
+
|
|
2038
|
+
this._processGlobalKeys( e, key );
|
|
2039
|
+
|
|
2040
|
+
// Clear tmp
|
|
2041
|
+
|
|
2042
|
+
delete this._lastProcessedCursorIndex;
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
async processKeyAtTargetCursor( e, key, targetIdx ) {
|
|
2046
|
+
|
|
2047
|
+
let cursor = this.cursors.children[ targetIdx ];
|
|
2048
|
+
|
|
2049
|
+
// We could delete secondary cursor while iterating..
|
|
2050
|
+
if( !cursor )
|
|
1869
2051
|
return;
|
|
1870
2052
|
|
|
1871
|
-
|
|
2053
|
+
this._processKeyAtCursor( e, key, cursor );
|
|
2054
|
+
this._processGlobalKeys( e, key );
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
async _processGlobalKeys( e, key ) {
|
|
2058
|
+
|
|
2059
|
+
let cursor = this._getCurrentCursor();
|
|
2060
|
+
|
|
2061
|
+
if( e.ctrlKey || e.metaKey )
|
|
2062
|
+
{
|
|
2063
|
+
e.preventDefault();
|
|
2064
|
+
|
|
2065
|
+
switch( key.toLowerCase() ) {
|
|
2066
|
+
case 'a': // select all
|
|
2067
|
+
this.selectAll();
|
|
2068
|
+
break;
|
|
2069
|
+
case 'f': // find/search
|
|
2070
|
+
this.showSearchBox();
|
|
2071
|
+
break;
|
|
2072
|
+
case 'g': // find line
|
|
2073
|
+
this.showSearchLineBox();
|
|
2074
|
+
break;
|
|
2075
|
+
case 's': // save
|
|
2076
|
+
this.onsave( this.getText() );
|
|
2077
|
+
break;
|
|
2078
|
+
case 'y': // redo
|
|
2079
|
+
this._doRedo( cursor );
|
|
2080
|
+
break;
|
|
2081
|
+
case 'z': // undo
|
|
2082
|
+
this._doUndo( cursor );
|
|
2083
|
+
break;
|
|
2084
|
+
case '+': // increase size
|
|
2085
|
+
this._increaseFontSize();
|
|
2086
|
+
break;
|
|
2087
|
+
case '-': // decrease size
|
|
2088
|
+
this._decreaseFontSize();
|
|
2089
|
+
break;
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
async _processKeyAtCursor( e, key, cursor ) {
|
|
1872
2095
|
|
|
1873
2096
|
const skip_undo = e.detail.skip_undo ?? false;
|
|
1874
2097
|
|
|
@@ -1876,82 +2099,38 @@ class CodeEditor {
|
|
|
1876
2099
|
if( key.length > 1 && this.specialKeys.indexOf( key ) == -1 )
|
|
1877
2100
|
return;
|
|
1878
2101
|
|
|
1879
|
-
let cursor = this.cursors.children[ 0 ];
|
|
1880
2102
|
let lidx = cursor.line;
|
|
1881
2103
|
this.code.lines[ lidx ] = this.code.lines[ lidx ] ?? "";
|
|
1882
2104
|
|
|
1883
2105
|
// Check combinations
|
|
1884
2106
|
|
|
2107
|
+
const isLastCursor = cursor.isLast();
|
|
2108
|
+
|
|
1885
2109
|
if( e.ctrlKey || e.metaKey )
|
|
1886
2110
|
{
|
|
1887
2111
|
switch( key.toLowerCase() ) {
|
|
1888
|
-
case 'a': // select all
|
|
1889
|
-
e.preventDefault();
|
|
1890
|
-
this.resetCursorPos( CodeEditor.CURSOR_LEFT | CodeEditor.CURSOR_TOP );
|
|
1891
|
-
this.startSelection( cursor );
|
|
1892
|
-
const nlines = this.code.lines.length - 1;
|
|
1893
|
-
this.selection.toX = this.code.lines[ nlines ].length;
|
|
1894
|
-
this.selection.toY = nlines;
|
|
1895
|
-
this.cursorToPosition( cursor, this.selection.toX );
|
|
1896
|
-
this.cursorToLine( cursor, this.selection.toY );
|
|
1897
|
-
this.processSelection( null, true );
|
|
1898
|
-
this.hideAutoCompleteBox();
|
|
1899
|
-
break;
|
|
1900
2112
|
case 'c': // copy
|
|
1901
|
-
|
|
2113
|
+
// TODO: COPY TEXT FROM EVERY CURSOR
|
|
2114
|
+
this._copyContent( cursor );
|
|
1902
2115
|
return;
|
|
1903
2116
|
case 'd': // duplicate line
|
|
1904
2117
|
e.preventDefault();
|
|
1905
|
-
|
|
1906
|
-
this.
|
|
1907
|
-
this.lineDown( cursor );
|
|
1908
|
-
this.processLines();
|
|
1909
|
-
this.hideAutoCompleteBox();
|
|
1910
|
-
return;
|
|
1911
|
-
case 'f': // find/search
|
|
1912
|
-
e.preventDefault();
|
|
1913
|
-
this.showSearchBox();
|
|
1914
|
-
return;
|
|
1915
|
-
case 'g': // find line
|
|
1916
|
-
e.preventDefault();
|
|
1917
|
-
this.showSearchLineBox();
|
|
1918
|
-
return;
|
|
1919
|
-
case 's': // save
|
|
1920
|
-
e.preventDefault();
|
|
1921
|
-
this.onsave( this.getText() );
|
|
2118
|
+
// TODO: UPDATE NEXT CURSOR ON MODIFY STATE
|
|
2119
|
+
this._duplicateLine( lidx, cursor );
|
|
1922
2120
|
return;
|
|
1923
2121
|
case 'v': // paste
|
|
1924
|
-
this._pasteContent();
|
|
2122
|
+
this._pasteContent( cursor );
|
|
1925
2123
|
return;
|
|
1926
2124
|
case 'x': // cut line
|
|
1927
|
-
this._cutContent();
|
|
2125
|
+
this._cutContent( cursor );
|
|
1928
2126
|
this.hideAutoCompleteBox();
|
|
1929
2127
|
return;
|
|
1930
|
-
case '
|
|
1931
|
-
if(
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
this.processLines();
|
|
1937
|
-
this.restoreCursor( cursor, redo_step.cursor );
|
|
1938
|
-
return;
|
|
1939
|
-
case 'z': // undo
|
|
1940
|
-
if(!this.code.undoSteps.length)
|
|
1941
|
-
return;
|
|
1942
|
-
this._addRedoStep( cursor );
|
|
1943
|
-
const undo_step = this.code.undoSteps.pop();
|
|
1944
|
-
this.code.lines = undo_step.lines;
|
|
1945
|
-
this.processLines();
|
|
1946
|
-
this.restoreCursor( cursor, undo_step.cursor );
|
|
1947
|
-
return;
|
|
1948
|
-
case '+': // increase size
|
|
1949
|
-
e.preventDefault();
|
|
1950
|
-
this._increaseFontSize();
|
|
1951
|
-
return;
|
|
1952
|
-
case '-': // decrease size
|
|
1953
|
-
e.preventDefault();
|
|
1954
|
-
this._decreaseFontSize();
|
|
2128
|
+
case 'arrowdown': // add cursor below only for the main cursor..
|
|
2129
|
+
if( isLastCursor && this.code.lines[ lidx + 1 ] != undefined )
|
|
2130
|
+
{
|
|
2131
|
+
var new_cursor = this._addCursor( cursor.line, cursor.position, true );
|
|
2132
|
+
this.lineDown( new_cursor );
|
|
2133
|
+
}
|
|
1955
2134
|
return;
|
|
1956
2135
|
}
|
|
1957
2136
|
}
|
|
@@ -1962,8 +2141,9 @@ class CodeEditor {
|
|
|
1962
2141
|
case 'ArrowUp':
|
|
1963
2142
|
if(this.code.lines[ lidx - 1 ] == undefined)
|
|
1964
2143
|
return;
|
|
1965
|
-
|
|
1966
|
-
this.
|
|
2144
|
+
this._addUndoStep( cursor, true );
|
|
2145
|
+
swapArrayElements( this.code.lines, lidx - 1, lidx );
|
|
2146
|
+
this.lineUp( cursor );
|
|
1967
2147
|
this.processLine( lidx - 1 );
|
|
1968
2148
|
this.processLine( lidx );
|
|
1969
2149
|
this.hideAutoCompleteBox();
|
|
@@ -1971,8 +2151,9 @@ class CodeEditor {
|
|
|
1971
2151
|
case 'ArrowDown':
|
|
1972
2152
|
if(this.code.lines[ lidx + 1 ] == undefined)
|
|
1973
2153
|
return;
|
|
1974
|
-
|
|
1975
|
-
this.
|
|
2154
|
+
this._addUndoStep( cursor, true );
|
|
2155
|
+
swapArrayElements( this.code.lines, lidx, lidx + 1 );
|
|
2156
|
+
this.lineDown( cursor );
|
|
1976
2157
|
this.processLine( lidx );
|
|
1977
2158
|
this.processLine( lidx + 1 );
|
|
1978
2159
|
this.hideAutoCompleteBox();
|
|
@@ -1983,17 +2164,18 @@ class CodeEditor {
|
|
|
1983
2164
|
// Apply binded actions...
|
|
1984
2165
|
|
|
1985
2166
|
for( const actKey in this.actions ) {
|
|
2167
|
+
|
|
1986
2168
|
if( key != actKey ) continue;
|
|
1987
2169
|
e.preventDefault();
|
|
1988
2170
|
|
|
1989
|
-
if(this.actions[ key ].deleteSelection && this.selection)
|
|
1990
|
-
this.actions['Backspace'].callback(lidx, cursor, e);
|
|
2171
|
+
if( this.actions[ key ].deleteSelection && this.selection )
|
|
2172
|
+
this.actions['Backspace'].callback( lidx, cursor, e );
|
|
1991
2173
|
|
|
1992
2174
|
return this.actions[ key ].callback( lidx, cursor, e );
|
|
1993
2175
|
}
|
|
1994
2176
|
|
|
1995
2177
|
// From now on, don't allow ctrl, shift or meta (mac) combinations
|
|
1996
|
-
if(
|
|
2178
|
+
if( e.ctrlKey || e.metaKey )
|
|
1997
2179
|
return;
|
|
1998
2180
|
|
|
1999
2181
|
// Add undo steps
|
|
@@ -2005,7 +2187,7 @@ class CodeEditor {
|
|
|
2005
2187
|
|
|
2006
2188
|
// Some custom cases for word enclosing (), {}, "", '', ...
|
|
2007
2189
|
|
|
2008
|
-
const enclosableKeys = ["\"", "'", "(", "{"];
|
|
2190
|
+
const enclosableKeys = [ "\"", "'", "(", "{" ];
|
|
2009
2191
|
if( enclosableKeys.indexOf( key ) > -1 )
|
|
2010
2192
|
{
|
|
2011
2193
|
if( this._encloseSelectedWordWithKey( key, lidx, cursor ) )
|
|
@@ -2023,19 +2205,19 @@ class CodeEditor {
|
|
|
2023
2205
|
|
|
2024
2206
|
// Append key
|
|
2025
2207
|
|
|
2026
|
-
const isPairKey = (Object.values( this.pairKeys ).indexOf( key ) > -1) && !this.wasKeyPaired;
|
|
2027
|
-
const sameKeyNext = isPairKey && (this.code.lines[ lidx ][cursor.position] === key);
|
|
2208
|
+
const isPairKey = ( Object.values( this.pairKeys ).indexOf( key ) > -1 ) && !this.wasKeyPaired;
|
|
2209
|
+
const sameKeyNext = isPairKey && ( this.code.lines[ lidx ][ cursor.position ] === key );
|
|
2028
2210
|
|
|
2029
2211
|
if( !sameKeyNext )
|
|
2030
2212
|
{
|
|
2031
2213
|
this.code.lines[ lidx ] = [
|
|
2032
|
-
this.code.lines[ lidx ].slice(0, cursor.position),
|
|
2214
|
+
this.code.lines[ lidx ].slice( 0, cursor.position ),
|
|
2033
2215
|
key,
|
|
2034
|
-
this.code.lines[ lidx ].slice(cursor.position)
|
|
2216
|
+
this.code.lines[ lidx ].slice( cursor.position )
|
|
2035
2217
|
].join('');
|
|
2036
2218
|
}
|
|
2037
2219
|
|
|
2038
|
-
this.cursorToRight( key );
|
|
2220
|
+
this.cursorToRight( key, cursor );
|
|
2039
2221
|
|
|
2040
2222
|
// Some custom cases for auto key pair (), {}, "", '', ...
|
|
2041
2223
|
|
|
@@ -2045,7 +2227,7 @@ class CodeEditor {
|
|
|
2045
2227
|
// Make sure to detect later that the key is paired automatically to avoid loops...
|
|
2046
2228
|
this.wasKeyPaired = true;
|
|
2047
2229
|
|
|
2048
|
-
if(sameKeyNext) return;
|
|
2230
|
+
if( sameKeyNext ) return;
|
|
2049
2231
|
|
|
2050
2232
|
this.root.dispatchEvent(new KeyboardEvent('keydown', { 'key': this.pairKeys[ key ] }));
|
|
2051
2233
|
this.cursorToLeft( key, cursor );
|
|
@@ -2072,14 +2254,17 @@ class CodeEditor {
|
|
|
2072
2254
|
this.showAutoCompleteBox( key, cursor );
|
|
2073
2255
|
}
|
|
2074
2256
|
|
|
2075
|
-
async _pasteContent() {
|
|
2257
|
+
async _pasteContent( cursor ) {
|
|
2258
|
+
|
|
2076
2259
|
let text = await navigator.clipboard.readText();
|
|
2077
|
-
|
|
2260
|
+
|
|
2261
|
+
this._addUndoStep( cursor, true );
|
|
2262
|
+
|
|
2263
|
+
this.appendText( text, cursor );
|
|
2078
2264
|
}
|
|
2079
2265
|
|
|
2080
|
-
async _copyContent() {
|
|
2266
|
+
async _copyContent( cursor ) {
|
|
2081
2267
|
|
|
2082
|
-
let cursor = this.cursors.children[ 0 ];
|
|
2083
2268
|
let text_to_copy = "";
|
|
2084
2269
|
|
|
2085
2270
|
if( !this.selection ) {
|
|
@@ -2091,27 +2276,26 @@ class CodeEditor {
|
|
|
2091
2276
|
if( this.selection ) this.selection.invertIfNecessary();
|
|
2092
2277
|
|
|
2093
2278
|
const separator = "_NEWLINE_";
|
|
2094
|
-
let code = this.code.lines.join(separator);
|
|
2279
|
+
let code = this.code.lines.join( separator );
|
|
2095
2280
|
|
|
2096
2281
|
// Get linear start index
|
|
2097
2282
|
let index = 0;
|
|
2098
2283
|
|
|
2099
|
-
for(let i = 0; i <= this.selection.fromY; i++)
|
|
2100
|
-
index += (i == this.selection.fromY ? this.selection.fromX : this.code.lines[ i ].length);
|
|
2284
|
+
for( let i = 0; i <= this.selection.fromY; i++ )
|
|
2285
|
+
index += ( i == this.selection.fromY ? this.selection.fromX : this.code.lines[ i ].length );
|
|
2101
2286
|
|
|
2102
2287
|
index += this.selection.fromY * separator.length;
|
|
2103
|
-
const num_chars = this.selection.chars + (this.selection.toY - this.selection.fromY) * separator.length;
|
|
2104
|
-
const text = code.substr(index, num_chars);
|
|
2105
|
-
const lines = text.split(separator);
|
|
2288
|
+
const num_chars = this.selection.chars + ( this.selection.toY - this.selection.fromY ) * separator.length;
|
|
2289
|
+
const text = code.substr( index, num_chars );
|
|
2290
|
+
const lines = text.split( separator );
|
|
2106
2291
|
text_to_copy = lines.join('\n');
|
|
2107
2292
|
}
|
|
2108
2293
|
|
|
2109
2294
|
navigator.clipboard.writeText( text_to_copy ).then(() => console.log("Successfully copied"), (err) => console.error("Error"));
|
|
2110
2295
|
}
|
|
2111
2296
|
|
|
2112
|
-
async _cutContent() {
|
|
2297
|
+
async _cutContent( cursor ) {
|
|
2113
2298
|
|
|
2114
|
-
let cursor = this.cursors.children[ 0 ];
|
|
2115
2299
|
let lidx = cursor.line;
|
|
2116
2300
|
let text_to_cut = "";
|
|
2117
2301
|
|
|
@@ -2121,9 +2305,9 @@ class CodeEditor {
|
|
|
2121
2305
|
text_to_cut = "\n" + this.code.lines[ cursor.line ];
|
|
2122
2306
|
this.code.lines.splice( lidx, 1 );
|
|
2123
2307
|
this.processLines();
|
|
2124
|
-
this.resetCursorPos( CodeEditor.CURSOR_LEFT );
|
|
2308
|
+
this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
|
|
2125
2309
|
if( this.code.lines[ lidx ] == undefined )
|
|
2126
|
-
this.lineUp();
|
|
2310
|
+
this.lineUp( cursor );
|
|
2127
2311
|
}
|
|
2128
2312
|
else {
|
|
2129
2313
|
|
|
@@ -2151,6 +2335,16 @@ class CodeEditor {
|
|
|
2151
2335
|
navigator.clipboard.writeText( text_to_cut ).then(() => console.log("Successfully cut"), (err) => console.error("Error"));
|
|
2152
2336
|
}
|
|
2153
2337
|
|
|
2338
|
+
_duplicateLine( lidx, cursor ) {
|
|
2339
|
+
|
|
2340
|
+
this.endSelection();
|
|
2341
|
+
this._addUndoStep( cursor, true );
|
|
2342
|
+
this.code.lines.splice( lidx, 0, this.code.lines[ lidx ] );
|
|
2343
|
+
this.lineDown( cursor );
|
|
2344
|
+
this.processLines();
|
|
2345
|
+
this.hideAutoCompleteBox();
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2154
2348
|
action( key, deleteSelection, fn ) {
|
|
2155
2349
|
|
|
2156
2350
|
this.actions[ key ] = {
|
|
@@ -2184,8 +2378,6 @@ class CodeEditor {
|
|
|
2184
2378
|
|
|
2185
2379
|
processLines( mode ) {
|
|
2186
2380
|
|
|
2187
|
-
const start = performance.now();
|
|
2188
|
-
|
|
2189
2381
|
var code_html = "";
|
|
2190
2382
|
|
|
2191
2383
|
// Reset all lines content
|
|
@@ -2618,7 +2810,13 @@ class CodeEditor {
|
|
|
2618
2810
|
}
|
|
2619
2811
|
|
|
2620
2812
|
_isCSSClass( token, prev, next ) {
|
|
2621
|
-
|
|
2813
|
+
|
|
2814
|
+
if( this.highlight != 'CSS' )
|
|
2815
|
+
return false;
|
|
2816
|
+
|
|
2817
|
+
return ( prev == '.' || prev == '::'
|
|
2818
|
+
|| ( prev == ':' && next == '{' )
|
|
2819
|
+
|| ( token[ 0 ] == '#' && prev != ':' ) );
|
|
2622
2820
|
}
|
|
2623
2821
|
|
|
2624
2822
|
isNumber( token ) {
|
|
@@ -2712,8 +2910,6 @@ class CodeEditor {
|
|
|
2712
2910
|
|
|
2713
2911
|
lineUp( cursor, resetLeft ) {
|
|
2714
2912
|
|
|
2715
|
-
cursor = cursor ?? this.cursors.children[ 0 ];
|
|
2716
|
-
|
|
2717
2913
|
if( this.code.lines[ cursor.line - 1 ] == undefined )
|
|
2718
2914
|
return false;
|
|
2719
2915
|
|
|
@@ -2725,13 +2921,12 @@ class CodeEditor {
|
|
|
2725
2921
|
}
|
|
2726
2922
|
|
|
2727
2923
|
lineDown( cursor, resetLeft ) {
|
|
2728
|
-
|
|
2729
|
-
cursor = cursor ?? this.cursors.children[ 0 ];
|
|
2730
2924
|
|
|
2731
2925
|
if( this.code.lines[ cursor.line + 1 ] == undefined )
|
|
2732
2926
|
return false;
|
|
2733
2927
|
|
|
2734
2928
|
cursor.line++;
|
|
2929
|
+
|
|
2735
2930
|
this.cursorToBottom( cursor, resetLeft );
|
|
2736
2931
|
|
|
2737
2932
|
return true;
|
|
@@ -2804,10 +2999,32 @@ class CodeEditor {
|
|
|
2804
2999
|
delete this._lastSelectionKeyDir;
|
|
2805
3000
|
}
|
|
2806
3001
|
|
|
3002
|
+
selectAll() {
|
|
3003
|
+
|
|
3004
|
+
// Use main cursor
|
|
3005
|
+
this._removeSecondaryCursors();
|
|
3006
|
+
|
|
3007
|
+
var cursor = this._getCurrentCursor();
|
|
3008
|
+
this.resetCursorPos( CodeEditor.CURSOR_LEFT | CodeEditor.CURSOR_TOP, cursor );
|
|
3009
|
+
|
|
3010
|
+
this.startSelection( cursor );
|
|
3011
|
+
|
|
3012
|
+
const nlines = this.code.lines.length - 1;
|
|
3013
|
+
this.selection.toX = this.code.lines[ nlines ].length;
|
|
3014
|
+
this.selection.toY = nlines;
|
|
3015
|
+
|
|
3016
|
+
this.cursorToPosition( cursor, this.selection.toX );
|
|
3017
|
+
this.cursorToLine( cursor, this.selection.toY );
|
|
3018
|
+
|
|
3019
|
+
this.processSelection( null, true );
|
|
3020
|
+
|
|
3021
|
+
this.hideAutoCompleteBox();
|
|
3022
|
+
}
|
|
3023
|
+
|
|
2807
3024
|
cursorToRight( key, cursor ) {
|
|
2808
3025
|
|
|
2809
3026
|
if( !key ) return;
|
|
2810
|
-
|
|
3027
|
+
|
|
2811
3028
|
cursor._left += this.charWidth;
|
|
2812
3029
|
cursor.style.left = "calc( " + cursor._left + "px + " + this.xPadding + " )";
|
|
2813
3030
|
cursor.position++;
|
|
@@ -2826,7 +3043,7 @@ class CodeEditor {
|
|
|
2826
3043
|
cursorToLeft( key, cursor ) {
|
|
2827
3044
|
|
|
2828
3045
|
if( !key ) return;
|
|
2829
|
-
|
|
3046
|
+
|
|
2830
3047
|
cursor._left -= this.charWidth;
|
|
2831
3048
|
cursor._left = Math.max( cursor._left, 0 );
|
|
2832
3049
|
cursor.style.left = "calc( " + cursor._left + "px + " + this.xPadding + " )";
|
|
@@ -2845,13 +3062,12 @@ class CodeEditor {
|
|
|
2845
3062
|
|
|
2846
3063
|
cursorToTop( cursor, resetLeft = false ) {
|
|
2847
3064
|
|
|
2848
|
-
cursor = cursor ?? this.cursors.children[ 0 ];
|
|
2849
3065
|
cursor._top -= this.lineHeight;
|
|
2850
3066
|
cursor._top = Math.max(cursor._top, 0);
|
|
2851
3067
|
cursor.style.top = "calc(" + cursor._top + "px)";
|
|
2852
3068
|
this.restartBlink();
|
|
2853
3069
|
|
|
2854
|
-
if(resetLeft)
|
|
3070
|
+
if( resetLeft )
|
|
2855
3071
|
this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
|
|
2856
3072
|
|
|
2857
3073
|
doAsync(() => {
|
|
@@ -2863,12 +3079,12 @@ class CodeEditor {
|
|
|
2863
3079
|
|
|
2864
3080
|
cursorToBottom( cursor, resetLeft = false ) {
|
|
2865
3081
|
|
|
2866
|
-
cursor = cursor ?? this.cursors.children[ 0 ];
|
|
2867
3082
|
cursor._top += this.lineHeight;
|
|
2868
3083
|
cursor.style.top = "calc(" + cursor._top + "px)";
|
|
3084
|
+
|
|
2869
3085
|
this.restartBlink();
|
|
2870
3086
|
|
|
2871
|
-
if(resetLeft)
|
|
3087
|
+
if( resetLeft )
|
|
2872
3088
|
this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
|
|
2873
3089
|
|
|
2874
3090
|
doAsync(() => {
|
|
@@ -2880,10 +3096,11 @@ class CodeEditor {
|
|
|
2880
3096
|
|
|
2881
3097
|
cursorToString( cursor, text, reverse ) {
|
|
2882
3098
|
|
|
2883
|
-
if( !text.length )
|
|
2884
|
-
|
|
3099
|
+
if( !text.length )
|
|
3100
|
+
return;
|
|
3101
|
+
|
|
2885
3102
|
for( let char of text )
|
|
2886
|
-
reverse ? this.cursorToLeft( char ) : this.cursorToRight( char );
|
|
3103
|
+
reverse ? this.cursorToLeft( char, cursor ) : this.cursorToRight( char, cursor );
|
|
2887
3104
|
}
|
|
2888
3105
|
|
|
2889
3106
|
cursorToPosition( cursor, position ) {
|
|
@@ -2903,29 +3120,57 @@ class CodeEditor {
|
|
|
2903
3120
|
|
|
2904
3121
|
saveCursor( cursor, state = {} ) {
|
|
2905
3122
|
|
|
2906
|
-
var cursor = cursor ?? this.cursors.children[ 0 ];
|
|
2907
|
-
state.top = cursor._top;
|
|
2908
|
-
state.left = cursor._left;
|
|
2909
|
-
state.line = cursor.line;
|
|
2910
3123
|
state.position = cursor.position;
|
|
3124
|
+
state.line = cursor.line;
|
|
2911
3125
|
return state;
|
|
2912
3126
|
}
|
|
2913
3127
|
|
|
3128
|
+
saveCursors() {
|
|
3129
|
+
|
|
3130
|
+
var cursors = [];
|
|
3131
|
+
|
|
3132
|
+
for( let cursor of this.cursors.children )
|
|
3133
|
+
{
|
|
3134
|
+
cursors.push( this.saveCursor( cursor ) );
|
|
3135
|
+
}
|
|
3136
|
+
|
|
3137
|
+
return cursors;
|
|
3138
|
+
}
|
|
3139
|
+
|
|
3140
|
+
relocateCursors() {
|
|
3141
|
+
|
|
3142
|
+
for( let cursor of this.cursors.children )
|
|
3143
|
+
{
|
|
3144
|
+
cursor._left = cursor.position * this.charWidth;
|
|
3145
|
+
cursor.style.left = "calc(" + cursor._left + "px + " + this.xPadding + ")";
|
|
3146
|
+
cursor._top = cursor.line * this.lineHeight;
|
|
3147
|
+
cursor.style.top = "calc(" + cursor._top + "px)";
|
|
3148
|
+
}
|
|
3149
|
+
}
|
|
3150
|
+
|
|
3151
|
+
mergeCursors( line ) {
|
|
3152
|
+
|
|
3153
|
+
console.assert( line >= 0 );
|
|
3154
|
+
const cursorsInLine = Array.from( this.cursors.children ).filter( v => v.line == line );
|
|
3155
|
+
|
|
3156
|
+
while( cursorsInLine.length > 1 )
|
|
3157
|
+
cursorsInLine.pop().remove();
|
|
3158
|
+
}
|
|
3159
|
+
|
|
2914
3160
|
restoreCursor( cursor, state ) {
|
|
2915
3161
|
|
|
2916
|
-
cursor = cursor ?? this.cursors.children[ 0 ];
|
|
2917
|
-
cursor.line = state.line ?? 0;
|
|
2918
3162
|
cursor.position = state.position ?? 0;
|
|
3163
|
+
cursor.line = state.line ?? 0;
|
|
2919
3164
|
|
|
2920
|
-
cursor._left =
|
|
3165
|
+
cursor._left = cursor.position * this.charWidth;
|
|
2921
3166
|
cursor.style.left = "calc(" + cursor._left + "px + " + this.xPadding + ")";
|
|
2922
|
-
cursor._top =
|
|
3167
|
+
cursor._top = cursor.line * this.lineHeight;
|
|
2923
3168
|
cursor.style.top = "calc(" + cursor._top + "px)";
|
|
2924
3169
|
}
|
|
2925
3170
|
|
|
2926
3171
|
resetCursorPos( flag, cursor ) {
|
|
2927
3172
|
|
|
2928
|
-
cursor = cursor ?? this.
|
|
3173
|
+
cursor = cursor ?? this._getCurrentCursor();
|
|
2929
3174
|
|
|
2930
3175
|
if( flag & CodeEditor.CURSOR_LEFT )
|
|
2931
3176
|
{
|
|
@@ -2942,19 +3187,20 @@ class CodeEditor {
|
|
|
2942
3187
|
}
|
|
2943
3188
|
}
|
|
2944
3189
|
|
|
2945
|
-
addSpaceTabs(n) {
|
|
3190
|
+
addSpaceTabs( n ) {
|
|
2946
3191
|
|
|
2947
3192
|
for( var i = 0; i < n; ++i ) {
|
|
2948
3193
|
this.actions[ 'Tab' ].callback();
|
|
2949
3194
|
}
|
|
2950
3195
|
}
|
|
2951
3196
|
|
|
2952
|
-
addSpaces(n) {
|
|
3197
|
+
addSpaces( n ) {
|
|
2953
3198
|
|
|
2954
3199
|
for( var i = 0; i < n; ++i ) {
|
|
2955
3200
|
this.root.dispatchEvent( new CustomEvent( 'keydown', { 'detail': {
|
|
2956
3201
|
skip_undo: true,
|
|
2957
|
-
key: ' '
|
|
3202
|
+
key: ' ',
|
|
3203
|
+
targetCursor: this._lastProcessedCursorIndex
|
|
2958
3204
|
}}));
|
|
2959
3205
|
}
|
|
2960
3206
|
}
|
|
@@ -3109,14 +3355,11 @@ class CodeEditor {
|
|
|
3109
3355
|
|
|
3110
3356
|
getCharAtPos( cursor, offset = 0 ) {
|
|
3111
3357
|
|
|
3112
|
-
cursor = cursor ?? this.cursors.children[ 0 ];
|
|
3113
3358
|
return this.code.lines[ cursor.line ][ cursor.position + offset ];
|
|
3114
3359
|
}
|
|
3115
3360
|
|
|
3116
|
-
getWordAtPos( cursor,
|
|
3361
|
+
getWordAtPos( cursor, offset = 0 ) {
|
|
3117
3362
|
|
|
3118
|
-
roffset = roffset ?? loffset;
|
|
3119
|
-
cursor = cursor ?? this.cursors.children[ 0 ];
|
|
3120
3363
|
const col = cursor.line;
|
|
3121
3364
|
const words = this.code.lines[ col ];
|
|
3122
3365
|
|
|
@@ -3126,8 +3369,8 @@ class CodeEditor {
|
|
|
3126
3369
|
return (exceptions.indexOf( char ) > - 1) || (code > 47 && code < 58) || (code > 64 && code < 91) || (code > 96 && code < 123);
|
|
3127
3370
|
}
|
|
3128
3371
|
|
|
3129
|
-
let from = cursor.position +
|
|
3130
|
-
let to = cursor.position +
|
|
3372
|
+
let from = cursor.position + offset;
|
|
3373
|
+
let to = cursor.position + offset;
|
|
3131
3374
|
|
|
3132
3375
|
// Check left ...
|
|
3133
3376
|
|
|
@@ -3146,7 +3389,7 @@ class CodeEditor {
|
|
|
3146
3389
|
let word = words.substring( from, to );
|
|
3147
3390
|
if( word == ' ' )
|
|
3148
3391
|
{
|
|
3149
|
-
if(
|
|
3392
|
+
if( offset < 0 )
|
|
3150
3393
|
{
|
|
3151
3394
|
while( words[ from - 1 ] != undefined && words[ from - 1 ] == ' ' )
|
|
3152
3395
|
from--;
|
|
@@ -3319,8 +3562,10 @@ class CodeEditor {
|
|
|
3319
3562
|
|
|
3320
3563
|
hideAutoCompleteBox() {
|
|
3321
3564
|
|
|
3565
|
+
const isActive = this.isAutoCompleteActive;
|
|
3322
3566
|
this.isAutoCompleteActive = false;
|
|
3323
3567
|
this.autocomplete.classList.remove( 'show' );
|
|
3568
|
+
return isActive != this.isAutoCompleteActive;
|
|
3324
3569
|
}
|
|
3325
3570
|
|
|
3326
3571
|
autoCompleteWord( cursor, suggestion ) {
|
|
@@ -3521,12 +3766,21 @@ class CodeEditor {
|
|
|
3521
3766
|
return;
|
|
3522
3767
|
|
|
3523
3768
|
this.codeScroller.scrollTo( 0, Math.max( line - 15 ) * this.lineHeight );
|
|
3769
|
+
|
|
3770
|
+
// Select line ?
|
|
3771
|
+
var cursor = this._getCurrentCursor( true );
|
|
3772
|
+
this.cursorToLine( cursor, line - 1, true );
|
|
3524
3773
|
}
|
|
3525
3774
|
|
|
3526
3775
|
_updateDataInfoPanel( signal, value ) {
|
|
3527
3776
|
|
|
3528
3777
|
if( !this.skipCodeInfo )
|
|
3529
3778
|
{
|
|
3779
|
+
if( this.cursors.childElementCount > 1 )
|
|
3780
|
+
{
|
|
3781
|
+
value = "";
|
|
3782
|
+
}
|
|
3783
|
+
|
|
3530
3784
|
LX.emit( signal, value );
|
|
3531
3785
|
}
|
|
3532
3786
|
}
|
|
@@ -3541,7 +3795,7 @@ class CodeEditor {
|
|
|
3541
3795
|
let line = this.code.childNodes[ old_local ];
|
|
3542
3796
|
|
|
3543
3797
|
if( !line )
|
|
3544
|
-
|
|
3798
|
+
return;
|
|
3545
3799
|
|
|
3546
3800
|
line.classList.remove( 'active-line' );
|
|
3547
3801
|
|
|
@@ -3572,7 +3826,13 @@ class CodeEditor {
|
|
|
3572
3826
|
r.style.setProperty( "--code-editor-row-height", row_pixels + "px" );
|
|
3573
3827
|
this.lineHeight = row_pixels;
|
|
3574
3828
|
|
|
3575
|
-
|
|
3829
|
+
// Relocate cursors
|
|
3830
|
+
|
|
3831
|
+
this.relocateCursors();
|
|
3832
|
+
|
|
3833
|
+
// Resize the code area
|
|
3834
|
+
|
|
3835
|
+
this.processLines();
|
|
3576
3836
|
}
|
|
3577
3837
|
|
|
3578
3838
|
_decreaseFontSize() {
|
|
@@ -3592,7 +3852,13 @@ class CodeEditor {
|
|
|
3592
3852
|
r.style.setProperty( "--code-editor-row-height", row_pixels + "px" );
|
|
3593
3853
|
this.lineHeight = row_pixels;
|
|
3594
3854
|
|
|
3595
|
-
|
|
3855
|
+
// Relocate cursors
|
|
3856
|
+
|
|
3857
|
+
this.relocateCursors();
|
|
3858
|
+
|
|
3859
|
+
// Resize the code area
|
|
3860
|
+
|
|
3861
|
+
this.processLines();
|
|
3596
3862
|
}
|
|
3597
3863
|
|
|
3598
3864
|
_clearTmpVariables() {
|