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.
@@ -245,12 +245,12 @@ class CodeEditor {
245
245
  // Scroll stuff
246
246
  {
247
247
  this.codeScroller = this.tabs.area.root;
248
- this.viewportRangeStart = 0;
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', (e) => {
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
- const scrollDownBoundary = (this.viewportRangeStart + this.lineScrollMargin.y) * this.lineHeight;
269
-
270
- if( scrollTop > scrollDownBoundary )
268
+ if( this.visibleLinesViewport.y < (this.code.lines.length - 1) )
271
269
  {
272
- this.code.style.top = (scrollDownBoundary - this.lineScrollMargin.x * this.lineHeight) + "px";
273
- this.processLines( CodeEditor.UPDATE_VISIBLE_LINES );
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 = (this.viewportRangeStart + this.lineScrollMargin.x) * this.lineHeight;
280
- // console.log(scrollTop, scrollUpBoundary)
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', (e) => {
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.startSelection( cursor );
559
+ if( !this.selection )
560
+ this.startSelection( cursor );
563
561
  var string = this.code.lines[ ln ].substring( idx, lastX );
564
- this.selection.selectInline( idx, cursor.line, this.measureString( string ) );
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.selectInline(cursor.position, cursor.line, this.measureString(string));
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
- this.tabs.add(name, code, { 'selected': selected, 'fixed': (name === '+') , 'title': code.title, 'onSelect': (e, tabname) => {
1134
+ const ext = LX.getExtension( name );
1104
1135
 
1105
- if(tabname == '+')
1106
- {
1107
- this._onNewTab( e );
1108
- return;
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
- var cursor = cursor ?? this.cursors.children[ 0 ];
1112
- this.saveCursor( cursor, this.code.cursorState );
1113
- this.code = this.openedTabs[ tabname ];
1114
- this.restoreCursor( cursor, this.code.cursorState );
1115
- this.endSelection();
1116
- this._changeLanguageFromExtension( LX.getExtension( tabname ) );
1117
- this._refreshCodeInfo( cursor.line, cursor.position );
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
- // Make sure that the line selection is generated...
1318
- let domEl = this.selections.childNodes[sId];
1319
- if(!domEl)
1360
+ if( isVisible )
1320
1361
  {
1321
- domEl = document.createElement( 'div' );
1322
- domEl.className = "lexcodeselection";
1323
- this.selections.appendChild( domEl );
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
- // Make sure that the line selection is generated...
1360
- let domEl = this.selections.childNodes[sId];
1361
- if(!domEl)
1411
+ if( isVisible )
1362
1412
  {
1363
- domEl = document.createElement( 'div' );
1364
- domEl.className = "lexcodeselection";
1365
- this.selections.appendChild( domEl );
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
- const d = new Date();
1497
- const current = d.getTime();
1498
-
1499
- if( !skip_undo )
1554
+ if( !skip_undo && this.code.lines.length )
1500
1555
  {
1501
- if( !this._lastTime ) {
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.encloseSelectedWordWithKey(key, lidx, cursor) )
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.viewportRangeStart - this.lineScrollMargin.x, 0 );
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 = "", code_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 firstLineInViewport = mode & CodeEditor.UPDATE_VISIBLE_LINES ?
1690
- ( (lastScrollTop / this.lineHeight)|0 ) : this.viewportRangeStart;
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.viewportRangeStart = firstLineInViewport;
1693
-
1694
- const viewportRange = new LX.vec2(
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 - viewportRange.y, 0 );
1702
- if( diff < ( totalLinesInViewport + this.lineScrollMargin.y ) )
1703
- viewportRange.y += diff;
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
- for( let i = viewportRange.x; i < viewportRange.y; ++i )
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:", viewportRange);
1715
- console.log( "Num lines processed:", (viewportRange.y - viewportRange.x), performance.now() - start );
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
- setTimeout( () => {
1721
-
1722
- // Update max viewport
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
- this.resizeScrollBars( totalLinesInViewport );
1782
+ // Clear tmp vars
1783
+ delete this._buildingString;
1784
+ delete this._pendingString;
1730
1785
 
1731
- }, 10 );
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.evaluateToken( token, prev, next, (i == tokensToEvaluate.length - 1) );
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(/(::|[\[\](){}<>.,;:*"'%@ ])/g);
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
- evaluateToken( token, prev, next, isLastToken ) {
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.appendStringToken( token );
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.appendStringToken( token );
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, 2) == singleLineCommentToken )
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.isCSSClass(token, prev, next) )
2051
+ else if( this._isCSSClass( token, prev, next ) )
1997
2052
  token_classname = "cm-kwd";
1998
2053
 
1999
- else if ( this.isType(token, prev, next) )
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.getCurrentString();
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( "<", "&lt;" );
2094
+ token = token.replace( ">", "&gt;" );
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
- appendStringToken( token ) {
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
- getCurrentString() {
2114
+ _getCurrentString() {
2057
2115
 
2058
2116
  const chars = this._pendingString;
2059
2117
  delete this._pendingString;
2060
2118
  return chars;
2061
2119
  }
2062
2120
 
2063
- isCSSClass( token, prev, next ) {
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
- isType( token, prev, next ) {
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
- encloseSelectedWordWithKey( key, lidx, cursor ) {
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
- this.processLines();
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 last_char = ((this.codeScroller.clientWidth + this.getScrollLeft()) / this.charWidth)|0;
2246
- if( cursor.position >= last_char )
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 first_char = (this.getScrollLeft() / this.charWidth)|0;
2267
- if( (cursor.position - 1) < first_char )
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.charPos ?? 0;
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
- resizeScrollBars( numViewportLines ) {
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( numViewportLines > this.code.lines.length )
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 = (numViewportLines / this.code.lines.length);
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 line_lengths = this.code.lines.map( value => value.length );
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: #28282a;
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: 4px 0px;
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: 16px;
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: 16px;
2835
+ height: 20px;
2823
2836
  font-size: 14px;
2824
- line-height: 22px;
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: 18px;
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: 18px;
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
- /* plain color */
3077
- .cm-str.plaintext, .cm-kwd.plaintext, .cm-com.plaintext, .cm-typ.plaintext, .cm-std.plaintext,
3078
- .cm-bln.plaintext, .cm-dec.plaintext, .cm-sym.plaintext, .cm-mtd.plaintext { color: inherit; }
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.17",
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;
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  var LX = {
11
- version: "0.1.17",
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","Godot","Godot","Godot","Godot","Godot","Godot","Godot","Godot","Godot","Godot","Godot","Godot","Godot","Godot","Godot","Godot","Godot","Godot","Godot","Godot","Godot","Godot","Godot","Godot","Godot","Godot","Godot","Godot","Godot","Godot","Godot","Godot","Godot","Godot","Godot","Godot","Godot", "Unity", "Unreal Engine"], "Unity", (value, event) => {
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:["40%","60%"] });
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";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lexgui",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
4
4
  "description": "JS library to create web graphical user interfaces",
5
5
  "type": "module",
6
6
  "main": "./build/lexgui.js",