lexgui 0.1.17 → 0.1.18
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 +268 -186
- package/build/lexgui.css +30 -15
- package/build/lexgui.js +14 -2
- package/build/lexgui.module.js +14 -2
- package/demo.js +1 -1
- package/examples/code_editor.html +1 -13
- package/package.json +1 -1
|
@@ -245,12 +245,12 @@ class CodeEditor {
|
|
|
245
245
|
// Scroll stuff
|
|
246
246
|
{
|
|
247
247
|
this.codeScroller = this.tabs.area.root;
|
|
248
|
-
this.
|
|
248
|
+
this.firstLineInViewport = 0;
|
|
249
249
|
this.lineScrollMargin = new LX.vec2( 20, 20 ); // [ mUp, mDown ]
|
|
250
250
|
window.scroller = this.codeScroller;
|
|
251
251
|
|
|
252
252
|
let lastScrollTopValue = -1;
|
|
253
|
-
this.codeScroller.addEventListener( 'scroll',
|
|
253
|
+
this.codeScroller.addEventListener( 'scroll', e => {
|
|
254
254
|
|
|
255
255
|
if( this._discardScroll )
|
|
256
256
|
{
|
|
@@ -265,36 +265,29 @@ class CodeEditor {
|
|
|
265
265
|
// Scroll down...
|
|
266
266
|
if( scrollTop > lastScrollTopValue )
|
|
267
267
|
{
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
if( scrollTop > scrollDownBoundary )
|
|
268
|
+
if( this.visibleLinesViewport.y < (this.code.lines.length - 1) )
|
|
271
269
|
{
|
|
272
|
-
|
|
273
|
-
|
|
270
|
+
const totalLinesInViewport = ((this.codeScroller.offsetHeight - 36) / this.lineHeight)|0;
|
|
271
|
+
const scrollDownBoundary =
|
|
272
|
+
( Math.max( this.visibleLinesViewport.y - totalLinesInViewport, 0 ) - 1 ) * this.lineHeight;
|
|
273
|
+
|
|
274
|
+
if( scrollTop >= scrollDownBoundary )
|
|
275
|
+
this.processLines( CodeEditor.UPDATE_VISIBLE_LINES );
|
|
274
276
|
}
|
|
275
277
|
}
|
|
276
278
|
// Scroll up...
|
|
277
279
|
else
|
|
278
280
|
{
|
|
279
|
-
const scrollUpBoundary = (
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
if( scrollTop <= scrollUpBoundary )
|
|
283
|
-
{
|
|
284
|
-
this.viewportRangeStart -= this.lineScrollMargin.x;
|
|
285
|
-
this.viewportRangeStart = Math.max( this.viewportRangeStart, 0 );
|
|
286
|
-
|
|
287
|
-
this.code.style.top = this.viewportRangeStart == 0 ? "0px" : (scrollUpBoundary - this.lineScrollMargin.x * this.lineHeight) + "px";
|
|
288
|
-
|
|
289
|
-
this.processLines( CodeEditor.KEEP_VISIBLE_LINES );
|
|
290
|
-
}
|
|
281
|
+
const scrollUpBoundary = parseInt( this.code.style.top );
|
|
282
|
+
if( scrollTop < scrollUpBoundary )
|
|
283
|
+
this.processLines( CodeEditor.UPDATE_VISIBLE_LINES );
|
|
291
284
|
}
|
|
292
285
|
|
|
293
286
|
lastScrollTopValue = scrollTop;
|
|
294
287
|
});
|
|
295
288
|
|
|
296
|
-
this.codeScroller.addEventListener( 'wheel',
|
|
297
|
-
const dX = (e.deltaY > 0.0 ? 10.0 : -10.0) * ( e.shiftKey ? 1.0 : 0.0 );
|
|
289
|
+
this.codeScroller.addEventListener( 'wheel', e => {
|
|
290
|
+
const dX = ( e.deltaY > 0.0 ? 10.0 : -10.0 ) * ( e.shiftKey ? 1.0 : 0.0 );
|
|
298
291
|
if( dX != 0.0 ) this.setScrollBarValue( 'horizontal', dX );
|
|
299
292
|
});
|
|
300
293
|
}
|
|
@@ -309,13 +302,13 @@ class CodeEditor {
|
|
|
309
302
|
// Add custom vertical scroll bar
|
|
310
303
|
{
|
|
311
304
|
this.vScrollbar = new ScrollBar( this, ScrollBar.SCROLLBAR_VERTICAL );
|
|
312
|
-
area.attach(this.vScrollbar.root);
|
|
305
|
+
area.attach( this.vScrollbar.root );
|
|
313
306
|
}
|
|
314
307
|
|
|
315
308
|
// Add custom horizontal scroll bar
|
|
316
309
|
{
|
|
317
310
|
this.hScrollbar = new ScrollBar( this, ScrollBar.SCROLLBAR_HORIZONTAL );
|
|
318
|
-
area.attach(this.hScrollbar.root);
|
|
311
|
+
area.attach( this.hScrollbar.root );
|
|
319
312
|
}
|
|
320
313
|
|
|
321
314
|
// Add autocomplete box
|
|
@@ -323,7 +316,7 @@ class CodeEditor {
|
|
|
323
316
|
var box = document.createElement( 'div' );
|
|
324
317
|
box.className = "autocomplete";
|
|
325
318
|
this.autocomplete = box;
|
|
326
|
-
this.tabs.area.attach(box);
|
|
319
|
+
this.tabs.area.attach( box );
|
|
327
320
|
|
|
328
321
|
this.isAutoCompleteActive = false;
|
|
329
322
|
}
|
|
@@ -387,7 +380,8 @@ class CodeEditor {
|
|
|
387
380
|
'WGSL': { },
|
|
388
381
|
'JSON': { },
|
|
389
382
|
'XML': { },
|
|
390
|
-
'Python': { },
|
|
383
|
+
'Python': { singleLineCommentToken: '#' },
|
|
384
|
+
'HTML': { },
|
|
391
385
|
'Batch': { blockComments: false, singleLineCommentToken: '::' }
|
|
392
386
|
};
|
|
393
387
|
|
|
@@ -411,7 +405,8 @@ class CodeEditor {
|
|
|
411
405
|
'texture_storage_2d_array', 'texture_storage_3d'],
|
|
412
406
|
'Python': ['False', 'def', 'None', 'True', 'in', 'is', 'and', 'lambda', 'nonlocal', 'not', 'or'],
|
|
413
407
|
'Batch': ['set', 'SET', 'echo', 'ECHO', 'off', 'OFF', 'del', 'DEL', 'defined', 'DEFINED', 'setlocal', 'SETLOCAL', 'enabledelayedexpansion', 'ENABLEDELAYEDEXPANSION', 'driverquery',
|
|
414
|
-
'DRIVERQUERY', 'print', 'PRINT']
|
|
408
|
+
'DRIVERQUERY', 'print', 'PRINT'],
|
|
409
|
+
'HTML': ['html', 'meta', 'title', 'link', 'script', 'body', 'DOCTYPE', 'head'],
|
|
415
410
|
};
|
|
416
411
|
this.utils = { // These ones don't have hightlight, used as suggestions to autocomplete only...
|
|
417
412
|
'JavaScript': ['querySelector', 'body', 'addEventListener', 'removeEventListener', 'remove', 'sort', 'keys', 'filter', 'isNaN', 'parseFloat', 'parseInt', 'EPSILON', 'isFinite',
|
|
@@ -428,13 +423,14 @@ class CodeEditor {
|
|
|
428
423
|
'Python': ['int', 'type', 'float', 'map', 'list', 'ArithmeticError', 'AssertionError', 'AttributeError', 'Exception', 'EOFError', 'FloatingPointError', 'GeneratorExit',
|
|
429
424
|
'ImportError', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'NotImplementedError', 'OSError',
|
|
430
425
|
'OverflowError', 'ReferenceError', 'RuntimeError', 'StopIteration', 'SyntaxError', 'TabError', 'SystemError', 'SystemExit', 'TypeError', 'UnboundLocalError',
|
|
431
|
-
'UnicodeError', 'UnicodeEncodeError', 'UnicodeDecodeError', 'UnicodeTranslateError', 'ValueError', 'ZeroDivisionError'
|
|
426
|
+
'UnicodeError', 'UnicodeEncodeError', 'UnicodeDecodeError', 'UnicodeTranslateError', 'ValueError', 'ZeroDivisionError'],
|
|
432
427
|
'C++': ['uint8_t', 'uint16_t', 'uint32_t']
|
|
433
428
|
};
|
|
434
429
|
this.builtin = {
|
|
435
430
|
'JavaScript': ['document', 'console', 'window', 'navigator', 'performance'],
|
|
436
431
|
'CSS': ['*', '!important'],
|
|
437
|
-
'C++': ['vector', 'list', 'map']
|
|
432
|
+
'C++': ['vector', 'list', 'map'],
|
|
433
|
+
'HTML': ['type', 'xmlns', 'PUBLIC', 'http-equiv', 'src', 'lang', 'href', 'rel', 'content', 'xml'], // attributes
|
|
438
434
|
};
|
|
439
435
|
this.statementsAndDeclarations = {
|
|
440
436
|
'JavaScript': ['for', 'if', 'else', 'case', 'switch', 'return', 'while', 'continue', 'break', 'do', 'import', 'from', 'throw', 'async', 'try', 'catch', 'await'],
|
|
@@ -454,6 +450,7 @@ class CodeEditor {
|
|
|
454
450
|
'CSS': ['{', '}', '(', ')', '*'],
|
|
455
451
|
'Python': ['<', '>', '[', ']', '(', ')', '='],
|
|
456
452
|
'Batch': ['[', ']', '(', ')', '%'],
|
|
453
|
+
'HTML': ['<', '>', '/']
|
|
457
454
|
};
|
|
458
455
|
|
|
459
456
|
// Convert reserved word arrays to maps so we can search tokens faster
|
|
@@ -559,9 +556,15 @@ class CodeEditor {
|
|
|
559
556
|
if( this.selection )
|
|
560
557
|
lastX += this.selection.chars;
|
|
561
558
|
|
|
562
|
-
this.
|
|
559
|
+
if( !this.selection )
|
|
560
|
+
this.startSelection( cursor );
|
|
563
561
|
var string = this.code.lines[ ln ].substring( idx, lastX );
|
|
564
|
-
this.selection.
|
|
562
|
+
if( this.selection.sameLine() )
|
|
563
|
+
this.selection.selectInline( idx, cursor.line, this.measureString( string ) );
|
|
564
|
+
else
|
|
565
|
+
{
|
|
566
|
+
this.processSelection();
|
|
567
|
+
}
|
|
565
568
|
} else if( !e.keepSelection )
|
|
566
569
|
this.endSelection();
|
|
567
570
|
});
|
|
@@ -570,18 +573,25 @@ class CodeEditor {
|
|
|
570
573
|
|
|
571
574
|
if( e.shiftKey || e._shiftKey ) {
|
|
572
575
|
|
|
573
|
-
var string = this.code.lines[ ln ].substring(cursor.position);
|
|
576
|
+
var string = this.code.lines[ ln ].substring( cursor.position );
|
|
574
577
|
if( !this.selection )
|
|
575
578
|
this.startSelection( cursor );
|
|
576
|
-
this.selection.
|
|
579
|
+
if( this.selection.sameLine() )
|
|
580
|
+
this.selection.selectInline(cursor.position, cursor.line, this.measureString( string ));
|
|
581
|
+
else
|
|
582
|
+
{
|
|
583
|
+
this.resetCursorPos( CodeEditor.CURSOR_LEFT );
|
|
584
|
+
this.cursorToString( cursor, this.code.lines[ ln ] );
|
|
585
|
+
this.processSelection();
|
|
586
|
+
}
|
|
577
587
|
} else
|
|
578
588
|
this.endSelection();
|
|
579
589
|
|
|
580
590
|
this.resetCursorPos( CodeEditor.CURSOR_LEFT );
|
|
581
591
|
this.cursorToString( cursor, this.code.lines[ ln ] );
|
|
582
592
|
|
|
583
|
-
const last_char = (this.code.clientWidth / this.charWidth)|0;
|
|
584
|
-
this.setScrollLeft( cursor.position >= last_char ? (cursor.position - last_char) * this.charWidth : 0 );
|
|
593
|
+
const last_char = ( this.code.clientWidth / this.charWidth )|0;
|
|
594
|
+
this.setScrollLeft( cursor.position >= last_char ? ( cursor.position - last_char ) * this.charWidth : 0 );
|
|
585
595
|
});
|
|
586
596
|
|
|
587
597
|
this.action( 'Enter', true, ( ln, cursor, e ) => {
|
|
@@ -708,7 +718,8 @@ class CodeEditor {
|
|
|
708
718
|
var diff = Math.max(cursor.position - from, 1);
|
|
709
719
|
var substr = word.substr(0, diff);
|
|
710
720
|
// Selections...
|
|
711
|
-
if( e.shiftKey ) if( !this.selection ) this.startSelection( cursor );
|
|
721
|
+
if( e.shiftKey ) { if( !this.selection ) this.startSelection( cursor ); }
|
|
722
|
+
else this.endSelection();
|
|
712
723
|
this.cursorToString(cursor, substr, true);
|
|
713
724
|
if( e.shiftKey ) this.processSelection();
|
|
714
725
|
}
|
|
@@ -772,7 +783,8 @@ class CodeEditor {
|
|
|
772
783
|
var diff = cursor.position - from;
|
|
773
784
|
var substr = word.substr( diff );
|
|
774
785
|
// Selections...
|
|
775
|
-
if( e.shiftKey ) if( !this.selection ) this.startSelection( cursor );
|
|
786
|
+
if( e.shiftKey ) { if( !this.selection ) this.startSelection( cursor ); }
|
|
787
|
+
else this.endSelection();
|
|
776
788
|
this.cursorToString( cursor, substr);
|
|
777
789
|
if( e.shiftKey ) this.processSelection();
|
|
778
790
|
} else {
|
|
@@ -970,12 +982,28 @@ class CodeEditor {
|
|
|
970
982
|
|
|
971
983
|
_addUndoStep( cursor ) {
|
|
972
984
|
|
|
985
|
+
const d = new Date();
|
|
986
|
+
const current = d.getTime();
|
|
987
|
+
|
|
988
|
+
if( !this._lastTime ) {
|
|
989
|
+
this._lastTime = current;
|
|
990
|
+
} else {
|
|
991
|
+
if( ( current - this._lastTime ) > 3000 ){
|
|
992
|
+
this._lastTime = null;
|
|
993
|
+
} else {
|
|
994
|
+
// If time not enough, reset timer
|
|
995
|
+
this._lastTime = current;
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
|
|
973
1000
|
var cursor = cursor ?? this.cursors.children[ 0 ];
|
|
974
1001
|
|
|
975
1002
|
this.code.undoSteps.push( {
|
|
976
1003
|
lines: LX.deepCopy( this.code.lines ),
|
|
977
1004
|
cursor: this.saveCursor( cursor ),
|
|
978
|
-
line: cursor.line
|
|
1005
|
+
line: cursor.line,
|
|
1006
|
+
position: cursor.position
|
|
979
1007
|
} );
|
|
980
1008
|
}
|
|
981
1009
|
|
|
@@ -1004,6 +1032,7 @@ class CodeEditor {
|
|
|
1004
1032
|
case 'wgsl': return this._changeLanguage( 'WGSL' );
|
|
1005
1033
|
case 'py': return this._changeLanguage( 'Python' );
|
|
1006
1034
|
case 'bat': return this._changeLanguage( 'Batch' );
|
|
1035
|
+
case 'html': return this._changeLanguage( 'HTML' );
|
|
1007
1036
|
case 'txt':
|
|
1008
1037
|
default:
|
|
1009
1038
|
this._changeLanguage( 'Plain Text' );
|
|
@@ -1064,7 +1093,7 @@ class CodeEditor {
|
|
|
1064
1093
|
});
|
|
1065
1094
|
}
|
|
1066
1095
|
|
|
1067
|
-
addTab(name, selected, title) {
|
|
1096
|
+
addTab( name, selected, title ) {
|
|
1068
1097
|
|
|
1069
1098
|
if(this.openedTabs[ name ])
|
|
1070
1099
|
{
|
|
@@ -1082,6 +1111,8 @@ class CodeEditor {
|
|
|
1082
1111
|
code.tabName = name;
|
|
1083
1112
|
code.title = title ?? name;
|
|
1084
1113
|
code.tokens = {};
|
|
1114
|
+
code.style.left = "0px";
|
|
1115
|
+
code.style.top = "0px";
|
|
1085
1116
|
|
|
1086
1117
|
code.addEventListener( 'dragenter', function(e) {
|
|
1087
1118
|
e.preventDefault();
|
|
@@ -1100,22 +1131,32 @@ class CodeEditor {
|
|
|
1100
1131
|
|
|
1101
1132
|
this.openedTabs[ name ] = code;
|
|
1102
1133
|
|
|
1103
|
-
|
|
1134
|
+
const ext = LX.getExtension( name );
|
|
1104
1135
|
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1136
|
+
this.tabs.add(name, code, {
|
|
1137
|
+
selected: selected,
|
|
1138
|
+
fixed: (name === '+') ,
|
|
1139
|
+
title: code.title,
|
|
1140
|
+
icon: ext == 'html' ? "fa-solid fa-code orange" :
|
|
1141
|
+
ext == 'js' ? "images/js.png" :
|
|
1142
|
+
ext == 'py' ? "images/py.png" : undefined,
|
|
1143
|
+
onSelect: (e, tabname) => {
|
|
1110
1144
|
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1145
|
+
if(tabname == '+')
|
|
1146
|
+
{
|
|
1147
|
+
this._onNewTab( e );
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
var cursor = cursor ?? this.cursors.children[ 0 ];
|
|
1152
|
+
this.saveCursor( cursor, this.code.cursorState );
|
|
1153
|
+
this.code = this.openedTabs[ tabname ];
|
|
1154
|
+
this.restoreCursor( cursor, this.code.cursorState );
|
|
1155
|
+
this.endSelection();
|
|
1156
|
+
this._changeLanguageFromExtension( LX.getExtension( tabname ) );
|
|
1157
|
+
this._refreshCodeInfo( cursor.line, cursor.position );
|
|
1158
|
+
}
|
|
1159
|
+
});
|
|
1119
1160
|
|
|
1120
1161
|
// Move into the sizer..
|
|
1121
1162
|
this.codeSizer.appendChild( code );
|
|
@@ -1221,7 +1262,7 @@ class CodeEditor {
|
|
|
1221
1262
|
this.resetCursorPos( CodeEditor.CURSOR_LEFT );
|
|
1222
1263
|
this.cursorToPosition( cursor, from );
|
|
1223
1264
|
this.startSelection( cursor );
|
|
1224
|
-
this.selection.selectInline(from, cursor.line, this.measureString(word));
|
|
1265
|
+
this.selection.selectInline( from, cursor.line, this.measureString( word ) );
|
|
1225
1266
|
this.cursorToString( cursor, word ); // Go to the end of the word
|
|
1226
1267
|
break;
|
|
1227
1268
|
// Select entire line
|
|
@@ -1248,8 +1289,8 @@ class CodeEditor {
|
|
|
1248
1289
|
m.add( "Paste", () => { this._pasteContent(); } );
|
|
1249
1290
|
m.add( "" );
|
|
1250
1291
|
m.add( "Format/JSON", () => {
|
|
1251
|
-
let json = this.toJSONFormat(this.getText());
|
|
1252
|
-
this.code.lines = json.split("\n");
|
|
1292
|
+
let json = this.toJSONFormat( this.getText() );
|
|
1293
|
+
this.code.lines = json.split( "\n" );
|
|
1253
1294
|
this.processLines();
|
|
1254
1295
|
} );
|
|
1255
1296
|
}
|
|
@@ -1285,7 +1326,7 @@ class CodeEditor {
|
|
|
1285
1326
|
|
|
1286
1327
|
var cursor = this.cursors.children[ 0 ];
|
|
1287
1328
|
|
|
1288
|
-
if(e) this.processClick( e, true );
|
|
1329
|
+
if( e ) this.processClick( e, true );
|
|
1289
1330
|
if( !this.selection )
|
|
1290
1331
|
this.startSelection( cursor );
|
|
1291
1332
|
|
|
@@ -1307,20 +1348,25 @@ class CodeEditor {
|
|
|
1307
1348
|
// Selection goes down...
|
|
1308
1349
|
if( deltaY >= 0 )
|
|
1309
1350
|
{
|
|
1310
|
-
while( deltaY < (this.selections.childElementCount - 1) )
|
|
1351
|
+
while( deltaY < ( this.selections.childElementCount - 1 ) )
|
|
1311
1352
|
deleteElement( this.selections.lastChild );
|
|
1312
1353
|
|
|
1313
1354
|
for(let i = fromY; i <= toY; i++){
|
|
1314
1355
|
|
|
1315
1356
|
const sId = i - fromY;
|
|
1357
|
+
const isVisible = i >= this.visibleLinesViewport.x && i <= this.visibleLinesViewport.y;
|
|
1358
|
+
let domEl = null;
|
|
1316
1359
|
|
|
1317
|
-
|
|
1318
|
-
let domEl = this.selections.childNodes[sId];
|
|
1319
|
-
if(!domEl)
|
|
1360
|
+
if( isVisible )
|
|
1320
1361
|
{
|
|
1321
|
-
|
|
1322
|
-
domEl
|
|
1323
|
-
|
|
1362
|
+
// Make sure that the line selection is generated...
|
|
1363
|
+
domEl = this.selections.childNodes[ sId ];
|
|
1364
|
+
if(!domEl)
|
|
1365
|
+
{
|
|
1366
|
+
domEl = document.createElement( 'div' );
|
|
1367
|
+
domEl.className = "lexcodeselection";
|
|
1368
|
+
this.selections.appendChild( domEl );
|
|
1369
|
+
}
|
|
1324
1370
|
}
|
|
1325
1371
|
|
|
1326
1372
|
// Compute new width and selection margins
|
|
@@ -1329,67 +1375,80 @@ class CodeEditor {
|
|
|
1329
1375
|
if(sId == 0) // First line 2 cases (single line, multiline)
|
|
1330
1376
|
{
|
|
1331
1377
|
const reverse = fromX > toX;
|
|
1332
|
-
if(deltaY == 0) string = !reverse ? this.code.lines[ i ].substring(fromX, toX) : this.code.lines[ i ].substring(toX, fromX);
|
|
1333
|
-
else string = this.code.lines[ i ].substr(fromX);
|
|
1378
|
+
if(deltaY == 0) string = !reverse ? this.code.lines[ i ].substring( fromX, toX ) : this.code.lines[ i ].substring(toX, fromX);
|
|
1379
|
+
else string = this.code.lines[ i ].substr( fromX );
|
|
1334
1380
|
const pixels = (reverse && deltaY == 0 ? toX : fromX) * this.charWidth;
|
|
1335
|
-
domEl.style.left = "calc(" + pixels + "px + " + this.xPadding + ")";
|
|
1381
|
+
if( isVisible ) domEl.style.left = "calc(" + pixels + "px + " + this.xPadding + ")";
|
|
1336
1382
|
}
|
|
1337
1383
|
else
|
|
1338
1384
|
{
|
|
1339
|
-
string = (i == toY) ? this.code.lines[ i ].substring(0, toX) : this.code.lines[ i ]; // Last line, any multiple line...
|
|
1340
|
-
domEl.style.left = this.xPadding;
|
|
1385
|
+
string = (i == toY) ? this.code.lines[ i ].substring( 0, toX ) : this.code.lines[ i ]; // Last line, any multiple line...
|
|
1386
|
+
if( isVisible ) domEl.style.left = this.xPadding;
|
|
1341
1387
|
}
|
|
1342
1388
|
|
|
1343
|
-
const stringWidth = this.measureString(string);
|
|
1344
|
-
domEl.style.width = (stringWidth || 8) + "px";
|
|
1345
|
-
domEl._top = i * this.lineHeight;
|
|
1346
|
-
domEl.style.top = domEl._top + "px";
|
|
1389
|
+
const stringWidth = this.measureString( string );
|
|
1347
1390
|
this.selection.chars += stringWidth / this.charWidth;
|
|
1391
|
+
|
|
1392
|
+
if( isVisible )
|
|
1393
|
+
{
|
|
1394
|
+
domEl.style.width = (stringWidth || 8) + "px";
|
|
1395
|
+
domEl._top = i * this.lineHeight;
|
|
1396
|
+
domEl.style.top = domEl._top + "px";
|
|
1397
|
+
}
|
|
1348
1398
|
}
|
|
1349
1399
|
}
|
|
1350
1400
|
else // Selection goes up...
|
|
1351
1401
|
{
|
|
1352
|
-
while( Math.abs(deltaY) < (this.selections.childElementCount - 1) )
|
|
1402
|
+
while( Math.abs( deltaY ) < ( this.selections.childElementCount - 1 ) )
|
|
1353
1403
|
deleteElement( this.selections.firstChild );
|
|
1354
1404
|
|
|
1355
|
-
for(let i = toY; i <= fromY; i++){
|
|
1405
|
+
for( let i = toY; i <= fromY; i++ ){
|
|
1356
1406
|
|
|
1357
1407
|
const sId = i - toY;
|
|
1408
|
+
const isVisible = i >= this.visibleLinesViewport.x && i <= this.visibleLinesViewport.y;
|
|
1409
|
+
let domEl = null;
|
|
1358
1410
|
|
|
1359
|
-
|
|
1360
|
-
let domEl = this.selections.childNodes[sId];
|
|
1361
|
-
if(!domEl)
|
|
1411
|
+
if( isVisible )
|
|
1362
1412
|
{
|
|
1363
|
-
|
|
1364
|
-
domEl
|
|
1365
|
-
|
|
1413
|
+
// Make sure that the line selection is generated...
|
|
1414
|
+
domEl = this.selections.childNodes[ sId ];
|
|
1415
|
+
if(!domEl)
|
|
1416
|
+
{
|
|
1417
|
+
domEl = document.createElement( 'div' );
|
|
1418
|
+
domEl.className = "lexcodeselection";
|
|
1419
|
+
this.selections.appendChild( domEl );
|
|
1420
|
+
}
|
|
1366
1421
|
}
|
|
1367
1422
|
|
|
1368
1423
|
// Compute new width and selection margins
|
|
1369
1424
|
let string;
|
|
1370
1425
|
|
|
1371
|
-
if(sId == 0)
|
|
1426
|
+
if( sId == 0 )
|
|
1372
1427
|
{
|
|
1373
1428
|
string = this.code.lines[ i ].substr(toX);
|
|
1374
1429
|
const pixels = toX * this.charWidth;
|
|
1375
|
-
domEl.style.left = "calc(" + pixels + "px + " + this.xPadding + ")";
|
|
1430
|
+
if( isVisible ) domEl.style.left = "calc(" + pixels + "px + " + this.xPadding + ")";
|
|
1376
1431
|
}
|
|
1377
1432
|
else
|
|
1378
1433
|
{
|
|
1379
1434
|
string = (i == fromY) ? this.code.lines[ i ].substring(0, fromX) : this.code.lines[ i ]; // Last line, any multiple line...
|
|
1380
|
-
domEl.style.left = this.xPadding;
|
|
1435
|
+
if( isVisible ) domEl.style.left = this.xPadding;
|
|
1381
1436
|
}
|
|
1382
1437
|
|
|
1383
|
-
const stringWidth = this.measureString(string);
|
|
1384
|
-
domEl.style.width = (stringWidth || 8) + "px";
|
|
1385
|
-
domEl._top = i * this.lineHeight;
|
|
1386
|
-
domEl.style.top = domEl._top + "px";
|
|
1438
|
+
const stringWidth = this.measureString( string );
|
|
1387
1439
|
this.selection.chars += stringWidth / this.charWidth;
|
|
1440
|
+
|
|
1441
|
+
if( isVisible )
|
|
1442
|
+
{
|
|
1443
|
+
domEl.style.width = (stringWidth || 8) + "px";
|
|
1444
|
+
domEl._top = i * this.lineHeight;
|
|
1445
|
+
domEl.style.top = domEl._top + "px";
|
|
1446
|
+
}
|
|
1388
1447
|
}
|
|
1389
1448
|
}
|
|
1390
1449
|
}
|
|
1391
1450
|
|
|
1392
|
-
async processKey(e) {
|
|
1451
|
+
async processKey( e ) {
|
|
1393
1452
|
|
|
1394
1453
|
if( !this.code )
|
|
1395
1454
|
return;
|
|
@@ -1399,7 +1458,7 @@ class CodeEditor {
|
|
|
1399
1458
|
const skip_undo = e.detail.skip_undo ?? false;
|
|
1400
1459
|
|
|
1401
1460
|
// keys with length > 1 are probably special keys
|
|
1402
|
-
if( key.length > 1 && this.specialKeys.indexOf(key) == -1 )
|
|
1461
|
+
if( key.length > 1 && this.specialKeys.indexOf( key ) == -1 )
|
|
1403
1462
|
return;
|
|
1404
1463
|
|
|
1405
1464
|
let cursor = this.cursors.children[ 0 ];
|
|
@@ -1446,7 +1505,6 @@ class CodeEditor {
|
|
|
1446
1505
|
return;
|
|
1447
1506
|
const step = this.code.undoSteps.pop();
|
|
1448
1507
|
this.code.lines = step.lines;
|
|
1449
|
-
cursor.line = step.line;
|
|
1450
1508
|
this.restoreCursor( cursor, step.cursor );
|
|
1451
1509
|
this.processLines();
|
|
1452
1510
|
return;
|
|
@@ -1493,23 +1551,9 @@ class CodeEditor {
|
|
|
1493
1551
|
|
|
1494
1552
|
// Add undo steps
|
|
1495
1553
|
|
|
1496
|
-
|
|
1497
|
-
const current = d.getTime();
|
|
1498
|
-
|
|
1499
|
-
if( !skip_undo )
|
|
1554
|
+
if( !skip_undo && this.code.lines.length )
|
|
1500
1555
|
{
|
|
1501
|
-
|
|
1502
|
-
this._lastTime = current;
|
|
1503
|
-
this._addUndoStep( cursor );
|
|
1504
|
-
} else {
|
|
1505
|
-
if( (current - this._lastTime) > 3000 && this.code.lines.length){
|
|
1506
|
-
this._lastTime = null;
|
|
1507
|
-
this._addUndoStep( cursor );
|
|
1508
|
-
}else{
|
|
1509
|
-
// If time not enough, reset timer
|
|
1510
|
-
this._lastTime = current;
|
|
1511
|
-
}
|
|
1512
|
-
}
|
|
1556
|
+
this._addUndoStep( cursor );
|
|
1513
1557
|
}
|
|
1514
1558
|
|
|
1515
1559
|
// Some custom cases for word enclosing (), {}, "", '', ...
|
|
@@ -1517,7 +1561,7 @@ class CodeEditor {
|
|
|
1517
1561
|
const enclosableKeys = ["\"", "'", "(", "{"];
|
|
1518
1562
|
if( enclosableKeys.indexOf( key ) > -1 )
|
|
1519
1563
|
{
|
|
1520
|
-
if( this.
|
|
1564
|
+
if( this._encloseSelectedWordWithKey(key, lidx, cursor) )
|
|
1521
1565
|
return;
|
|
1522
1566
|
}
|
|
1523
1567
|
|
|
@@ -1567,6 +1611,14 @@ class CodeEditor {
|
|
|
1567
1611
|
// Update only the current line, since it's only an appended key
|
|
1568
1612
|
this.processLine( lidx );
|
|
1569
1613
|
|
|
1614
|
+
// We are out of the viewport and max length is different? Resize scrollbars...
|
|
1615
|
+
const maxLineLength = this.getMaxLineLength();
|
|
1616
|
+
const numViewportChars = Math.floor( this.codeScroller.clientWidth / this.charWidth );
|
|
1617
|
+
if( maxLineLength >= numViewportChars && maxLineLength != this._lastMaxLineLength )
|
|
1618
|
+
{
|
|
1619
|
+
this.resize( maxLineLength );
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1570
1622
|
// Manage autocomplete
|
|
1571
1623
|
|
|
1572
1624
|
if( this.useAutoComplete )
|
|
@@ -1587,6 +1639,10 @@ class CodeEditor {
|
|
|
1587
1639
|
text_to_copy = "\n" + this.code.lines[ cursor.line ];
|
|
1588
1640
|
}
|
|
1589
1641
|
else {
|
|
1642
|
+
|
|
1643
|
+
// Some selections don't depend on mouse up..
|
|
1644
|
+
if( this.selection ) this.selection.invertIfNecessary();
|
|
1645
|
+
|
|
1590
1646
|
const separator = "_NEWLINE_";
|
|
1591
1647
|
let code = this.code.lines.join(separator);
|
|
1592
1648
|
|
|
@@ -1603,7 +1659,7 @@ class CodeEditor {
|
|
|
1603
1659
|
text_to_copy = lines.join('\n');
|
|
1604
1660
|
}
|
|
1605
1661
|
|
|
1606
|
-
navigator.clipboard.writeText(text_to_copy).then(() => console.log("Successfully copied"), (err) => console.error("Error"));
|
|
1662
|
+
navigator.clipboard.writeText( text_to_copy ).then(() => console.log("Successfully copied"), (err) => console.error("Error"));
|
|
1607
1663
|
}
|
|
1608
1664
|
|
|
1609
1665
|
async _cutContent() {
|
|
@@ -1619,6 +1675,10 @@ class CodeEditor {
|
|
|
1619
1675
|
this.resetCursorPos( CodeEditor.CURSOR_LEFT );
|
|
1620
1676
|
}
|
|
1621
1677
|
else {
|
|
1678
|
+
|
|
1679
|
+
// Some selections don't depend on mouse up..
|
|
1680
|
+
if( this.selection ) this.selection.invertIfNecessary();
|
|
1681
|
+
|
|
1622
1682
|
const separator = "_NEWLINE_";
|
|
1623
1683
|
let code = this.code.lines.join(separator);
|
|
1624
1684
|
|
|
@@ -1637,7 +1697,7 @@ class CodeEditor {
|
|
|
1637
1697
|
this.deleteSelection( cursor );
|
|
1638
1698
|
}
|
|
1639
1699
|
|
|
1640
|
-
navigator.clipboard.writeText(text_to_cut).then(() => console.log("Successfully cut"), (err) => console.error("Error"));
|
|
1700
|
+
navigator.clipboard.writeText( text_to_cut ).then(() => console.log("Successfully cut"), (err) => console.error("Error"));
|
|
1641
1701
|
}
|
|
1642
1702
|
|
|
1643
1703
|
action( key, deleteSelection, fn ) {
|
|
@@ -1662,7 +1722,7 @@ class CodeEditor {
|
|
|
1662
1722
|
|
|
1663
1723
|
toLocalLine( line ) {
|
|
1664
1724
|
|
|
1665
|
-
const d = Math.max( this.
|
|
1725
|
+
const d = Math.max( this.firstLineInViewport - this.lineScrollMargin.x, 0 );
|
|
1666
1726
|
return Math.min( Math.max( line - d, 0 ), this.code.lines.length - 1 );
|
|
1667
1727
|
}
|
|
1668
1728
|
|
|
@@ -1672,38 +1732,34 @@ class CodeEditor {
|
|
|
1672
1732
|
}
|
|
1673
1733
|
|
|
1674
1734
|
processLines( mode ) {
|
|
1675
|
-
|
|
1676
|
-
mode = mode ?? CodeEditor.KEEP_VISIBLE_LINES;
|
|
1677
|
-
|
|
1678
|
-
// console.clear();
|
|
1679
|
-
console.log("--------------------------------------------");
|
|
1680
|
-
|
|
1681
|
-
const lastScrollTop = this.getScrollTop();
|
|
1735
|
+
|
|
1682
1736
|
const start = performance.now();
|
|
1683
1737
|
|
|
1684
|
-
var gutter_html = ""
|
|
1685
|
-
|
|
1738
|
+
var gutter_html = "";
|
|
1739
|
+
var code_html = "";
|
|
1740
|
+
|
|
1741
|
+
// Reset all lines content
|
|
1686
1742
|
this.code.innerHTML = "";
|
|
1687
|
-
|
|
1743
|
+
|
|
1688
1744
|
// Get info about lines in viewport
|
|
1689
|
-
const
|
|
1690
|
-
|
|
1745
|
+
const lastScrollTop = this.getScrollTop();
|
|
1746
|
+
this.firstLineInViewport = ( mode ?? CodeEditor.KEEP_VISIBLE_LINES ) & CodeEditor.UPDATE_VISIBLE_LINES ?
|
|
1747
|
+
( (lastScrollTop / this.lineHeight)|0 ) : this.firstLineInViewport;
|
|
1691
1748
|
const totalLinesInViewport = ((this.codeScroller.offsetHeight - 36) / this.lineHeight)|0;
|
|
1692
|
-
this.
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
Math.max( firstLineInViewport - this.lineScrollMargin.x, 0 ),
|
|
1696
|
-
Math.min( firstLineInViewport + totalLinesInViewport + this.lineScrollMargin.y, this.code.lines.length )
|
|
1749
|
+
this.visibleLinesViewport = new LX.vec2(
|
|
1750
|
+
Math.max( this.firstLineInViewport - this.lineScrollMargin.x, 0 ),
|
|
1751
|
+
Math.min( this.firstLineInViewport + totalLinesInViewport + this.lineScrollMargin.y, this.code.lines.length )
|
|
1697
1752
|
);
|
|
1698
1753
|
|
|
1699
1754
|
// Add remaining lines if we are near the end of the scroll
|
|
1700
1755
|
{
|
|
1701
|
-
const diff = Math.max( this.code.lines.length -
|
|
1702
|
-
if( diff
|
|
1703
|
-
|
|
1756
|
+
const diff = Math.max( this.code.lines.length - this.visibleLinesViewport.y, 0 );
|
|
1757
|
+
if( diff <= this.lineScrollMargin.y )
|
|
1758
|
+
this.visibleLinesViewport.y += diff;
|
|
1704
1759
|
}
|
|
1705
|
-
|
|
1706
|
-
|
|
1760
|
+
|
|
1761
|
+
// Process visible lines
|
|
1762
|
+
for( let i = this.visibleLinesViewport.x; i < this.visibleLinesViewport.y; ++i )
|
|
1707
1763
|
{
|
|
1708
1764
|
gutter_html += "<span>" + (i + 1) + "</span>";
|
|
1709
1765
|
code_html += this.processLine( i, true );
|
|
@@ -1711,24 +1767,23 @@ class CodeEditor {
|
|
|
1711
1767
|
|
|
1712
1768
|
this.code.innerHTML = code_html;
|
|
1713
1769
|
|
|
1714
|
-
console.log("RANGE:",
|
|
1715
|
-
console.log( "Num lines processed:", (
|
|
1716
|
-
console.log("--------------------------------------------");
|
|
1770
|
+
// console.log("RANGE:", this.visibleLinesViewport);
|
|
1771
|
+
// console.log( "Num lines processed:", (this.visibleLinesViewport.y - this.visibleLinesViewport.x), performance.now() - start );
|
|
1772
|
+
// console.log("--------------------------------------------");
|
|
1717
1773
|
|
|
1774
|
+
// Update scroll data
|
|
1718
1775
|
this.codeScroller.scrollTop = lastScrollTop;
|
|
1776
|
+
this.code.style.top = ( this.visibleLinesViewport.x * this.lineHeight ) + "px";
|
|
1719
1777
|
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
const scrollWidth = this.getMaxLineLength() * this.charWidth;
|
|
1724
|
-
const scrollHeight = this.code.lines.length * this.lineHeight + 10; // scrollbar offset
|
|
1725
|
-
|
|
1726
|
-
this.codeSizer.style.minWidth = scrollWidth + "px";
|
|
1727
|
-
this.codeSizer.style.minHeight = scrollHeight + "px";
|
|
1778
|
+
// Update selections
|
|
1779
|
+
if( this.selection )
|
|
1780
|
+
this.processSelection( null, true );
|
|
1728
1781
|
|
|
1729
|
-
|
|
1782
|
+
// Clear tmp vars
|
|
1783
|
+
delete this._buildingString;
|
|
1784
|
+
delete this._pendingString;
|
|
1730
1785
|
|
|
1731
|
-
|
|
1786
|
+
this.resize();
|
|
1732
1787
|
}
|
|
1733
1788
|
|
|
1734
1789
|
processLine( linenum, force ) {
|
|
@@ -1796,7 +1851,7 @@ class CodeEditor {
|
|
|
1796
1851
|
delete this._buildingBlockComment;
|
|
1797
1852
|
}
|
|
1798
1853
|
|
|
1799
|
-
line_inner_html += this.
|
|
1854
|
+
line_inner_html += this._evaluateToken( token, prev, next, (i == tokensToEvaluate.length - 1) );
|
|
1800
1855
|
}
|
|
1801
1856
|
|
|
1802
1857
|
return UPDATE_LINE( line_inner_html );
|
|
@@ -1829,7 +1884,7 @@ class CodeEditor {
|
|
|
1829
1884
|
|
|
1830
1885
|
const singleLineCommentToken = this.languages[ this.highlight ].singleLineCommentToken ?? this.defaultSingleLineCommentToken;
|
|
1831
1886
|
const idx = linestring.indexOf( singleLineCommentToken );
|
|
1832
|
-
|
|
1887
|
+
|
|
1833
1888
|
if( idx > -1 )
|
|
1834
1889
|
{
|
|
1835
1890
|
const stringKeys = Object.values( this.stringKeys );
|
|
@@ -1870,7 +1925,7 @@ class CodeEditor {
|
|
|
1870
1925
|
tokensToEvaluate.push( t );
|
|
1871
1926
|
};
|
|
1872
1927
|
|
|
1873
|
-
let iter = linestring.matchAll(/(::|[\[\](){}<>.,;:*"'
|
|
1928
|
+
let iter = linestring.matchAll(/(::|[\[\](){}<>.,;:*"'%@!/= ])/g);
|
|
1874
1929
|
let subtokens = iter.next();
|
|
1875
1930
|
if( subtokens.value )
|
|
1876
1931
|
{
|
|
@@ -1923,7 +1978,7 @@ class CodeEditor {
|
|
|
1923
1978
|
return kindArray[this.highlight] && kindArray[this.highlight][token] != undefined;
|
|
1924
1979
|
}
|
|
1925
1980
|
|
|
1926
|
-
|
|
1981
|
+
_evaluateToken( token, prev, next, isLastToken ) {
|
|
1927
1982
|
|
|
1928
1983
|
const highlight = this.highlight.replace(/\s/g, '').replaceAll("+", "p").toLowerCase();
|
|
1929
1984
|
const customStringKeys = Object.assign( {}, this.stringKeys );
|
|
@@ -1950,7 +2005,7 @@ class CodeEditor {
|
|
|
1950
2005
|
{
|
|
1951
2006
|
if( this._buildingString != undefined )
|
|
1952
2007
|
{
|
|
1953
|
-
this.
|
|
2008
|
+
this._appendStringToken( token );
|
|
1954
2009
|
return "";
|
|
1955
2010
|
}
|
|
1956
2011
|
return token;
|
|
@@ -1967,7 +2022,7 @@ class CodeEditor {
|
|
|
1967
2022
|
token_classname = "cm-com";
|
|
1968
2023
|
|
|
1969
2024
|
else if( this._buildingString != undefined )
|
|
1970
|
-
discardToken = this.
|
|
2025
|
+
discardToken = this._appendStringToken( token );
|
|
1971
2026
|
|
|
1972
2027
|
else if( this._mustHightlightWord( token, this.keywords ) )
|
|
1973
2028
|
token_classname = "cm-kwd";
|
|
@@ -1981,28 +2036,28 @@ class CodeEditor {
|
|
|
1981
2036
|
else if( this._mustHightlightWord( token, this.symbols ) )
|
|
1982
2037
|
token_classname = "cm-sym";
|
|
1983
2038
|
|
|
1984
|
-
else if( token.substr(0,
|
|
2039
|
+
else if( token.substr( 0, singleLineCommentToken.length ) == singleLineCommentToken )
|
|
1985
2040
|
token_classname = "cm-com";
|
|
1986
2041
|
|
|
1987
|
-
else if( usesBlockComments && token.substr(0, 2) == '/*' )
|
|
2042
|
+
else if( usesBlockComments && token.substr( 0, 2 ) == '/*' )
|
|
1988
2043
|
token_classname = "cm-com";
|
|
1989
2044
|
|
|
1990
|
-
else if( usesBlockComments && token.substr(token.length - 2) == '*/' )
|
|
2045
|
+
else if( usesBlockComments && token.substr( token.length - 2 ) == '*/' )
|
|
1991
2046
|
token_classname = "cm-com";
|
|
1992
2047
|
|
|
1993
|
-
else if( this.isNumber(token) || this.isNumber( token.replace(/[px]|[em]|%/g,'') ) )
|
|
2048
|
+
else if( this.isNumber( token ) || this.isNumber( token.replace(/[px]|[em]|%/g,'') ) )
|
|
1994
2049
|
token_classname = "cm-dec";
|
|
1995
2050
|
|
|
1996
|
-
else if( this.
|
|
2051
|
+
else if( this._isCSSClass( token, prev, next ) )
|
|
1997
2052
|
token_classname = "cm-kwd";
|
|
1998
2053
|
|
|
1999
|
-
else if ( this.
|
|
2054
|
+
else if ( this._isType( token, prev, next ) )
|
|
2000
2055
|
token_classname = "cm-typ";
|
|
2001
2056
|
|
|
2002
|
-
else if ( highlight == 'batch' && (token == '@' || prev == ':' || prev == '@') )
|
|
2057
|
+
else if ( highlight == 'batch' && ( token == '@' || prev == ':' || prev == '@' ) )
|
|
2003
2058
|
token_classname = "cm-kwd";
|
|
2004
2059
|
|
|
2005
|
-
else if ( highlight == 'cpp' && token.includes('#') ) // C++ preprocessor
|
|
2060
|
+
else if ( highlight == 'cpp' && token.includes( '#' ) ) // C++ preprocessor
|
|
2006
2061
|
token_classname = "cm-ppc";
|
|
2007
2062
|
|
|
2008
2063
|
else if ( highlight == 'cpp' && prev == '<' && (next == '>' || next == '*') ) // Defining template type in C++
|
|
@@ -2024,7 +2079,7 @@ class CodeEditor {
|
|
|
2024
2079
|
// We finished constructing a string
|
|
2025
2080
|
if( this._buildingString && ( this._stringEnded || isLastToken ) )
|
|
2026
2081
|
{
|
|
2027
|
-
token = this.
|
|
2082
|
+
token = this._getCurrentString();
|
|
2028
2083
|
token_classname = "cm-str";
|
|
2029
2084
|
discardToken = false;
|
|
2030
2085
|
}
|
|
@@ -2035,6 +2090,9 @@ class CodeEditor {
|
|
|
2035
2090
|
if( discardToken )
|
|
2036
2091
|
return "";
|
|
2037
2092
|
|
|
2093
|
+
token = token.replace( "<", "<" );
|
|
2094
|
+
token = token.replace( ">", ">" );
|
|
2095
|
+
|
|
2038
2096
|
// No highlighting, no need to put it inside another span..
|
|
2039
2097
|
if( !token_classname.length )
|
|
2040
2098
|
return token;
|
|
@@ -2043,7 +2101,7 @@ class CodeEditor {
|
|
|
2043
2101
|
}
|
|
2044
2102
|
}
|
|
2045
2103
|
|
|
2046
|
-
|
|
2104
|
+
_appendStringToken( token ) {
|
|
2047
2105
|
|
|
2048
2106
|
if( !this._pendingString )
|
|
2049
2107
|
this._pendingString = "";
|
|
@@ -2053,14 +2111,14 @@ class CodeEditor {
|
|
|
2053
2111
|
return true;
|
|
2054
2112
|
}
|
|
2055
2113
|
|
|
2056
|
-
|
|
2114
|
+
_getCurrentString() {
|
|
2057
2115
|
|
|
2058
2116
|
const chars = this._pendingString;
|
|
2059
2117
|
delete this._pendingString;
|
|
2060
2118
|
return chars;
|
|
2061
2119
|
}
|
|
2062
2120
|
|
|
2063
|
-
|
|
2121
|
+
_isCSSClass( token, prev, next ) {
|
|
2064
2122
|
return this.highlight == 'CSS' && prev == '.';
|
|
2065
2123
|
}
|
|
2066
2124
|
|
|
@@ -2077,7 +2135,7 @@ class CodeEditor {
|
|
|
2077
2135
|
return token.length && token != ' ' && !Number.isNaN(+token);
|
|
2078
2136
|
}
|
|
2079
2137
|
|
|
2080
|
-
|
|
2138
|
+
_isType( token, prev, next ) {
|
|
2081
2139
|
|
|
2082
2140
|
// Common case
|
|
2083
2141
|
if( this._mustHightlightWord( token, this.types ) )
|
|
@@ -2101,7 +2159,7 @@ class CodeEditor {
|
|
|
2101
2159
|
}
|
|
2102
2160
|
}
|
|
2103
2161
|
|
|
2104
|
-
|
|
2162
|
+
_encloseSelectedWordWithKey( key, lidx, cursor ) {
|
|
2105
2163
|
|
|
2106
2164
|
if( !this.selection || (this.selection.fromY != this.selection.toY) )
|
|
2107
2165
|
return false;
|
|
@@ -2213,12 +2271,13 @@ class CodeEditor {
|
|
|
2213
2271
|
const post = code.slice( index + num_chars );
|
|
2214
2272
|
|
|
2215
2273
|
this.code.lines = ( pre + post ).split( separator );
|
|
2216
|
-
|
|
2217
|
-
|
|
2274
|
+
|
|
2218
2275
|
this.cursorToLine( cursor, this.selection.fromY, true );
|
|
2219
2276
|
this.cursorToPosition( cursor, this.selection.fromX );
|
|
2220
2277
|
|
|
2221
2278
|
this.endSelection();
|
|
2279
|
+
|
|
2280
|
+
this.processLines();
|
|
2222
2281
|
this._refreshCodeInfo( cursor.line, cursor.position );
|
|
2223
2282
|
}
|
|
2224
2283
|
|
|
@@ -2236,14 +2295,15 @@ class CodeEditor {
|
|
|
2236
2295
|
cursor._left += this.charWidth;
|
|
2237
2296
|
cursor.style.left = "calc( " + cursor._left + "px + " + this.xPadding + " )";
|
|
2238
2297
|
cursor.position++;
|
|
2298
|
+
|
|
2239
2299
|
this.restartBlink();
|
|
2240
2300
|
this._refreshCodeInfo( cursor.line, cursor.position );
|
|
2241
2301
|
|
|
2242
2302
|
// Add horizontal scroll
|
|
2243
2303
|
|
|
2244
2304
|
doAsync(() => {
|
|
2245
|
-
var
|
|
2246
|
-
if( cursor.position >=
|
|
2305
|
+
var viewportSizeX = ( this.codeScroller.clientWidth + this.getScrollLeft() ) - 48; // Gutter offset
|
|
2306
|
+
if( (cursor.position * this.charWidth) >= viewportSizeX )
|
|
2247
2307
|
this.setScrollLeft( this.getScrollLeft() + this.charWidth );
|
|
2248
2308
|
});
|
|
2249
2309
|
}
|
|
@@ -2253,18 +2313,18 @@ class CodeEditor {
|
|
|
2253
2313
|
if(!key) return;
|
|
2254
2314
|
cursor = cursor ?? this.cursors.children[ 0 ];
|
|
2255
2315
|
cursor._left -= this.charWidth;
|
|
2256
|
-
cursor._left = Math.max(cursor._left, 0);
|
|
2316
|
+
cursor._left = Math.max( cursor._left, 0 );
|
|
2257
2317
|
cursor.style.left = "calc( " + cursor._left + "px + " + this.xPadding + " )";
|
|
2258
2318
|
cursor.position--;
|
|
2259
|
-
cursor.position = Math.max(cursor.position, 0);
|
|
2319
|
+
cursor.position = Math.max( cursor.position, 0 );
|
|
2260
2320
|
this.restartBlink();
|
|
2261
2321
|
this._refreshCodeInfo( cursor.line, cursor.position );
|
|
2262
2322
|
|
|
2263
2323
|
// Add horizontal scroll
|
|
2264
2324
|
|
|
2265
2325
|
doAsync(() => {
|
|
2266
|
-
var
|
|
2267
|
-
if( (cursor.position - 1) <
|
|
2326
|
+
var viewportSizeX = this.getScrollLeft(); // Gutter offset
|
|
2327
|
+
if( ( ( cursor.position - 1 ) * this.charWidth ) < viewportSizeX )
|
|
2268
2328
|
this.setScrollLeft( this.getScrollLeft() - this.charWidth );
|
|
2269
2329
|
});
|
|
2270
2330
|
}
|
|
@@ -2283,7 +2343,7 @@ class CodeEditor {
|
|
|
2283
2343
|
this._refreshCodeInfo( cursor.line, cursor.position );
|
|
2284
2344
|
|
|
2285
2345
|
doAsync(() => {
|
|
2286
|
-
var first_line = (this.getScrollTop() / this.lineHeight)|0;
|
|
2346
|
+
var first_line = ( this.getScrollTop() / this.lineHeight )|0;
|
|
2287
2347
|
if( (cursor.line - 1) < first_line )
|
|
2288
2348
|
this.setScrollTop( this.getScrollTop() - this.lineHeight );
|
|
2289
2349
|
});
|
|
@@ -2302,7 +2362,7 @@ class CodeEditor {
|
|
|
2302
2362
|
this._refreshCodeInfo( cursor.line, cursor.position );
|
|
2303
2363
|
|
|
2304
2364
|
doAsync(() => {
|
|
2305
|
-
var last_line = ((this.codeScroller.offsetHeight + this.getScrollTop()) / this.lineHeight)|0;
|
|
2365
|
+
var last_line = ( ( this.codeScroller.offsetHeight + this.getScrollTop() ) / this.lineHeight )|0;
|
|
2306
2366
|
if( cursor.line >= last_line )
|
|
2307
2367
|
this.setScrollTop( this.getScrollTop() + this.lineHeight );
|
|
2308
2368
|
});
|
|
@@ -2344,7 +2404,7 @@ class CodeEditor {
|
|
|
2344
2404
|
|
|
2345
2405
|
cursor = cursor ?? this.cursors.children[ 0 ];
|
|
2346
2406
|
cursor.line = state.line ?? 0;
|
|
2347
|
-
cursor.position = state.
|
|
2407
|
+
cursor.position = state.position ?? 0;
|
|
2348
2408
|
|
|
2349
2409
|
cursor._left = state.left ?? 0;
|
|
2350
2410
|
cursor.style.left = "calc(" + (cursor._left - this.getScrollLeft()) + "px + " + this.xPadding + ")";
|
|
@@ -2414,9 +2474,32 @@ class CodeEditor {
|
|
|
2414
2474
|
this.setScrollBarValue( 'vertical' );
|
|
2415
2475
|
}
|
|
2416
2476
|
|
|
2417
|
-
|
|
2477
|
+
resize( pMaxLength ) {
|
|
2478
|
+
|
|
2479
|
+
setTimeout( () => {
|
|
2480
|
+
|
|
2481
|
+
// Update max viewport
|
|
2482
|
+
const maxLineLength = pMaxLength ?? this.getMaxLineLength();
|
|
2483
|
+
const scrollWidth = maxLineLength * this.charWidth;
|
|
2484
|
+
const scrollHeight = this.code.lines.length * this.lineHeight;
|
|
2485
|
+
|
|
2486
|
+
this._lastMaxLineLength = maxLineLength;
|
|
2487
|
+
|
|
2488
|
+
this.codeSizer.style.minWidth = scrollWidth + "px";
|
|
2489
|
+
this.codeSizer.style.minHeight = scrollHeight + "px";
|
|
2490
|
+
|
|
2491
|
+
this.resizeScrollBars();
|
|
2492
|
+
|
|
2493
|
+
// console.warn("Resize editor viewport");
|
|
2494
|
+
|
|
2495
|
+
}, 10 );
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
resizeScrollBars() {
|
|
2499
|
+
|
|
2500
|
+
const totalLinesInViewport = ((this.codeScroller.offsetHeight - 36) / this.lineHeight)|0;
|
|
2418
2501
|
|
|
2419
|
-
if(
|
|
2502
|
+
if( totalLinesInViewport > this.code.lines.length )
|
|
2420
2503
|
{
|
|
2421
2504
|
this.codeScroller.classList.remove( 'with-vscrollbar' );
|
|
2422
2505
|
this.vScrollbar.root.classList.add( 'scrollbar-unused' );
|
|
@@ -2425,13 +2508,12 @@ class CodeEditor {
|
|
|
2425
2508
|
{
|
|
2426
2509
|
this.codeScroller.classList.add( 'with-vscrollbar' );
|
|
2427
2510
|
this.vScrollbar.root.classList.remove( 'scrollbar-unused' );
|
|
2428
|
-
this.vScrollbar.thumb.size = (
|
|
2511
|
+
this.vScrollbar.thumb.size = (totalLinesInViewport / this.code.lines.length);
|
|
2429
2512
|
this.vScrollbar.thumb.style.height = (this.vScrollbar.thumb.size * 100.0) + "%";
|
|
2430
2513
|
}
|
|
2431
2514
|
|
|
2432
2515
|
const numViewportChars = Math.floor( this.codeScroller.clientWidth / this.charWidth );
|
|
2433
|
-
const
|
|
2434
|
-
const maxLineLength = Math.max(...line_lengths);
|
|
2516
|
+
const maxLineLength = this._lastMaxLineLength;
|
|
2435
2517
|
|
|
2436
2518
|
if( numViewportChars > maxLineLength )
|
|
2437
2519
|
{
|
|
@@ -2625,11 +2707,11 @@ class CodeEditor {
|
|
|
2625
2707
|
|
|
2626
2708
|
// Add language special keys...
|
|
2627
2709
|
suggestions = suggestions.concat(
|
|
2628
|
-
Object.keys( this.builtin[ this.highlight ]
|
|
2629
|
-
Object.keys( this.keywords[ this.highlight ]
|
|
2630
|
-
Object.keys( this.statementsAndDeclarations[ this.highlight ]
|
|
2631
|
-
Object.keys( this.types[ this.highlight ]
|
|
2632
|
-
Object.keys( this.utils[ this.highlight ]
|
|
2710
|
+
Object.keys( this.builtin[ this.highlight ] ?? {} ),
|
|
2711
|
+
Object.keys( this.keywords[ this.highlight ] ?? {} ),
|
|
2712
|
+
Object.keys( this.statementsAndDeclarations[ this.highlight ] ?? {} ),
|
|
2713
|
+
Object.keys( this.types[ this.highlight ] ?? {} ),
|
|
2714
|
+
Object.keys( this.utils[ this.highlight ] ?? {} )
|
|
2633
2715
|
);
|
|
2634
2716
|
|
|
2635
2717
|
// Add words in current tab plus remove current word
|
package/build/lexgui.css
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
--global-color-primary: #232323;
|
|
10
10
|
--global-color-secondary: #343434;
|
|
11
11
|
--global-color-terciary: #444;
|
|
12
|
-
--global-branch-darker: #
|
|
12
|
+
--global-branch-darker: #252525;
|
|
13
13
|
--branch-title-background: #2e3338;
|
|
14
14
|
--branch-title-inactive-background: #42484e;
|
|
15
15
|
--global-button-color: #4e4e4e;
|
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
--transition-time: 1000;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/* Some global colors */
|
|
25
|
+
.orange { color: orange }
|
|
26
|
+
|
|
24
27
|
::-webkit-scrollbar {
|
|
25
28
|
height: 3px;
|
|
26
29
|
width: 4px;
|
|
@@ -2002,6 +2005,7 @@ meter::-webkit-meter-even-less-good-value {
|
|
|
2002
2005
|
-ms-user-select: none; /* IE 10+ */
|
|
2003
2006
|
user-select: none; /* Standard syntax */
|
|
2004
2007
|
transition: 0.2s;
|
|
2008
|
+
line-height: 16px;
|
|
2005
2009
|
}
|
|
2006
2010
|
|
|
2007
2011
|
.lexareatabs.row {
|
|
@@ -2692,6 +2696,19 @@ ul.lexassetscontent {
|
|
|
2692
2696
|
padding: 0px;
|
|
2693
2697
|
}
|
|
2694
2698
|
|
|
2699
|
+
.codebasearea .lexareatab i {
|
|
2700
|
+
font-size: 10px;
|
|
2701
|
+
margin-right: 4px;
|
|
2702
|
+
vertical-align: middle;
|
|
2703
|
+
}
|
|
2704
|
+
|
|
2705
|
+
.codebasearea .lexareatab img {
|
|
2706
|
+
width: 12px;
|
|
2707
|
+
height: 12px;
|
|
2708
|
+
margin-right: 4px;
|
|
2709
|
+
vertical-align: middle;
|
|
2710
|
+
}
|
|
2711
|
+
|
|
2695
2712
|
.lexcodeeditor ::-webkit-scrollbar {
|
|
2696
2713
|
width: 6px;
|
|
2697
2714
|
height: 6px;
|
|
@@ -2751,10 +2768,6 @@ ul.lexassetscontent {
|
|
|
2751
2768
|
width: calc( 100% - 10px ) !important;
|
|
2752
2769
|
}
|
|
2753
2770
|
|
|
2754
|
-
/* .lexcodeeditor .codetabsarea.with-hscrollbar {
|
|
2755
|
-
height: calc( 100% - 72px ) !important;
|
|
2756
|
-
} */
|
|
2757
|
-
|
|
2758
2771
|
.lexcodeeditor .codetabsarea.dragging {
|
|
2759
2772
|
background-color: var(--global-color-secondary);
|
|
2760
2773
|
}
|
|
@@ -2786,7 +2799,7 @@ ul.lexassetscontent {
|
|
|
2786
2799
|
font-family: 'Inconsolata', monospace;
|
|
2787
2800
|
font-size: inherit;
|
|
2788
2801
|
font-weight: bold;
|
|
2789
|
-
margin:
|
|
2802
|
+
margin: 0px 0px;
|
|
2790
2803
|
white-space: pre;
|
|
2791
2804
|
word-wrap: normal;
|
|
2792
2805
|
line-height: inherit;
|
|
@@ -2794,7 +2807,7 @@ ul.lexassetscontent {
|
|
|
2794
2807
|
position: relative;
|
|
2795
2808
|
overflow: visible;
|
|
2796
2809
|
-webkit-tap-highlight-color: transparent;
|
|
2797
|
-
height:
|
|
2810
|
+
height: 20px;
|
|
2798
2811
|
pointer-events: none;
|
|
2799
2812
|
}
|
|
2800
2813
|
|
|
@@ -2819,9 +2832,9 @@ ul.lexassetscontent {
|
|
|
2819
2832
|
pre .line-gutter {
|
|
2820
2833
|
color: #888;
|
|
2821
2834
|
width: 48px;
|
|
2822
|
-
height:
|
|
2835
|
+
height: 20px;
|
|
2823
2836
|
font-size: 14px;
|
|
2824
|
-
line-height:
|
|
2837
|
+
line-height: 20px;
|
|
2825
2838
|
text-align: center;
|
|
2826
2839
|
-webkit-user-select: none; /* Safari 3.1+ */
|
|
2827
2840
|
-moz-user-select: none; /* Firefox 2+ */
|
|
@@ -2843,7 +2856,6 @@ pre .line-gutter {
|
|
|
2843
2856
|
margin: 0;
|
|
2844
2857
|
padding: 0;
|
|
2845
2858
|
position: relative;
|
|
2846
|
-
z-index: 3;
|
|
2847
2859
|
pointer-events: none;
|
|
2848
2860
|
}
|
|
2849
2861
|
|
|
@@ -2867,7 +2879,7 @@ pre .line-gutter {
|
|
|
2867
2879
|
z-index: 0 !important;
|
|
2868
2880
|
left: 0px;
|
|
2869
2881
|
top: 0px;
|
|
2870
|
-
height:
|
|
2882
|
+
height: 20px;
|
|
2871
2883
|
}
|
|
2872
2884
|
|
|
2873
2885
|
.lexcodescrollbar {
|
|
@@ -2881,6 +2893,7 @@ pre .line-gutter {
|
|
|
2881
2893
|
margin-top: 26px;
|
|
2882
2894
|
z-index: 1 !important;
|
|
2883
2895
|
right: 0px;
|
|
2896
|
+
pointer-events: none;
|
|
2884
2897
|
}
|
|
2885
2898
|
|
|
2886
2899
|
.lexcodescrollbar.horizontal {
|
|
@@ -2906,6 +2919,7 @@ pre .line-gutter {
|
|
|
2906
2919
|
width: 10px;
|
|
2907
2920
|
height: 10px;
|
|
2908
2921
|
transition: linear 0.1s background-color;
|
|
2922
|
+
pointer-events: all;
|
|
2909
2923
|
}
|
|
2910
2924
|
|
|
2911
2925
|
.lexcodescrollbar div:hover { /* thumb */
|
|
@@ -2961,7 +2975,7 @@ pre .line-gutter {
|
|
|
2961
2975
|
margin: 0;
|
|
2962
2976
|
pointer-events: unset;
|
|
2963
2977
|
cursor: default;
|
|
2964
|
-
height:
|
|
2978
|
+
height: 22px;
|
|
2965
2979
|
}
|
|
2966
2980
|
|
|
2967
2981
|
.lexcodeeditor .autocomplete pre a {
|
|
@@ -3073,9 +3087,10 @@ pre .line-gutter {
|
|
|
3073
3087
|
.cm-sym.batch { color: #dfd85e; } /* symbol */
|
|
3074
3088
|
.cm-mtd.batch { color: inherit } /* method */
|
|
3075
3089
|
|
|
3076
|
-
|
|
3077
|
-
.cm-
|
|
3078
|
-
.cm-bln.
|
|
3090
|
+
.cm-str.html { color: #ca7d59; } /* string */
|
|
3091
|
+
.cm-kwd.html { color: #2194ce; } /* keyword */
|
|
3092
|
+
.cm-bln.html { color: #b4d7ec; } /* builtin */
|
|
3093
|
+
.cm-sym.html { color: #929292; } /* symbol */
|
|
3079
3094
|
|
|
3080
3095
|
|
|
3081
3096
|
/* Node Graph */
|
package/build/lexgui.js
CHANGED
|
@@ -12,7 +12,7 @@ console.warn( 'Script "build/lexgui.js" is depracated and will be removed soon.
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
var LX = global.LX = {
|
|
15
|
-
version: "0.1.
|
|
15
|
+
version: "0.1.18",
|
|
16
16
|
ready: false,
|
|
17
17
|
components: [], // specific pre-build components
|
|
18
18
|
signals: {} // events and triggers
|
|
@@ -1541,11 +1541,23 @@ console.warn( 'Script "build/lexgui.js" is depracated and will be removed soon.
|
|
|
1541
1541
|
contentEl.style.display = isSelected ? "block" : "none";
|
|
1542
1542
|
contentEl.classList.add("lextabcontent");
|
|
1543
1543
|
|
|
1544
|
+
// Process icon
|
|
1545
|
+
if( options.icon )
|
|
1546
|
+
{
|
|
1547
|
+
if( options.icon.includes( 'fa-' ) ) // It's fontawesome icon...
|
|
1548
|
+
options.icon = "<i class='" + options.icon + "'></i>";
|
|
1549
|
+
else // an image..
|
|
1550
|
+
{
|
|
1551
|
+
const rootPath = "https://raw.githubusercontent.com/jxarco/lexgui.js/master/";
|
|
1552
|
+
options.icon = "<img src='" + ( rootPath + options.icon ) + "'>";
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1544
1556
|
// Create tab
|
|
1545
1557
|
let tabEl = document.createElement('span');
|
|
1546
1558
|
tabEl.dataset["name"] = name;
|
|
1547
1559
|
tabEl.className = "lexareatab" + (isSelected ? " selected" : "");
|
|
1548
|
-
tabEl.innerHTML = name;
|
|
1560
|
+
tabEl.innerHTML = (options.icon ?? "") + name;
|
|
1549
1561
|
tabEl.id = name.replace(/\s/g, '') + Tabs.TAB_ID++;
|
|
1550
1562
|
tabEl.title = options.title;
|
|
1551
1563
|
tabEl.selected = isSelected ?? false;
|
package/build/lexgui.module.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
var LX = {
|
|
11
|
-
version: "0.1.
|
|
11
|
+
version: "0.1.18",
|
|
12
12
|
ready: false,
|
|
13
13
|
components: [], // specific pre-build components
|
|
14
14
|
signals: {} // events and triggers
|
|
@@ -1537,11 +1537,23 @@ class Tabs {
|
|
|
1537
1537
|
contentEl.style.display = isSelected ? "block" : "none";
|
|
1538
1538
|
contentEl.classList.add("lextabcontent");
|
|
1539
1539
|
|
|
1540
|
+
// Process icon
|
|
1541
|
+
if( options.icon )
|
|
1542
|
+
{
|
|
1543
|
+
if( options.icon.includes( 'fa-' ) ) // It's fontawesome icon...
|
|
1544
|
+
options.icon = "<i class='" + options.icon + "'></i>";
|
|
1545
|
+
else // an image..
|
|
1546
|
+
{
|
|
1547
|
+
const rootPath = "https://raw.githubusercontent.com/jxarco/lexgui.js/master/";
|
|
1548
|
+
options.icon = "<img src='" + ( rootPath + options.icon ) + "'>";
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1540
1552
|
// Create tab
|
|
1541
1553
|
let tabEl = document.createElement('span');
|
|
1542
1554
|
tabEl.dataset["name"] = name;
|
|
1543
1555
|
tabEl.className = "lexareatab" + (isSelected ? " selected" : "");
|
|
1544
|
-
tabEl.innerHTML = name;
|
|
1556
|
+
tabEl.innerHTML = (options.icon ?? "") + name;
|
|
1545
1557
|
tabEl.id = name.replace(/\s/g, '') + Tabs.TAB_ID++;
|
|
1546
1558
|
tabEl.title = options.title;
|
|
1547
1559
|
tabEl.selected = isSelected ?? false;
|
package/demo.js
CHANGED
|
@@ -487,7 +487,7 @@ function fillPanel( panel ) {
|
|
|
487
487
|
panel.sameLine(2);
|
|
488
488
|
panel.addFile("Img1", data => { console.log(data) }, {} );
|
|
489
489
|
panel.addFile("Img2", data => { console.log(data) }, {} );
|
|
490
|
-
panel.addDropdown("Best Engine", ["Godot",
|
|
490
|
+
panel.addDropdown("Best Engine", ["Godot", "Unity", "Unreal Engine"], "Unity", (value, event) => {
|
|
491
491
|
console.log(value);
|
|
492
492
|
});
|
|
493
493
|
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
let area = LX.init();
|
|
25
25
|
|
|
26
26
|
// split main area
|
|
27
|
-
var [leftArea, rightArea] = area.split({ sizes:["
|
|
27
|
+
var [leftArea, rightArea] = area.split({ sizes:["55%","45%"] });
|
|
28
28
|
|
|
29
29
|
// add canvas to leftArea
|
|
30
30
|
var canvas = document.createElement('canvas');
|
|
@@ -47,20 +47,8 @@
|
|
|
47
47
|
// disable_edition: true
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
-
// editor.loadFile( "../data/test.json" );
|
|
51
|
-
// editor.loadFile( "../data/style.css" );
|
|
52
|
-
// editor.loadFile( "../data/script.js" );
|
|
53
|
-
// editor.loadFile( "../data/engine.cpp" );
|
|
54
50
|
editor.loadFile( "../demo.js" );
|
|
55
51
|
|
|
56
|
-
// var button = document.createElement('button');
|
|
57
|
-
// button.value = "CLICK ME";
|
|
58
|
-
// button.style.width = "100px";
|
|
59
|
-
// button.style.height = "100px";
|
|
60
|
-
// button.style.position = "absolute";
|
|
61
|
-
// button.onclick = editor.processLines.bind(editor);
|
|
62
|
-
// document.body.appendChild( button );
|
|
63
|
-
|
|
64
52
|
var ctx = canvas.getContext("2d");
|
|
65
53
|
ctx.fillStyle = "#b7a9b1";
|
|
66
54
|
ctx.font = "48px Monospace";
|