lexgui 0.1.41 → 0.1.43

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.
@@ -88,7 +88,7 @@ class CodeSelection {
88
88
  {
89
89
  swapElements( this, 'fromX', 'toX' );
90
90
  swapElements( this, 'fromY', 'toY' );
91
- }
91
+ }
92
92
  else if( this.sameLine() && this.fromX > this.toX )
93
93
  {
94
94
  swapElements( this, 'fromX', 'toX' );
@@ -96,7 +96,7 @@ class CodeSelection {
96
96
  }
97
97
 
98
98
  selectInline( cursor, x, y, width, isSearchResult ) {
99
-
99
+
100
100
  this.chars = width / this.editor.charWidth;
101
101
  this.fromX = x;
102
102
  this.toX = x + this.chars;
@@ -104,7 +104,7 @@ class CodeSelection {
104
104
 
105
105
  var domEl = document.createElement( 'div' );
106
106
  domEl.className = this.className;
107
-
107
+
108
108
  domEl._top = y * this.editor.lineHeight;
109
109
  domEl.style.top = domEl._top + "px";
110
110
  domEl._left = x * this.editor.charWidth;
@@ -164,9 +164,9 @@ class ScrollBar {
164
164
  this.root = document.createElement( 'div' );
165
165
  this.root.className = "lexcodescrollbar";
166
166
 
167
- if( type & ScrollBar.SCROLLBAR_VERTICAL )
167
+ if( type & ScrollBar.SCROLLBAR_VERTICAL )
168
168
  this.root.classList.add( 'vertical' );
169
- else if( type & ScrollBar.SCROLLBAR_HORIZONTAL )
169
+ else if( type & ScrollBar.SCROLLBAR_HORIZONTAL )
170
170
  this.root.classList.add( 'horizontal' );
171
171
 
172
172
  this.thumb = document.createElement( 'div' );
@@ -176,11 +176,11 @@ class ScrollBar {
176
176
  this.root.appendChild( this.thumb );
177
177
 
178
178
  this.thumb.addEventListener( "mousedown", inner_mousedown );
179
-
179
+
180
180
  this.lastPosition = new LX.vec2( 0, 0 );
181
181
 
182
182
  let that = this;
183
-
183
+
184
184
  function inner_mousedown( e )
185
185
  {
186
186
  var doc = editor.root.ownerDocument;
@@ -194,7 +194,7 @@ class ScrollBar {
194
194
  function inner_mousemove( e )
195
195
  {
196
196
  var dt = that.lastPosition.sub( new LX.vec2( e.x, e.y ) );
197
- if( that.type & ScrollBar.SCROLLBAR_VERTICAL )
197
+ if( that.type & ScrollBar.SCROLLBAR_VERTICAL )
198
198
  editor.updateVerticalScrollFromScrollBar( dt.y )
199
199
  else
200
200
  editor.updateHorizontalScrollFromScrollBar( dt.x )
@@ -239,7 +239,10 @@ class CodeEditor {
239
239
 
240
240
  /**
241
241
  * @param {*} options
242
- * skip_info, allow_add_scripts, name
242
+ * name:
243
+ * skipInfo:
244
+ * fileExplorer:
245
+ * allowAddScripts:
243
246
  */
244
247
 
245
248
  constructor( area, options = {} ) {
@@ -247,11 +250,11 @@ class CodeEditor {
247
250
  window.editor = this;
248
251
 
249
252
  CodeEditor.__instances.push( this );
250
-
253
+
251
254
  // File explorer
252
- if( options.file_explorer ?? false )
255
+ if( options.fileExplorer ?? false )
253
256
  {
254
- var [explorerArea, codeArea] = area.split({ sizes:["15%","85%"] });
257
+ var [ explorerArea, codeArea ] = area.split({ sizes:[ "15%","85%" ] });
255
258
  explorerArea.setLimitBox( 180, 20, 512 );
256
259
  this.explorerArea = explorerArea;
257
260
 
@@ -259,40 +262,42 @@ class CodeEditor {
259
262
 
260
263
  panel.addTitle( "EXPLORER" );
261
264
 
265
+ this._tabStorage = {};
266
+
262
267
  let sceneData = {
263
268
  'id': 'WORKSPACE',
264
269
  'skipVisibility': true,
265
270
  'children': []
266
271
  };
267
-
268
- this.explorer = panel.addTree( null, sceneData, {
272
+
273
+ this.explorer = panel.addTree( null, sceneData, {
269
274
  filter: false,
270
275
  rename: false,
271
276
  skip_default_icon: true,
272
- onevent: (event) => {
277
+ onevent: (event) => {
273
278
  switch(event.type) {
274
279
  // case LX.TreeEvent.NODE_SELECTED:
275
280
  // if( !this.tabs.tabDOMs[ event.node.id ] ) break;
276
281
  case LX.TreeEvent.NODE_DBLCLICKED:
277
282
  this.loadTab( event.node.id );
278
283
  break;
279
- case LX.TreeEvent.NODE_DELETED:
284
+ case LX.TreeEvent.NODE_DELETED:
280
285
  this.tabs.delete( event.node.id );
281
286
  delete this.loadedTabs[ event.node.id ];
282
287
  break;
283
- // case LX.TreeEvent.NODE_CONTEXTMENU:
288
+ // case LX.TreeEvent.NODE_CONTEXTMENU:
284
289
  // LX.addContextMenu( event.multiple ? "Selected Nodes" : event.node.id, event.value, m => {
285
- //
290
+ //
286
291
  // });
287
292
  // break;
288
- // case LX.TreeEvent.NODE_DRAGGED:
289
- // console.log(event.node.id + " is now child of " + event.value.id);
293
+ // case LX.TreeEvent.NODE_DRAGGED:
294
+ // console.log(event.node.id + " is now child of " + event.value.id);
290
295
  // break;
291
296
  }
292
297
  }
293
- });
298
+ });
294
299
 
295
- this.addExplorerItem = function( item )
300
+ this.addExplorerItem = function( item )
296
301
  {
297
302
  if( !this.explorer.data.children.find( (value, index) => value.id === item.id ) )
298
303
  this.explorer.data.children.push( item );
@@ -305,7 +310,7 @@ class CodeEditor {
305
310
  }
306
311
 
307
312
  this.base_area = area;
308
- this.area = new LX.Area( { className: "lexcodeeditor", height: "100%", no_append: true } );
313
+ this.area = new LX.Area( { className: "lexcodeeditor", height: "100%", skipAppend: true } );
309
314
 
310
315
  this.tabs = this.area.addTabs( { onclose: (name) => {
311
316
  delete this.openedTabs[ name ];
@@ -315,8 +320,9 @@ class CodeEditor {
315
320
  this.cursors.classList.remove( 'show' );
316
321
  }
317
322
  } } );
323
+
318
324
  this.tabs.root.addEventListener( 'dblclick', (e) => {
319
- if( options.allow_add_scripts ?? true ) {
325
+ if( options.allowAddScripts ?? true ) {
320
326
  e.preventDefault();
321
327
  this.addTab("unnamed.js", true);
322
328
  }
@@ -332,8 +338,8 @@ class CodeEditor {
332
338
  this.root.tabIndex = -1;
333
339
  area.attach( this.root );
334
340
 
335
- this.skipCodeInfo = options.skip_info ?? false;
336
- this.disableEdition = options.disable_edition ?? false;
341
+ this.skipCodeInfo = options.skipInfo ?? false;
342
+ this.disableEdition = options.disableEdition ?? false;
337
343
 
338
344
  if( !this.disableEdition )
339
345
  {
@@ -341,7 +347,7 @@ class CodeEditor {
341
347
  this.root.addEventListener( 'focus', this.processFocus.bind( this, true ) );
342
348
  this.root.addEventListener( 'focusout', this.processFocus.bind( this, false ) );
343
349
  }
344
-
350
+
345
351
  this.root.addEventListener( 'mousedown', this.processMouse.bind(this) );
346
352
  this.root.addEventListener( 'mouseup', this.processMouse.bind(this) );
347
353
  this.root.addEventListener( 'mousemove', this.processMouse.bind(this) );
@@ -397,8 +403,8 @@ class CodeEditor {
397
403
  if( this.visibleLinesViewport.y < (this.code.lines.length - 1) )
398
404
  {
399
405
  const totalLinesInViewport = ((this.codeScroller.offsetHeight - 36) / this.lineHeight)|0;
400
- const scrollDownBoundary =
401
- ( Math.max( this.visibleLinesViewport.y - totalLinesInViewport, 0 ) - 1 ) * this.lineHeight;
406
+ const scrollDownBoundary =
407
+ ( Math.max( this.visibleLinesViewport.y - totalLinesInViewport, 0 ) - 1 ) * this.lineHeight;
402
408
 
403
409
  if( scrollTop >= scrollDownBoundary )
404
410
  this.processLines( CodeEditor.UPDATE_VISIBLE_LINES );
@@ -462,7 +468,7 @@ class CodeEditor {
462
468
  {
463
469
  var box = document.createElement( 'div' );
464
470
  box.className = "searchbox";
465
-
471
+
466
472
  var searchPanel = new LX.Panel();
467
473
  box.appendChild( searchPanel.root );
468
474
 
@@ -485,7 +491,7 @@ class CodeEditor {
485
491
  {
486
492
  var box = document.createElement( 'div' );
487
493
  box.className = "searchbox gotoline";
488
-
494
+
489
495
  var searchPanel = new LX.Panel();
490
496
  box.appendChild( searchPanel.root );
491
497
 
@@ -507,7 +513,7 @@ class CodeEditor {
507
513
  {
508
514
  this.codeSizer = document.createElement( 'div' );
509
515
  this.codeSizer.className = "code-sizer";
510
-
516
+
511
517
  // Append all childs
512
518
  while( this.codeScroller.firstChild )
513
519
  this.codeSizer.appendChild( this.codeScroller.firstChild );
@@ -549,7 +555,7 @@ class CodeEditor {
549
555
  };
550
556
 
551
557
  this.stringKeys = { // adding @ because some words are always true in (e.g. constructor..)
552
- "@\"": "\"",
558
+ "@\"": "\"",
553
559
  "@'": "'"
554
560
  };
555
561
 
@@ -559,8 +565,10 @@ class CodeEditor {
559
565
  this.languages = {
560
566
  'Plain Text': { ext: 'txt', blockComments: false, singleLineComments: false },
561
567
  'JavaScript': { ext: 'js' },
562
- 'C++': { ext: 'cpp' },
568
+ 'C': { ext: [ 'c', 'h' ] },
569
+ 'C++': { ext: [ 'cpp', 'hpp' ] },
563
570
  'CSS': { ext: 'css' },
571
+ 'CMake': { ext: 'cmake', singleLineCommentToken: '#', blockComments: false, ignoreCase: true },
564
572
  'GLSL': { ext: 'glsl' },
565
573
  'WGSL': { ext: 'wgsl' },
566
574
  'JSON': { ext: 'json', blockComments: false, singleLineComments: false },
@@ -573,7 +581,7 @@ class CodeEditor {
573
581
  };
574
582
 
575
583
  this.specialKeys = [
576
- 'Backspace', 'Enter', 'ArrowUp', 'ArrowDown',
584
+ 'Backspace', 'Enter', 'ArrowUp', 'ArrowDown',
577
585
  'ArrowRight', 'ArrowLeft', 'Delete', 'Home',
578
586
  'End', 'Tab', 'Escape'
579
587
  ];
@@ -587,10 +595,10 @@ class CodeEditor {
587
595
  for( let lang in CodeEditor.keywords ) CodeEditor.keywords[lang] = CodeEditor.keywords[lang].reduce((a, v) => ({ ...a, [v]: true}), {});
588
596
  for( let lang in CodeEditor.utils ) CodeEditor.utils[lang] = CodeEditor.utils[lang].reduce((a, v) => ({ ...a, [v]: true}), {});
589
597
  for( let lang in CodeEditor.types ) CodeEditor.types[lang] = CodeEditor.types[lang].reduce((a, v) => ({ ...a, [v]: true}), {});
590
- for( let lang in CodeEditor.builtin ) CodeEditor.builtin[lang] = CodeEditor.builtin[lang].reduce((a, v) => ({ ...a, [v]: true}), {});
598
+ for( let lang in CodeEditor.builtIn ) CodeEditor.builtIn[lang] = CodeEditor.builtIn[lang].reduce((a, v) => ({ ...a, [v]: true}), {});
591
599
  for( let lang in CodeEditor.statementsAndDeclarations ) CodeEditor.statementsAndDeclarations[lang] = CodeEditor.statementsAndDeclarations[lang].reduce((a, v) => ({ ...a, [v]: true}), {});
592
600
  for( let lang in CodeEditor.symbols ) CodeEditor.symbols[lang] = CodeEditor.symbols[lang].reduce((a, v) => ({ ...a, [v]: true}), {});
593
-
601
+
594
602
  CodeEditor._staticReady = true;
595
603
  }
596
604
 
@@ -613,7 +621,7 @@ class CodeEditor {
613
621
  if( cursor.selection ) {
614
622
  this.deleteSelection( cursor );
615
623
  // Remove entire line when selecting with triple click
616
- if( this._tripleClickSelection )
624
+ if( this._tripleClickSelection )
617
625
  {
618
626
  this.actions['Backspace'].callback( ln, cursor, e );
619
627
  this.lineDown( cursor, true );
@@ -626,7 +634,7 @@ class CodeEditor {
626
634
 
627
635
  var deleteFromPosition = cursor.position - 1;
628
636
  var numCharsDeleted = 1;
629
-
637
+
630
638
  // Delete full word
631
639
  if( e.shiftKey )
632
640
  {
@@ -646,7 +654,7 @@ class CodeEditor {
646
654
 
647
655
  if( this.useAutoComplete )
648
656
  this.showAutoCompleteBox( 'foo', cursor );
649
- }
657
+ }
650
658
  else if( this.code.lines[ ln - 1 ] != undefined ) {
651
659
 
652
660
  this.lineUp( cursor );
@@ -663,7 +671,7 @@ class CodeEditor {
663
671
  this.action( 'Delete', false, ( ln, cursor, e ) => {
664
672
 
665
673
  this._addUndoStep( cursor );
666
-
674
+
667
675
  if( cursor.selection ) {
668
676
  // Use 'Backspace' as it's the same callback...
669
677
  this.actions['Backspace'].callback( ln, cursor, e );
@@ -674,7 +682,7 @@ class CodeEditor {
674
682
  if( letter ) {
675
683
  this.code.lines[ ln ] = sliceChars( this.code.lines[ ln ], cursor.position );
676
684
  this.processLine( ln );
677
- }
685
+ }
678
686
  else if( this.code.lines[ ln + 1 ] != undefined ) {
679
687
  this.code.lines[ ln ] += this.code.lines[ ln + 1 ];
680
688
  this.code.lines.splice( ln + 1, 1 );
@@ -684,7 +692,7 @@ class CodeEditor {
684
692
  });
685
693
 
686
694
  this.action( 'Tab', true, ( ln, cursor, e ) => {
687
-
695
+
688
696
  if( this._skipTabs )
689
697
  {
690
698
  this._skipTabs--;
@@ -712,7 +720,7 @@ class CodeEditor {
712
720
  }, "shiftKey");
713
721
 
714
722
  this.action( 'Home', false, ( ln, cursor, e ) => {
715
-
723
+
716
724
  let idx = firstNonspaceIndex( this.code.lines[ ln ] );
717
725
 
718
726
  // We already are in the first non space index...
@@ -732,7 +740,7 @@ class CodeEditor {
732
740
  if( e.shiftKey && !e.cancelShift )
733
741
  {
734
742
  // Get last selection range
735
- if( cursor.selection )
743
+ if( cursor.selection )
736
744
  lastX += cursor.selection.chars;
737
745
 
738
746
  if( !cursor.selection )
@@ -750,9 +758,9 @@ class CodeEditor {
750
758
  });
751
759
 
752
760
  this.action( 'End', false, ( ln, cursor, e ) => {
753
-
761
+
754
762
  if( ( e.shiftKey || e._shiftKey ) && !e.cancelShift ) {
755
-
763
+
756
764
  var string = this.code.lines[ ln ].substring( cursor.position );
757
765
  if( !cursor.selection )
758
766
  this.startSelection( cursor );
@@ -761,7 +769,7 @@ class CodeEditor {
761
769
  else
762
770
  {
763
771
  this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
764
- this.cursorToString( cursor, this.code.lines[ ln ] );
772
+ this.cursorToString( cursor, this.code.lines[ ln ] );
765
773
  this._processSelection( cursor, e );
766
774
  }
767
775
  } else if( !e.keepSelection )
@@ -833,7 +841,7 @@ class CodeEditor {
833
841
  if( !letter ) {
834
842
  this.cursorToPosition( cursor, this.code.lines[ cursor.line ].length );
835
843
  }
836
-
844
+
837
845
  this._processSelection( cursor, e, false );
838
846
 
839
847
  } else {
@@ -843,7 +851,7 @@ class CodeEditor {
843
851
  var letter = this.getCharAtPos( cursor );
844
852
  if( !letter ) this.actions['End'].callback( cursor.line, cursor, e );
845
853
  }
846
- }
854
+ }
847
855
  // Move up autocomplete selection
848
856
  else
849
857
  {
@@ -874,7 +882,7 @@ class CodeEditor {
874
882
  if( e.shiftKey ) {
875
883
  this._processSelection( cursor, e );
876
884
  }
877
- }
885
+ }
878
886
  // Move down autocomplete selection
879
887
  else
880
888
  {
@@ -910,7 +918,7 @@ class CodeEditor {
910
918
  // Selections...
911
919
  if( e.shiftKey ) {
912
920
  if( !cursor.selection )
913
- this.startSelection( cursor );
921
+ this.startSelection( cursor );
914
922
  }
915
923
  else
916
924
  this.endSelection();
@@ -944,9 +952,9 @@ class CodeEditor {
944
952
  }
945
953
  }
946
954
  else if( cursor.line > 0 ) {
947
-
955
+
948
956
  if( e.shiftKey && !cursor.selection ) this.startSelection( cursor );
949
-
957
+
950
958
  this.lineUp( cursor );
951
959
 
952
960
  e.cancelShift = e.keepSelection = true;
@@ -961,7 +969,7 @@ class CodeEditor {
961
969
  this.action( 'ArrowRight', false, ( ln, cursor, e ) => {
962
970
 
963
971
  // Nothing to do..
964
- if( cursor.line == this.code.lines.length - 1 &&
972
+ if( cursor.line == this.code.lines.length - 1 &&
965
973
  cursor.position == this.code.lines[ cursor.line ].length )
966
974
  return;
967
975
 
@@ -1025,14 +1033,14 @@ class CodeEditor {
1025
1033
  }
1026
1034
  }
1027
1035
  else if( this.code.lines[ cursor.line + 1 ] !== undefined ) {
1028
-
1036
+
1029
1037
  if( e.shiftKey ) {
1030
1038
  if( !cursor.selection ) this.startSelection( cursor );
1031
1039
  }
1032
1040
  else this.endSelection();
1033
1041
 
1034
1042
  this.lineDown( cursor, true );
1035
-
1043
+
1036
1044
  if( e.shiftKey ) this._processSelection( cursor, e, false );
1037
1045
 
1038
1046
  this.hideAutoCompleteBox();
@@ -1041,14 +1049,16 @@ class CodeEditor {
1041
1049
  });
1042
1050
 
1043
1051
  // Default code tab
1044
-
1052
+
1045
1053
  this.loadedTabs = { };
1046
1054
  this.openedTabs = { };
1047
-
1048
- if( options.allow_add_scripts ?? true )
1055
+
1056
+ if( options.allowAddScripts ?? true )
1057
+ {
1049
1058
  this.addTab("+", false, "New File");
1059
+ }
1050
1060
 
1051
- this.addTab( options.name || "untitled", true, options.title );
1061
+ this.addTab( options.name || "untitled", true, options.title, { language: "CSS" } );
1052
1062
 
1053
1063
  // Create inspector panel
1054
1064
  let panel = this._createPanelInfo();
@@ -1064,7 +1074,7 @@ class CodeEditor {
1064
1074
  display: "swap"
1065
1075
  }
1066
1076
  );
1067
-
1077
+
1068
1078
  // Add to the document.fonts (FontFaceSet)
1069
1079
  document.fonts.add( commitMono );
1070
1080
 
@@ -1085,7 +1095,7 @@ class CodeEditor {
1085
1095
 
1086
1096
  // This received key inputs from the entire document...
1087
1097
  onKeyPressed( e ) {
1088
-
1098
+
1089
1099
  // Toggle visibility of the file explorer
1090
1100
  if( e.key == 'b' && e.ctrlKey && this.explorer )
1091
1101
  {
@@ -1154,7 +1164,7 @@ class CodeEditor {
1154
1164
 
1155
1165
  // Add first line
1156
1166
  this.code.lines[ lidx ] = [
1157
- this.code.lines[ lidx ].slice( 0, cursor.position ),
1167
+ this.code.lines[ lidx ].slice( 0, cursor.position ),
1158
1168
  first_line
1159
1169
  ].join('');
1160
1170
 
@@ -1181,8 +1191,8 @@ class CodeEditor {
1181
1191
  else
1182
1192
  {
1183
1193
  this.code.lines[ lidx ] = [
1184
- this.code.lines[ lidx ].slice( 0, cursor.position ),
1185
- new_lines[ 0 ],
1194
+ this.code.lines[ lidx ].slice( 0, cursor.position ),
1195
+ new_lines[ 0 ],
1186
1196
  this.code.lines[ lidx ].slice( cursor.position )
1187
1197
  ].join('');
1188
1198
 
@@ -1191,7 +1201,7 @@ class CodeEditor {
1191
1201
  }
1192
1202
  }
1193
1203
 
1194
- loadFile( file ) {
1204
+ loadFile( file, options = {} ) {
1195
1205
 
1196
1206
  const inner_add_tab = ( text, name, title ) => {
1197
1207
 
@@ -1207,16 +1217,25 @@ class CodeEditor {
1207
1217
 
1208
1218
  if( this.explorer )
1209
1219
  {
1210
- this._storedLines = this._storedLines ?? {};
1211
- this._storedLines[ name ] = lines;
1212
- this.addExplorerItem( { 'id': name, 'skipVisibility': true, 'icon': this._getFileIcon( name ) } );
1220
+ this._tabStorage[ name ] = {
1221
+ lines: lines,
1222
+ options: options
1223
+ };
1224
+
1225
+ const ext = this.languages[ options.language ] ?. ext;
1226
+ this.addExplorerItem( { 'id': name, 'skipVisibility': true, 'icon': this._getFileIcon( name, ext ) } );
1213
1227
  this.explorer.frefresh( name );
1214
1228
  }
1215
1229
  else
1216
1230
  {
1217
- this.addTab(name, true, title);
1231
+ this.addTab( name, true, title, options );
1218
1232
  this.code.lines = lines;
1219
- this._changeLanguageFromExtension( LX.getExtension( name ) );
1233
+
1234
+ // Default inferred language if not specified
1235
+ if( !options.language )
1236
+ {
1237
+ this._changeLanguageFromExtension( LX.getExtension( name ) );
1238
+ }
1220
1239
  }
1221
1240
  };
1222
1241
 
@@ -1224,7 +1243,6 @@ class CodeEditor {
1224
1243
  {
1225
1244
  let filename = file;
1226
1245
  LX.request({ url: filename, success: text => {
1227
-
1228
1246
  const name = filename.substring(filename.lastIndexOf( '/' ) + 1);
1229
1247
  inner_add_tab( text, name, filename );
1230
1248
  } });
@@ -1233,7 +1251,7 @@ class CodeEditor {
1233
1251
  {
1234
1252
  const fr = new FileReader();
1235
1253
  fr.readAsText( file );
1236
- fr.onload = e => {
1254
+ fr.onload = e => {
1237
1255
  const text = e.currentTarget.result;
1238
1256
  inner_add_tab( text, file.name );
1239
1257
  };
@@ -1251,7 +1269,7 @@ class CodeEditor {
1251
1269
 
1252
1270
  return;
1253
1271
  }
1254
-
1272
+
1255
1273
  let cursor = document.createElement( 'div' );
1256
1274
  cursor.name = "cursor" + this.cursors.childElementCount;
1257
1275
  cursor.className = "cursor";
@@ -1268,7 +1286,7 @@ class CodeEditor {
1268
1286
 
1269
1287
  Object.defineProperty( cursor, 'line', {
1270
1288
  get: (v) => { return cursor._line },
1271
- set: (v) => {
1289
+ set: (v) => {
1272
1290
  cursor._line = v;
1273
1291
  if( cursor.isMain ) this._setActiveLine( v );
1274
1292
  }
@@ -1276,7 +1294,7 @@ class CodeEditor {
1276
1294
 
1277
1295
  Object.defineProperty( cursor, 'position', {
1278
1296
  get: (v) => { return cursor._position },
1279
- set: (v) => {
1297
+ set: (v) => {
1280
1298
  cursor._position = v;
1281
1299
  if( cursor.isMain ) this._updateDataInfoPanel( "@cursor-pos", "Col " + ( v + 1 ) );
1282
1300
  }
@@ -1335,7 +1353,7 @@ class CodeEditor {
1335
1353
  }
1336
1354
  }
1337
1355
 
1338
- if( deleteRedo )
1356
+ if( deleteRedo )
1339
1357
  {
1340
1358
  // Remove all redo steps
1341
1359
  this.code.redoSteps.length = 0;
@@ -1417,10 +1435,16 @@ class CodeEditor {
1417
1435
  }
1418
1436
  }
1419
1437
 
1420
- _changeLanguage( lang ) {
1438
+ _changeLanguage( lang, override = false ) {
1421
1439
 
1422
1440
  this.code.language = lang;
1423
1441
  this.highlight = lang;
1442
+
1443
+ if( override )
1444
+ {
1445
+ this.code.languageOverride = lang;
1446
+ }
1447
+
1424
1448
  this._updateDataInfoPanel( "@highlight", lang );
1425
1449
  this.processLines();
1426
1450
 
@@ -1437,7 +1461,8 @@ class CodeEditor {
1437
1461
  {
1438
1462
  iconEl = document.createElement( 'i' );
1439
1463
  iconEl.className = icon;
1440
- } else {
1464
+ } else
1465
+ {
1441
1466
  iconEl = document.createElement( 'img' );
1442
1467
  iconEl.src = "https://raw.githubusercontent.com/jxarco/lexgui.js/master/" + icon;
1443
1468
  }
@@ -1455,21 +1480,38 @@ class CodeEditor {
1455
1480
  }
1456
1481
 
1457
1482
  _changeLanguageFromExtension( ext ) {
1458
-
1483
+
1459
1484
  if( !ext )
1460
- return this._changeLanguage( this.code.language );
1485
+ {
1486
+ return this._changeLanguage( this.code.language );
1487
+ }
1461
1488
 
1462
1489
  for( let l in this.languages )
1463
1490
  {
1464
- if( this.languages[l].ext == ext )
1465
- return this._changeLanguage( l );
1491
+ const langExtension = this.languages[ l ].ext;
1492
+
1493
+ if( langExtension.constructor == Array )
1494
+ {
1495
+ if( langExtension.indexOf( ext ) > -1 )
1496
+ {
1497
+ return this._changeLanguage( l );
1498
+ }
1499
+ }
1500
+ else
1501
+ {
1502
+ if( langExtension == ext )
1503
+ {
1504
+ return this._changeLanguage( l );
1505
+ }
1506
+ }
1507
+
1466
1508
  }
1467
1509
 
1468
1510
  this._changeLanguage( 'Plain Text' );
1469
1511
  }
1470
1512
 
1471
1513
  _createPanelInfo() {
1472
-
1514
+
1473
1515
  if( !this.skipCodeInfo )
1474
1516
  {
1475
1517
  let panel = new LX.Panel({ className: "lexcodetabinfo", width: "calc(100%)", height: "auto" });
@@ -1491,8 +1533,12 @@ class CodeEditor {
1491
1533
  }, { width: "10%", nameWidth: "15%", signal: "@tab-spaces" });
1492
1534
  panel.addButton( "<b>{ }</b>", this.highlight, ( value, event ) => {
1493
1535
  LX.addContextMenu( "Language", event, m => {
1494
- for( const lang of Object.keys(this.languages) )
1495
- m.add( lang, this._changeLanguage.bind(this) );
1536
+ for( const lang of Object.keys( this.languages ) )
1537
+ {
1538
+ m.add( lang, v => {
1539
+ this._changeLanguage( v, true )
1540
+ } );
1541
+ }
1496
1542
  });
1497
1543
  }, { width: "17.5%", nameWidth: "15%", signal: "@highlight" });
1498
1544
  panel.endLine();
@@ -1517,12 +1563,36 @@ class CodeEditor {
1517
1563
  _getFileIcon( name, extension ) {
1518
1564
 
1519
1565
  const isNewTabButton = name ? ( name === '+' ) : false;
1520
- const ext = extension ?? LX.getExtension( name );
1521
- return ext == 'html' ? "fa-solid fa-code orange" :
1522
- ext == 'css' ? "fa-solid fa-hashtag dodgerblue" :
1523
- ext == 'xml' ? "fa-solid fa-rss orange" :
1524
- ext == 'bat' ? "fa-brands fa-windows lightblue" :
1525
- [ 'js', 'py', 'json', 'cpp', 'rs', 'md' ].indexOf( ext ) > -1 ? "images/" + ext + ".png" :
1566
+
1567
+ if( !extension )
1568
+ {
1569
+ extension = LX.getExtension( name );
1570
+ }
1571
+ else
1572
+ {
1573
+ const possibleExtensions = [].concat( extension );
1574
+
1575
+ if( name )
1576
+ {
1577
+ const fileExtension = LX.getExtension( name );
1578
+ const idx = possibleExtensions.indexOf( fileExtension );
1579
+
1580
+ if( idx > -1)
1581
+ {
1582
+ extension = possibleExtensions[ idx ];
1583
+ }
1584
+ }
1585
+ else
1586
+ {
1587
+ extension = possibleExtensions[ 0 ];
1588
+ }
1589
+ }
1590
+
1591
+ return extension == "html" ? "fa-solid fa-code orange" :
1592
+ extension == "css" ? "fa-solid fa-hashtag dodgerblue" :
1593
+ extension == "xml" ? "fa-solid fa-rss orange" :
1594
+ extension == "bat" ? "fa-brands fa-windows lightblue" :
1595
+ [ 'js', 'py', 'json', 'cpp', 'hpp', 'rs', 'md' ].indexOf( extension ) > -1 ? "images/" + extension + ".png" :
1526
1596
  !isNewTabButton ? "fa-solid fa-align-left gray" : undefined;
1527
1597
  }
1528
1598
 
@@ -1547,18 +1617,26 @@ class CodeEditor {
1547
1617
  this._removeSecondaryCursors();
1548
1618
  var cursor = this._getCurrentCursor( true );
1549
1619
 
1550
- this.saveCursor( cursor, this.code.cursorState );
1620
+ this.saveCursor( cursor, this.code.cursorState );
1551
1621
 
1552
1622
  this.code = this.loadedTabs[ name ];
1553
- this.restoreCursor( cursor, this.code.cursorState );
1623
+ this.restoreCursor( cursor, this.code.cursorState );
1554
1624
 
1555
1625
  this.endSelection();
1556
- this._changeLanguageFromExtension( LX.getExtension( name ) );
1557
1626
  this._updateDataInfoPanel( "@tab-name", name );
1627
+
1628
+ if( this.code.languageOverride )
1629
+ {
1630
+ this._changeLanguage( this.code.languageOverride );
1631
+ }
1632
+ else
1633
+ {
1634
+ this._changeLanguageFromExtension( LX.getExtension( name ) );
1635
+ }
1558
1636
  }
1559
1637
 
1560
1638
  _onContextMenuTab( isNewTabButton, event, name, ) {
1561
-
1639
+
1562
1640
  if( isNewTabButton )
1563
1641
  return;
1564
1642
 
@@ -1569,8 +1647,8 @@ class CodeEditor {
1569
1647
  });
1570
1648
  }
1571
1649
 
1572
- addTab( name, selected, title ) {
1573
-
1650
+ addTab( name, selected, title, options = {} ) {
1651
+
1574
1652
  // If already loaded, set new name...
1575
1653
  const repeats = Object.keys( editor.loadedTabs ).slice( 1 ).reduce( ( v, key ) => {
1576
1654
  const noRepeatName = key.replace( /[_\d+]/g, '');
@@ -1586,7 +1664,7 @@ class CodeEditor {
1586
1664
  let code = document.createElement( 'div' );
1587
1665
  code.className = 'code';
1588
1666
  code.lines = [ "" ];
1589
- code.language = "Plain Text";
1667
+ code.language = options.language ?? "Plain Text";
1590
1668
  code.cursorState = {};
1591
1669
  code.undoSteps = [];
1592
1670
  code.redoSteps = [];
@@ -1613,7 +1691,7 @@ class CodeEditor {
1613
1691
 
1614
1692
  this.loadedTabs[ name ] = code;
1615
1693
  this.openedTabs[ name ] = code;
1616
-
1694
+
1617
1695
  const tabIcon = this._getFileIcon( name );
1618
1696
 
1619
1697
  if( this.explorer && !isNewTabButton )
@@ -1622,27 +1700,33 @@ class CodeEditor {
1622
1700
  this.explorer.frefresh( name );
1623
1701
  }
1624
1702
 
1625
- this.tabs.add( name, code, {
1626
- selected: selected,
1627
- fixed: isNewTabButton,
1628
- title: code.title,
1629
- icon: tabIcon,
1703
+ this.tabs.add( name, code, {
1704
+ selected: selected,
1705
+ fixed: isNewTabButton,
1706
+ title: code.title,
1707
+ icon: tabIcon,
1630
1708
  onSelect: this._onSelectTab.bind( this, isNewTabButton ),
1631
1709
  onContextMenu: this._onContextMenuTab.bind( this, isNewTabButton )
1632
1710
  } );
1633
1711
 
1634
1712
  // Move into the sizer..
1635
1713
  this.codeSizer.appendChild( code );
1636
-
1714
+
1637
1715
  this.endSelection();
1638
1716
 
1639
1717
  if( selected )
1640
1718
  {
1641
- this.code = code;
1719
+ this.code = code;
1642
1720
  this.resetCursorPos( CodeEditor.CURSOR_LEFT | CodeEditor.CURSOR_TOP );
1643
1721
  this.processLines();
1644
1722
  }
1645
1723
 
1724
+ if( options.language )
1725
+ {
1726
+ code.languageOverride = options.language;
1727
+ this._changeLanguage( code.languageOverride );
1728
+ }
1729
+
1646
1730
  this._updateDataInfoPanel( "@tab-name", name );
1647
1731
 
1648
1732
  // Bc it could be overrided..
@@ -1663,41 +1747,53 @@ class CodeEditor {
1663
1747
  if( !code )
1664
1748
  {
1665
1749
  this.addTab( name, true );
1666
- // Unload lines from file...
1667
- if( this._storedLines[ name ] )
1750
+
1751
+ // Unload lines from storage...
1752
+ const tabData = this._tabStorage[ name ];
1753
+ if( tabData )
1668
1754
  {
1669
- this.code.lines = this._storedLines[ name ];
1670
- delete this._storedLines[ name ];
1755
+ this.code.lines = tabData.lines;
1756
+
1757
+ if( tabData.options.language )
1758
+ {
1759
+ this._changeLanguage( tabData.options.language, true );
1760
+ }
1761
+ else
1762
+ {
1763
+ this._changeLanguageFromExtension( LX.getExtension( name ) );
1764
+ }
1765
+
1766
+ delete this._tabStorage[ name ];
1671
1767
  }
1672
- this._changeLanguageFromExtension( LX.getExtension( name ) );
1768
+
1673
1769
  return;
1674
1770
  }
1675
1771
 
1676
1772
  this.openedTabs[ name ] = code;
1677
-
1773
+
1678
1774
  const isNewTabButton = ( name === '+' );
1679
1775
  const tabIcon = this._getFileIcon( name );
1680
1776
 
1681
- this.tabs.add(name, code, {
1682
- selected: true,
1683
- fixed: isNewTabButton,
1684
- title: code.title,
1685
- icon: tabIcon,
1777
+ this.tabs.add(name, code, {
1778
+ selected: true,
1779
+ fixed: isNewTabButton,
1780
+ title: code.title,
1781
+ icon: tabIcon,
1686
1782
  onSelect: this._onSelectTab.bind( this, isNewTabButton ),
1687
1783
  onContextMenu: this._onContextMenuTab.bind( this, isNewTabButton )
1688
1784
  });
1689
1785
 
1690
1786
  // Move into the sizer..
1691
1787
  this.codeSizer.appendChild( code );
1692
-
1788
+
1693
1789
  this.endSelection();
1694
1790
 
1695
1791
  // Select as current...
1696
- this.code = code;
1792
+ this.code = code;
1697
1793
  this.resetCursorPos( CodeEditor.CURSOR_LEFT | CodeEditor.CURSOR_TOP );
1698
1794
  this.processLines();
1699
1795
  this._changeLanguageFromExtension( LX.getExtension( name ) );
1700
- this._updateDataInfoPanel( "@tab-name", tabname );
1796
+ this._updateDataInfoPanel( "@tab-name", code.tabname );
1701
1797
  }
1702
1798
 
1703
1799
  loadTabFromFile() {
@@ -1747,7 +1843,7 @@ class CodeEditor {
1747
1843
 
1748
1844
  if( cursor.selection )
1749
1845
  {
1750
- this.canOpenContextMenu |= (cursor.line >= cursor.selection.fromY && cursor.line <= cursor.selection.toY
1846
+ this.canOpenContextMenu |= (cursor.line >= cursor.selection.fromY && cursor.line <= cursor.selection.toY
1751
1847
  && cursor.position >= cursor.selection.fromX && cursor.position <= cursor.selection.toX);
1752
1848
  if( this.canOpenContextMenu )
1753
1849
  return;
@@ -1759,7 +1855,7 @@ class CodeEditor {
1759
1855
  this.endSelection();
1760
1856
  this.processClick( e );
1761
1857
  }
1762
-
1858
+
1763
1859
  else if( e.type == 'mouseup' )
1764
1860
  {
1765
1861
  this._onMouseUp( e );
@@ -1807,11 +1903,11 @@ class CodeEditor {
1807
1903
  m.add( "Cut", () => { this._cutContent( cursor ); } );
1808
1904
  m.add( "Paste", () => { this._pasteContent( cursor ); } );
1809
1905
  m.add( "" );
1810
- m.add( "Format/JSON", () => {
1906
+ m.add( "Format/JSON", () => {
1811
1907
  let json = this.toJSONFormat( this.getText() );
1812
1908
  if( !json )
1813
1909
  return;
1814
- this.code.lines = json.split( "\n" );
1910
+ this.code.lines = json.split( "\n" );
1815
1911
  this.processLines();
1816
1912
  } );
1817
1913
  }
@@ -1859,7 +1955,7 @@ class CodeEditor {
1859
1955
  this.cursorToLine( cursor, ln, true );
1860
1956
  this.cursorToPosition( cursor, string.length );
1861
1957
  }
1862
-
1958
+
1863
1959
  // Add new cursor
1864
1960
  else
1865
1961
  {
@@ -1905,7 +2001,7 @@ class CodeEditor {
1905
2001
  let ccw = true;
1906
2002
 
1907
2003
  // Check if we must change ccw or not ... (not with mouse)
1908
- if( !isMouseEvent && cursor.line >= cursor.selection.fromY &&
2004
+ if( !isMouseEvent && cursor.line >= cursor.selection.fromY &&
1909
2005
  (cursor.line == cursor.selection.fromY ? cursor.position >= cursor.selection.fromX : true) )
1910
2006
  {
1911
2007
  ccw = ( e && this._lastSelectionKeyDir && ( e.key == 'ArrowRight' || e.key == 'ArrowDown' || e.key == 'End' ) );
@@ -1968,7 +2064,7 @@ class CodeEditor {
1968
2064
 
1969
2065
  // Compute new width and selection margins
1970
2066
  let string;
1971
-
2067
+
1972
2068
  if(sId == 0) // First line 2 cases (single line, multiline)
1973
2069
  {
1974
2070
  const reverse = fromX > toX;
@@ -1982,7 +2078,7 @@ class CodeEditor {
1982
2078
  string = (i == toY) ? this.code.lines[ i ].substring( 0, toX ) : this.code.lines[ i ]; // Last line, any multiple line...
1983
2079
  if( isVisible ) domEl.style.left = this.xPadding;
1984
2080
  }
1985
-
2081
+
1986
2082
  const stringWidth = this.measureString( string );
1987
2083
  cursor.selection.chars += stringWidth / this.charWidth;
1988
2084
 
@@ -2019,7 +2115,7 @@ class CodeEditor {
2019
2115
 
2020
2116
  // Compute new width and selection margins
2021
2117
  let string;
2022
-
2118
+
2023
2119
  if( sId == 0 )
2024
2120
  {
2025
2121
  string = this.code.lines[ i ].substr(toX);
@@ -2031,10 +2127,10 @@ class CodeEditor {
2031
2127
  string = (i == fromY) ? this.code.lines[ i ].substring(0, fromX) : this.code.lines[ i ]; // Last line, any multiple line...
2032
2128
  if( isVisible ) domEl.style.left = this.xPadding;
2033
2129
  }
2034
-
2130
+
2035
2131
  const stringWidth = this.measureString( string );
2036
2132
  cursor.selection.chars += stringWidth / this.charWidth;
2037
-
2133
+
2038
2134
  if( isVisible )
2039
2135
  {
2040
2136
  domEl.style.width = (stringWidth || 8) + "px";
@@ -2046,10 +2142,10 @@ class CodeEditor {
2046
2142
  }
2047
2143
 
2048
2144
  async processKey( e ) {
2049
-
2145
+
2050
2146
  const numCursors = this.cursors.childElementCount;
2051
2147
 
2052
- if( !this.code || e.srcElement.constructor != HTMLDivElement )
2148
+ if( !this.code || e.srcElement.constructor != HTMLDivElement )
2053
2149
  return;
2054
2150
 
2055
2151
  const key = e.key ?? e.detail.key;
@@ -2254,7 +2350,7 @@ class CodeEditor {
2254
2350
  return;
2255
2351
  }
2256
2352
  }
2257
-
2353
+
2258
2354
  // Apply binded actions...
2259
2355
 
2260
2356
  for( const actKey in this.actions ) {
@@ -2286,11 +2382,11 @@ class CodeEditor {
2286
2382
  const enclosableKeys = [ "\"", "'", "(", "{" ];
2287
2383
  if( enclosableKeys.indexOf( key ) > -1 )
2288
2384
  {
2289
- if( this._encloseSelectedWordWithKey( key, lidx, cursor ) )
2385
+ if( this._encloseSelectedWordWithKey( key, lidx, cursor ) )
2290
2386
  return;
2291
2387
  }
2292
2388
 
2293
- // Until this point, if there was a selection, we need
2389
+ // Until this point, if there was a selection, we need
2294
2390
  // to delete the content..
2295
2391
 
2296
2392
  if( cursor.selection )
@@ -2307,8 +2403,8 @@ class CodeEditor {
2307
2403
  if( !sameKeyNext )
2308
2404
  {
2309
2405
  this.code.lines[ lidx ] = [
2310
- this.code.lines[ lidx ].slice( 0, cursor.position ),
2311
- key,
2406
+ this.code.lines[ lidx ].slice( 0, cursor.position ),
2407
+ key,
2312
2408
  this.code.lines[ lidx ].slice( cursor.position )
2313
2409
  ].join('');
2314
2410
  }
@@ -2316,7 +2412,7 @@ class CodeEditor {
2316
2412
  this.cursorToRight( key, cursor );
2317
2413
 
2318
2414
  // Some custom cases for auto key pair (), {}, "", '', ...
2319
-
2415
+
2320
2416
  const keyMustPair = (this.pairKeys[ key ] !== undefined);
2321
2417
  if( keyMustPair && !this.wasKeyPaired )
2322
2418
  {
@@ -2377,7 +2473,7 @@ class CodeEditor {
2377
2473
  text_to_copy = "\n" + this.code.lines[ cursor.line ];
2378
2474
  }
2379
2475
  else {
2380
-
2476
+
2381
2477
  // Some selections don't depend on mouse up..
2382
2478
  if( cursor.selection ) cursor.selection.invertIfNecessary();
2383
2479
 
@@ -2386,11 +2482,11 @@ class CodeEditor {
2386
2482
 
2387
2483
  // Get linear start index
2388
2484
  let index = 0;
2389
-
2485
+
2390
2486
  for( let i = 0; i <= cursor.selection.fromY; i++ )
2391
2487
  index += ( i == cursor.selection.fromY ? cursor.selection.fromX : this.code.lines[ i ].length );
2392
2488
 
2393
- index += cursor.selection.fromY * separator.length;
2489
+ index += cursor.selection.fromY * separator.length;
2394
2490
  const num_chars = cursor.selection.chars + ( cursor.selection.toY - cursor.selection.fromY ) * separator.length;
2395
2491
  const text = code.substr( index, num_chars );
2396
2492
  const lines = text.split( separator );
@@ -2416,7 +2512,7 @@ class CodeEditor {
2416
2512
  this.lineUp( cursor );
2417
2513
  }
2418
2514
  else {
2419
-
2515
+
2420
2516
  // Some selections don't depend on mouse up..
2421
2517
  if( cursor.selection ) cursor.selection.invertIfNecessary();
2422
2518
 
@@ -2425,11 +2521,11 @@ class CodeEditor {
2425
2521
 
2426
2522
  // Get linear start index
2427
2523
  let index = 0;
2428
-
2524
+
2429
2525
  for(let i = 0; i <= cursor.selection.fromY; i++)
2430
2526
  index += (i == cursor.selection.fromY ? cursor.selection.fromX : this.code.lines[ i ].length);
2431
2527
 
2432
- index += cursor.selection.fromY * separator.length;
2528
+ index += cursor.selection.fromY * separator.length;
2433
2529
  const num_chars = cursor.selection.chars + (cursor.selection.toY - cursor.selection.fromY) * separator.length;
2434
2530
  const text = code.substr(index, num_chars);
2435
2531
  const lines = text.split(separator);
@@ -2442,7 +2538,7 @@ class CodeEditor {
2442
2538
  }
2443
2539
 
2444
2540
  _duplicateLine( lidx, cursor ) {
2445
-
2541
+
2446
2542
  this.endSelection();
2447
2543
  this._addUndoStep( cursor, true );
2448
2544
  this.code.lines.splice( lidx, 0, this.code.lines[ lidx ] );
@@ -2599,20 +2695,20 @@ class CodeEditor {
2599
2695
  }
2600
2696
 
2601
2697
  processLines( mode ) {
2602
-
2698
+
2603
2699
  var code_html = "";
2604
2700
  this._blockCommentCache.length = 0;
2605
-
2701
+
2606
2702
  // Reset all lines content
2607
2703
  this.code.innerHTML = "";
2608
-
2704
+
2609
2705
  // Get info about lines in viewport
2610
2706
  const lastScrollTop = this.getScrollTop();
2611
- this.firstLineInViewport = ( mode ?? CodeEditor.KEEP_VISIBLE_LINES ) & CodeEditor.UPDATE_VISIBLE_LINES ?
2707
+ this.firstLineInViewport = ( mode ?? CodeEditor.KEEP_VISIBLE_LINES ) & CodeEditor.UPDATE_VISIBLE_LINES ?
2612
2708
  ( (lastScrollTop / this.lineHeight)|0 ) : this.firstLineInViewport;
2613
2709
  const totalLinesInViewport = ((this.codeScroller.offsetHeight - 36) / this.lineHeight)|0;
2614
- this.visibleLinesViewport = new LX.vec2(
2615
- Math.max( this.firstLineInViewport - this.lineScrollMargin.x, 0 ),
2710
+ this.visibleLinesViewport = new LX.vec2(
2711
+ Math.max( this.firstLineInViewport - this.lineScrollMargin.x, 0 ),
2616
2712
  Math.min( this.firstLineInViewport + totalLinesInViewport + this.lineScrollMargin.y, this.code.lines.length )
2617
2713
  );
2618
2714
 
@@ -2622,15 +2718,15 @@ class CodeEditor {
2622
2718
  if( diff <= this.lineScrollMargin.y )
2623
2719
  this.visibleLinesViewport.y += diff;
2624
2720
  }
2625
-
2721
+
2626
2722
  // Process visible lines
2627
2723
  for( let i = this.visibleLinesViewport.x; i < this.visibleLinesViewport.y; ++i )
2628
2724
  {
2629
2725
  code_html += this.processLine( i, true );
2630
2726
  }
2631
-
2727
+
2632
2728
  this.code.innerHTML = code_html;
2633
-
2729
+
2634
2730
  // Update scroll data
2635
2731
  this.codeScroller.scrollTop = lastScrollTop;
2636
2732
  this.code.style.top = ( this.visibleLinesViewport.x * this.lineHeight ) + "px";
@@ -2668,10 +2764,10 @@ class CodeEditor {
2668
2764
  }
2669
2765
 
2670
2766
  // multi-line strings not supported by now
2671
- delete this._buildingString;
2767
+ delete this._buildingString;
2672
2768
  delete this._pendingString;
2673
2769
  delete this._markdownHeader;
2674
-
2770
+
2675
2771
  let linestring = this.code.lines[ linenum ];
2676
2772
 
2677
2773
  // Single line
@@ -2706,14 +2802,14 @@ class CodeEditor {
2706
2802
  it--;
2707
2803
  prev = tokensToEvaluate[ it ];
2708
2804
  }
2709
-
2805
+
2710
2806
  it = i + 1;
2711
2807
  let next = tokensToEvaluate[ it ];
2712
2808
  while( next == ' ' || next == '"' ) {
2713
2809
  it++;
2714
2810
  next = tokensToEvaluate[ it ];
2715
2811
  }
2716
-
2812
+
2717
2813
  const token = tokensToEvaluate[ i ];
2718
2814
 
2719
2815
  if( lang.blockComments ?? true )
@@ -2731,7 +2827,8 @@ class CodeEditor {
2731
2827
  nextWithSpaces: tokensToEvaluate[ i + 1 ],
2732
2828
  tokenIndex: i,
2733
2829
  isFirstToken: (i == 0),
2734
- isLastToken: (i == tokensToEvaluate.length - 1)
2830
+ isLastToken: (i == tokensToEvaluate.length - 1),
2831
+ tokens: tokensToEvaluate
2735
2832
  } );
2736
2833
  }
2737
2834
 
@@ -2753,10 +2850,10 @@ class CodeEditor {
2753
2850
  const stringKeys = Object.values( this.stringKeys );
2754
2851
  // Count times we started a string BEFORE the comment
2755
2852
  var err = false;
2756
- err |= stringKeys.some( function(v) {
2757
- var re = new RegExp( v, "g" );
2853
+ err |= stringKeys.some( function(v) {
2854
+ var re = new RegExp( v, "g" );
2758
2855
  var matches = (linestring.substring( 0, idx ).match( re ) || []);
2759
- return (matches.length % 2) !== 0;
2856
+ return (matches.length % 2) !== 0;
2760
2857
  } );
2761
2858
  return err ? undefined : idx;
2762
2859
  }
@@ -2839,9 +2936,21 @@ class CodeEditor {
2839
2936
  return tokens;
2840
2937
  }
2841
2938
 
2842
- _mustHightlightWord( token, kindArray ) {
2939
+ _mustHightlightWord( token, kindArray, lang ) {
2940
+
2941
+ if( !lang )
2942
+ {
2943
+ lang = this.languages[ this.highlight ];
2944
+ }
2945
+
2946
+ let t = token;
2947
+
2948
+ if( lang.ignoreCase )
2949
+ {
2950
+ t = t.toLowerCase();
2951
+ }
2843
2952
 
2844
- return kindArray[this.highlight] && kindArray[this.highlight][token] != undefined;
2953
+ return kindArray[ this.highlight ] && kindArray[ this.highlight ][ t ] != undefined;
2845
2954
  }
2846
2955
 
2847
2956
  _evaluateToken( ctxData ) {
@@ -2859,10 +2968,10 @@ class CodeEditor {
2859
2968
 
2860
2969
  var usePreviousTokenToCheckString = false;
2861
2970
 
2862
- if( highlight == 'cpp' && prev && prev.includes( '#' ) ) // preprocessor code..
2971
+ if( [ 'cpp', 'c' ].indexOf( highlight ) > -1 && prev && prev.includes( '#' ) ) // preprocessor code..
2863
2972
  {
2864
2973
  customStringKeys['@<'] = '>';
2865
- }
2974
+ }
2866
2975
  else if( highlight == 'markdown' && ( ctxData.prevWithSpaces == '[' || ctxData.nextWithSpaces == ']' ) )
2867
2976
  {
2868
2977
  // console.warn(prev, token, next)
@@ -2883,43 +2992,43 @@ class CodeEditor {
2883
2992
  if( this._buildingString != undefined )
2884
2993
  {
2885
2994
  checkIfStringEnded( usePreviousTokenToCheckString ? ctxData.nextWithSpaces : token );
2886
- }
2995
+ }
2887
2996
  else if( customStringKeys[ '@' + ( usePreviousTokenToCheckString ? ctxData.prevWithSpaces : token ) ] )
2888
- {
2997
+ {
2889
2998
  // Start new string
2890
2999
  this._buildingString = ( usePreviousTokenToCheckString ? ctxData.prevWithSpaces : token );
2891
3000
 
2892
3001
  // Check if string ended in same token using next...
2893
- if( usePreviousTokenToCheckString )
3002
+ if( usePreviousTokenToCheckString )
2894
3003
  {
2895
3004
  checkIfStringEnded( ctxData.nextWithSpaces );
2896
3005
  }
2897
3006
  }
2898
3007
  }
2899
-
3008
+
2900
3009
  const usesBlockComments = lang.blockComments ?? true;
2901
3010
  const blockCommentsTokens = lang.blockCommentsTokens ?? this.defaultBlockCommentTokens;
2902
3011
  const singleLineCommentToken = lang.singleLineCommentToken ?? this.defaultSingleLineCommentToken;
2903
-
3012
+
2904
3013
  let token_classname = "";
2905
3014
  let discardToken = false;
2906
3015
 
2907
3016
  if( this._buildingBlockComment != undefined )
2908
3017
  token_classname = "cm-com";
2909
-
3018
+
2910
3019
  else if( this._buildingString != undefined )
2911
3020
  discardToken = this._appendStringToken( token );
2912
-
2913
- else if( this._mustHightlightWord( token, CodeEditor.keywords ) && ( lang.tags ?? false ? ( this._enclosedByTokens( token, tokenIndex, '<', '>' ) ) : true ) )
3021
+
3022
+ else if( this._isKeyword( ctxData, lang ) )
2914
3023
  token_classname = "cm-kwd";
2915
3024
 
2916
- else if( this._mustHightlightWord( token, CodeEditor.builtin ) && ( lang.tags ?? false ? ( this._enclosedByTokens( token, tokenIndex, '<', '>' ) ) : true ) )
3025
+ else if( this._mustHightlightWord( token, CodeEditor.builtIn, lang ) && ( lang.tags ?? false ? ( this._enclosedByTokens( token, tokenIndex, '<', '>' ) ) : true ) )
2917
3026
  token_classname = "cm-bln";
2918
3027
 
2919
- else if( this._mustHightlightWord( token, CodeEditor.statementsAndDeclarations ) )
3028
+ else if( this._mustHightlightWord( token, CodeEditor.statementsAndDeclarations, lang ) )
2920
3029
  token_classname = "cm-std";
2921
3030
 
2922
- else if( this._mustHightlightWord( token, CodeEditor.symbols ) )
3031
+ else if( this._mustHightlightWord( token, CodeEditor.symbols, lang ) )
2923
3032
  token_classname = "cm-sym";
2924
3033
 
2925
3034
  else if( token.substr( 0, singleLineCommentToken.length ) == singleLineCommentToken )
@@ -2928,16 +3037,16 @@ class CodeEditor {
2928
3037
  else if( this._isNumber( token ) || this._isNumber( token.replace(/[px]|[em]|%/g,'') ) )
2929
3038
  token_classname = "cm-dec";
2930
3039
 
2931
- else if( this._isCSSClass( token, prev, next ) )
3040
+ else if( this._isCSSClass( ctxData ) )
2932
3041
  token_classname = "cm-kwd";
2933
3042
 
2934
- else if ( this._isType( token, prev, next ) )
3043
+ else if ( this._isType( ctxData, lang ) )
2935
3044
  token_classname = "cm-typ";
2936
3045
 
2937
3046
  else if ( highlight == 'batch' && ( token == '@' || prev == ':' || prev == '@' ) )
2938
3047
  token_classname = "cm-kwd";
2939
3048
 
2940
- else if ( [ 'cpp', 'wgsl', 'glsl' ].indexOf( highlight ) > -1 && token.includes( '#' ) ) // C++ preprocessor
3049
+ else if ( [ 'cpp', 'c', 'wgsl', 'glsl' ].indexOf( highlight ) > -1 && token.includes( '#' ) ) // C++ preprocessor
2941
3050
  token_classname = "cm-ppc";
2942
3051
 
2943
3052
  else if ( highlight == 'cpp' && prev == '<' && (next == '>' || next == '*') ) // Defining template type in C++
@@ -2951,7 +3060,7 @@ class CodeEditor {
2951
3060
 
2952
3061
  else if ( highlight == 'css' && prev == undefined && next == ':' ) // CSS attribute
2953
3062
  token_classname = "cm-typ";
2954
-
3063
+
2955
3064
  else if ( this._markdownHeader || ( highlight == 'markdown' && isFirstToken && token.replaceAll('#', '').length != token.length ) ) // Header
2956
3065
  {
2957
3066
  token_classname = "cm-kwd";
@@ -2962,7 +3071,7 @@ class CodeEditor {
2962
3071
  token_classname = "cm-mtd";
2963
3072
 
2964
3073
 
2965
- if( usesBlockComments && this._buildingBlockComment != undefined
3074
+ if( usesBlockComments && this._buildingBlockComment != undefined
2966
3075
  && token.substr( 0, blockCommentsTokens[ 1 ].length ) == blockCommentsTokens[ 1 ] )
2967
3076
  {
2968
3077
  this._blockCommentCache.push( new LX.vec2( this._buildingBlockComment, this._currentLineNumber ) );
@@ -2981,14 +3090,18 @@ class CodeEditor {
2981
3090
  this._buildingString = this._stringEnded ? undefined : this._buildingString;
2982
3091
 
2983
3092
  if( discardToken )
3093
+ {
2984
3094
  return "";
3095
+ }
2985
3096
 
2986
3097
  token = token.replace( "<", "&lt;" );
2987
3098
  token = token.replace( ">", "&gt;" );
2988
3099
 
2989
3100
  // No highlighting, no need to put it inside another span..
2990
3101
  if( !token_classname.length )
3102
+ {
2991
3103
  return token;
3104
+ }
2992
3105
 
2993
3106
  return "<span class='" + highlight + " " + token_classname + "'>" + token + "</span>";
2994
3107
  }
@@ -3016,11 +3129,17 @@ class CodeEditor {
3016
3129
  const tagStartIndex = indexOfFrom( this._currentLineString, tagStart, tokenStartIndex, true );
3017
3130
  if( tagStartIndex < 0 ) // Not found..
3018
3131
  return;
3132
+ const tagStartIndexOpposite = indexOfFrom( this._currentLineString, tagEnd, tokenStartIndex, true );
3133
+ if( tagStartIndexOpposite >= 0 && tagStartIndexOpposite > tagStartIndex ) // Found the opposite first while reversing..
3134
+ return;
3019
3135
  const tagEndIndex = indexOfFrom( this._currentLineString, tagEnd, tokenStartIndex );
3020
3136
  if( tagEndIndex < 0 ) // Not found..
3021
3137
  return;
3022
3138
 
3023
- return ( tagStartIndex < tokenStartIndex ) && ( tagEndIndex >= ( tokenStartIndex + token.length ) );
3139
+ if( ( tagStartIndex < tokenStartIndex ) && ( tagEndIndex >= ( tokenStartIndex + token.length ) ) && !this._mustHightlightWord( token, CodeEditor.symbols ) )
3140
+ {
3141
+ return [ tagStartIndex, tagEndIndex ];
3142
+ }
3024
3143
  }
3025
3144
 
3026
3145
  _inBlockCommentSection( line ) {
@@ -3028,52 +3147,104 @@ class CodeEditor {
3028
3147
  for( var section of this._blockCommentCache )
3029
3148
  {
3030
3149
  if( line >= section.x && line <= section.y )
3150
+ {
3031
3151
  return true;
3152
+ }
3032
3153
  }
3033
3154
 
3034
3155
  return false;
3035
3156
  }
3036
3157
 
3037
- _isCSSClass( token, prev, next ) {
3158
+ _isKeyword( ctxData, lang ) {
3159
+
3160
+ const token = ctxData.token;
3161
+ const tokenIndex = ctxData.tokenIndex;
3162
+ const tokens = ctxData.tokens;
3163
+
3164
+ let isKwd = this._mustHightlightWord( token, CodeEditor.keywords ) || this.highlight == 'XML';
3165
+
3166
+ if( this.highlight == 'CMake' )
3167
+ {
3168
+ // Highlight $ symbol
3169
+ if( token == '$' && this._enclosedByTokens( tokens[ tokenIndex + 2 ], tokenIndex + 2, '{', '}' ) )
3170
+ {
3171
+ isKwd = true;
3172
+ }
3173
+ // Highlight what is between the { }
3174
+ else if( this._enclosedByTokens( token, tokenIndex, '{', '}' ) )
3175
+ {
3176
+ isKwd |= ( ctxData.tokens[ tokenIndex - 2 ] == '$' );
3177
+ }
3178
+ }
3179
+ else if( lang.tags )
3180
+ {
3181
+ isKwd &= ( this._enclosedByTokens( token, tokenIndex, '<', '>' ) != undefined );
3182
+ }
3183
+
3184
+
3185
+ return isKwd;
3186
+ }
3187
+
3188
+ _isCSSClass( ctxData ) {
3189
+
3190
+ const token = ctxData.token;
3191
+ const prev = ctxData.prev;
3192
+ const next = ctxData.next;
3038
3193
 
3039
3194
  if( this.highlight != 'CSS' )
3195
+ {
3040
3196
  return false;
3197
+ }
3041
3198
 
3042
- return ( prev == '.' || prev == '::'
3043
- || ( prev == ':' && next == '{' )
3199
+ return ( prev == '.' || prev == '::'
3200
+ || ( prev == ':' && next == '{' )
3044
3201
  || ( token[ 0 ] == '#' && prev != ':' ) );
3045
3202
  }
3046
3203
 
3047
3204
  _isNumber( token ) {
3048
3205
 
3049
- if(this.highlight == 'C++')
3206
+ const subToken = token.substring( 0, token.length - 1 );
3207
+
3208
+ if( this.highlight == 'C++' )
3050
3209
  {
3051
3210
  if( token.lastChar == 'f' )
3052
- return this._isNumber( token.substring(0, token.length - 1) )
3211
+ {
3212
+ return this._isNumber( subToken )
3213
+ }
3053
3214
  else if( token.lastChar == 'u' )
3054
- return !(token.includes('.')) && this._isNumber( token.substring(0, token.length - 1) );
3215
+ {
3216
+ return !(token.includes('.')) && this._isNumber( subToken );
3217
+ }
3055
3218
  }
3056
-
3057
- else if(this.highlight == 'WGSL')
3219
+ else if( this.highlight == 'WGSL' )
3058
3220
  {
3059
3221
  if( token.lastChar == 'u' )
3060
- return !(token.includes('.')) && this._isNumber( token.substring(0, token.length - 1) );
3222
+ {
3223
+ return !(token.includes('.')) && this._isNumber( subToken );
3224
+ }
3061
3225
  }
3062
-
3063
- else if(this.highlight == 'CSS')
3226
+ else if( this.highlight == 'CSS' )
3064
3227
  {
3065
3228
  if( token.lastChar == '%' )
3066
- return this._isNumber( token.substring(0, token.length - 1) )
3229
+ {
3230
+ return this._isNumber( subToken )
3231
+ }
3067
3232
  }
3068
-
3069
- return token.length && token != ' ' && !Number.isNaN(+token);
3233
+
3234
+ return token.length && token != ' ' && !Number.isNaN( +token );
3070
3235
  }
3071
3236
 
3072
- _isType( token, prev, next ) {
3073
-
3237
+ _isType( ctxData, lang ) {
3238
+
3239
+ const token = ctxData.token;
3240
+ const prev = ctxData.prev;
3241
+ const next = ctxData.next;
3242
+
3074
3243
  // Common case
3075
- if( this._mustHightlightWord( token, CodeEditor.types ) )
3244
+ if( this._mustHightlightWord( token, CodeEditor.types, lang ) )
3245
+ {
3076
3246
  return true;
3247
+ }
3077
3248
 
3078
3249
  if( this.highlight == 'JavaScript' )
3079
3250
  {
@@ -3085,11 +3256,11 @@ class CodeEditor {
3085
3256
  }
3086
3257
  else if ( this.highlight == 'WGSL' )
3087
3258
  {
3088
- const not_kwd = !this._mustHightlightWord( token, CodeEditor.keywords );
3089
- return (prev == 'struct' && next == '{') ||
3090
- (not_kwd && prev == ':' && next == ';') ||
3091
- ( not_kwd &&
3092
- ( prev == ':' && next == ')' || prev == ':' && next == ',' || prev == '>' && next == '{'
3259
+ const not_kwd = !this._mustHightlightWord( token, CodeEditor.keywords, lang );
3260
+ return (prev == 'struct' && next == '{') ||
3261
+ (not_kwd && prev == ':' && next == ';') ||
3262
+ ( not_kwd &&
3263
+ ( prev == ':' && next == ')' || prev == ':' && next == ',' || prev == '>' && next == '{'
3093
3264
  || prev == '<' && next == ',' || prev == '<' && next == '>' || prev == '>' && token != ';' && !next ));
3094
3265
  }
3095
3266
  }
@@ -3098,13 +3269,13 @@ class CodeEditor {
3098
3269
 
3099
3270
  if( !cursor.selection || (cursor.selection.fromY != cursor.selection.toY) )
3100
3271
  return false;
3101
-
3272
+
3102
3273
  cursor.selection.invertIfNecessary();
3103
3274
 
3104
3275
  // Insert first..
3105
3276
  this.code.lines[ lidx ] = [
3106
- this.code.lines[ lidx ].slice(0, cursor.selection.fromX),
3107
- key,
3277
+ this.code.lines[ lidx ].slice(0, cursor.selection.fromX),
3278
+ key,
3108
3279
  this.code.lines[ lidx ].slice(cursor.selection.fromX)
3109
3280
  ].join('');
3110
3281
 
@@ -3123,14 +3294,14 @@ class CodeEditor {
3123
3294
 
3124
3295
  // Insert the other
3125
3296
  this.code.lines[ lidx ] = [
3126
- this.code.lines[ lidx ].slice(0, cursor.position),
3127
- key,
3297
+ this.code.lines[ lidx ].slice(0, cursor.position),
3298
+ key,
3128
3299
  this.code.lines[ lidx ].slice(cursor.position)
3129
3300
  ].join('');
3130
3301
 
3131
3302
  // Recompute and reposition current selection
3132
-
3133
- cursor.selection.fromX++;
3303
+
3304
+ cursor.selection.fromX++;
3134
3305
  cursor.selection.toX++;
3135
3306
 
3136
3307
  this._processSelection( cursor );
@@ -3142,7 +3313,7 @@ class CodeEditor {
3142
3313
 
3143
3314
  lineUp( cursor, resetLeft ) {
3144
3315
 
3145
- if( this.code.lines[ cursor.line - 1 ] == undefined )
3316
+ if( this.code.lines[ cursor.line - 1 ] == undefined )
3146
3317
  return false;
3147
3318
 
3148
3319
  cursor.line--;
@@ -3153,10 +3324,10 @@ class CodeEditor {
3153
3324
  }
3154
3325
 
3155
3326
  lineDown( cursor, resetLeft ) {
3156
-
3157
- if( this.code.lines[ cursor.line + 1 ] == undefined )
3327
+
3328
+ if( this.code.lines[ cursor.line + 1 ] == undefined )
3158
3329
  return false;
3159
-
3330
+
3160
3331
  cursor.line++;
3161
3332
 
3162
3333
  this.cursorToBottom( cursor, resetLeft );
@@ -3195,8 +3366,8 @@ class CodeEditor {
3195
3366
 
3196
3367
  deleteSelection( cursor ) {
3197
3368
 
3198
- // I think it's not necessary but...
3199
- if( this.disableEdition )
3369
+ // I think it's not necessary but...
3370
+ if( this.disableEdition )
3200
3371
  return;
3201
3372
 
3202
3373
  // Some selections don't depend on mouse up..
@@ -3210,14 +3381,14 @@ class CodeEditor {
3210
3381
  for( let i = 0; i <= cursor.selection.fromY; i++ )
3211
3382
  index += (i == cursor.selection.fromY ? cursor.selection.fromX : this.code.lines[ i ].length);
3212
3383
 
3213
- index += cursor.selection.fromY * separator.length;
3384
+ index += cursor.selection.fromY * separator.length;
3214
3385
 
3215
3386
  const num_chars = cursor.selection.chars + (cursor.selection.toY - cursor.selection.fromY) * separator.length;
3216
3387
  const pre = code.slice( 0, index );
3217
3388
  const post = code.slice( index + num_chars );
3218
3389
 
3219
3390
  this.code.lines = ( pre + post ).split( separator );
3220
-
3391
+
3221
3392
  this.cursorToLine( cursor, cursor.selection.fromY, true );
3222
3393
  this.cursorToPosition( cursor, cursor.selection.fromX );
3223
3394
  this.endSelection( cursor );
@@ -3315,7 +3486,7 @@ class CodeEditor {
3315
3486
  cursor._top = Math.max(cursor._top, 0);
3316
3487
  cursor.style.top = "calc(" + cursor._top + "px)";
3317
3488
  this.restartBlink();
3318
-
3489
+
3319
3490
  if( resetLeft )
3320
3491
  this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
3321
3492
 
@@ -3345,10 +3516,10 @@ class CodeEditor {
3345
3516
 
3346
3517
  cursorToString( cursor, text, reverse ) {
3347
3518
 
3348
- if( !text.length )
3519
+ if( !text.length )
3349
3520
  return;
3350
3521
 
3351
- for( let char of text )
3522
+ for( let char of text )
3352
3523
  reverse ? this.cursorToLeft( char, cursor ) : this.cursorToRight( char, cursor );
3353
3524
  }
3354
3525
 
@@ -3436,7 +3607,7 @@ class CodeEditor {
3436
3607
  }
3437
3608
 
3438
3609
  resetCursorPos( flag, cursor ) {
3439
-
3610
+
3440
3611
  cursor = cursor ?? this._getCurrentCursor();
3441
3612
 
3442
3613
  if( flag & CodeEditor.CURSOR_LEFT )
@@ -3455,14 +3626,14 @@ class CodeEditor {
3455
3626
  }
3456
3627
 
3457
3628
  _addSpaceTabs( cursor, n ) {
3458
-
3629
+
3459
3630
  for( var i = 0; i < n; ++i ) {
3460
3631
  this.actions[ 'Tab' ].callback( cursor.line, cursor, null );
3461
3632
  }
3462
3633
  }
3463
3634
 
3464
3635
  _addSpaces( n ) {
3465
-
3636
+
3466
3637
  for( var i = 0; i < n; ++i ) {
3467
3638
  this.root.dispatchEvent( new CustomEvent( 'keydown', { 'detail': {
3468
3639
  skip_undo: true,
@@ -3509,33 +3680,33 @@ class CodeEditor {
3509
3680
  }
3510
3681
 
3511
3682
  getScrollLeft() {
3512
-
3683
+
3513
3684
  if( !this.codeScroller ) return 0;
3514
3685
  return this.codeScroller.scrollLeft;
3515
3686
  }
3516
3687
 
3517
3688
  getScrollTop() {
3518
-
3689
+
3519
3690
  if( !this.codeScroller ) return 0;
3520
3691
  return this.codeScroller.scrollTop;
3521
3692
  }
3522
3693
 
3523
3694
  setScrollLeft( value ) {
3524
-
3695
+
3525
3696
  if( !this.codeScroller ) return;
3526
3697
  this.codeScroller.scrollLeft = value;
3527
3698
  this.setScrollBarValue( 'horizontal', 0 );
3528
3699
  }
3529
3700
 
3530
3701
  setScrollTop( value ) {
3531
-
3702
+
3532
3703
  if( !this.codeScroller ) return;
3533
3704
  this.codeScroller.scrollTop = value;
3534
3705
  this.setScrollBarValue( 'vertical' );
3535
3706
  }
3536
3707
 
3537
3708
  resize( pMaxLength ) {
3538
-
3709
+
3539
3710
  setTimeout( () => {
3540
3711
 
3541
3712
  // Update max viewport
@@ -3593,7 +3764,7 @@ class CodeEditor {
3593
3764
  {
3594
3765
  const scrollBarHeight = this.vScrollbar.thumb.parentElement.offsetHeight;
3595
3766
  const scrollThumbHeight = this.vScrollbar.thumb.offsetHeight;
3596
-
3767
+
3597
3768
  const scrollHeight = this.codeScroller.scrollHeight - this.codeScroller.clientHeight;
3598
3769
  const currentScroll = this.codeScroller.scrollTop;
3599
3770
 
@@ -3632,13 +3803,13 @@ class CodeEditor {
3632
3803
  const scrollWidth = this.codeScroller.scrollWidth - this.codeScroller.clientWidth;
3633
3804
  const currentScroll = (this.hScrollbar.thumb._left * scrollWidth) / ( scrollBarWidth - scrollThumbWidth );
3634
3805
  this.codeScroller.scrollLeft = currentScroll;
3635
-
3806
+
3636
3807
 
3637
3808
  this._discardScroll = true;
3638
3809
  }
3639
3810
 
3640
3811
  updateVerticalScrollFromScrollBar( value ) {
3641
-
3812
+
3642
3813
  value = this.vScrollbar.thumb._top - value;
3643
3814
 
3644
3815
  // Move scrollbar thumb
@@ -3657,12 +3828,12 @@ class CodeEditor {
3657
3828
  }
3658
3829
 
3659
3830
  getCharAtPos( cursor, offset = 0 ) {
3660
-
3831
+
3661
3832
  return this.code.lines[ cursor.line ][ cursor.position + offset ];
3662
3833
  }
3663
3834
 
3664
3835
  getWordAtPos( cursor, offset = 0 ) {
3665
-
3836
+
3666
3837
  const col = cursor.line;
3667
3838
  const words = this.code.lines[ col ];
3668
3839
 
@@ -3706,13 +3877,13 @@ class CodeEditor {
3706
3877
  from--;
3707
3878
  word = words.substring( from, to );
3708
3879
  }
3709
- }
3710
-
3880
+ }
3881
+
3711
3882
  return [ word, from, to ];
3712
3883
  }
3713
3884
 
3714
3885
  _measureChar( char = "a", use_floating = false, get_bb = false ) {
3715
-
3886
+
3716
3887
  var line = document.createElement( "pre" );
3717
3888
  var text = document.createElement( "span" );
3718
3889
  line.appendChild( text );
@@ -3776,7 +3947,7 @@ class CodeEditor {
3776
3947
  }
3777
3948
 
3778
3949
  showAutoCompleteBox( key, cursor ) {
3779
-
3950
+
3780
3951
  if( !cursor.isMain )
3781
3952
  return;
3782
3953
 
@@ -3791,8 +3962,8 @@ class CodeEditor {
3791
3962
  let suggestions = [];
3792
3963
 
3793
3964
  // Add language special keys...
3794
- suggestions = suggestions.concat(
3795
- Object.keys( CodeEditor.builtin[ this.highlight ] ?? {} ),
3965
+ suggestions = suggestions.concat(
3966
+ Object.keys( CodeEditor.builtIn[ this.highlight ] ?? {} ),
3796
3967
  Object.keys( CodeEditor.keywords[ this.highlight ] ?? {} ),
3797
3968
  Object.keys( CodeEditor.statementsAndDeclarations[ this.highlight ] ?? {} ),
3798
3969
  Object.keys( CodeEditor.types[ this.highlight ] ?? {} ),
@@ -3802,7 +3973,7 @@ class CodeEditor {
3802
3973
  // Add words in current tab plus remove current word
3803
3974
  // suggestions = suggestions.concat( Object.keys(this.code.tokens).filter( a => a != word ) );
3804
3975
 
3805
- // Remove 1/2 char words and duplicates...
3976
+ // Remove 1/2 char words and duplicates...
3806
3977
  suggestions = suggestions.filter( (value, index) => value.length > 2 && suggestions.indexOf(value) === index );
3807
3978
 
3808
3979
  // Order...
@@ -3817,7 +3988,7 @@ class CodeEditor {
3817
3988
  this.autocomplete.appendChild( pre );
3818
3989
 
3819
3990
  var icon = document.createElement( 'a' );
3820
-
3991
+
3821
3992
  if( this._mustHightlightWord( s, CodeEditor.utils ) )
3822
3993
  icon.className = "fa fa-cube";
3823
3994
  else if( this._mustHightlightWord( s, CodeEditor.types ) )
@@ -3829,7 +4000,7 @@ class CodeEditor {
3829
4000
 
3830
4001
  pre.addEventListener( 'click', () => {
3831
4002
  this.autoCompleteWord( s );
3832
- } );
4003
+ } );
3833
4004
 
3834
4005
  // Highlight the written part
3835
4006
  const index = s.toLowerCase().indexOf( word.toLowerCase() );
@@ -3875,7 +4046,7 @@ class CodeEditor {
3875
4046
  }
3876
4047
 
3877
4048
  autoCompleteWord( suggestion ) {
3878
-
4049
+
3879
4050
  if( !this.isAutoCompleteActive )
3880
4051
  return;
3881
4052
 
@@ -3902,7 +4073,7 @@ class CodeEditor {
3902
4073
  }
3903
4074
 
3904
4075
  _getSelectedAutoComplete() {
3905
-
4076
+
3906
4077
  if( !this.isAutoCompleteActive )
3907
4078
  return;
3908
4079
 
@@ -3942,7 +4113,7 @@ class CodeEditor {
3942
4113
  }
3943
4114
 
3944
4115
  showSearchBox( clear ) {
3945
-
4116
+
3946
4117
  this.hideSearchLineBox();
3947
4118
 
3948
4119
  this.searchbox.classList.add( 'opened' );
@@ -4011,7 +4182,7 @@ class CodeEditor {
4011
4182
  }
4012
4183
 
4013
4184
  const getIndex = l => {
4014
-
4185
+
4015
4186
  var string = this.code.lines[ l ];
4016
4187
 
4017
4188
  if( reverse )
@@ -4065,10 +4236,10 @@ class CodeEditor {
4065
4236
  };
4066
4237
  return;
4067
4238
  }
4068
-
4239
+
4069
4240
  /*
4070
4241
  Position idx is computed from last pos, which could be in same line,
4071
- so we search in the substring (first_ocurrence, end). That's why we
4242
+ so we search in the substring (first_ocurrence, end). That's why we
4072
4243
  have to add the length of the substring (0, first_ocurrence)
4073
4244
  */
4074
4245
 
@@ -4081,9 +4252,9 @@ class CodeEditor {
4081
4252
 
4082
4253
  this._lastTextFound = text;
4083
4254
 
4084
- this.codeScroller.scrollTo(
4085
- Math.max( char * this.charWidth - this.codeScroller.clientWidth, 0 ),
4086
- Math.max( line - 10, 0 ) * this.lineHeight
4255
+ this.codeScroller.scrollTo(
4256
+ Math.max( char * this.charWidth - this.codeScroller.clientWidth, 0 ),
4257
+ Math.max( line - 10, 0 ) * this.lineHeight
4087
4258
  );
4088
4259
 
4089
4260
  if( callback )
@@ -4107,7 +4278,7 @@ class CodeEditor {
4107
4278
  }
4108
4279
 
4109
4280
  showSearchLineBox() {
4110
-
4281
+
4111
4282
  this.hideSearchBox();
4112
4283
 
4113
4284
  this.searchlinebox.classList.add( 'opened' );
@@ -4271,7 +4442,7 @@ class CodeEditor {
4271
4442
 
4272
4443
  delete this._currentLineString;
4273
4444
  delete this._currentLineNumber;
4274
- delete this._buildingString;
4445
+ delete this._buildingString;
4275
4446
  delete this._pendingString;
4276
4447
  delete this._buildingBlockComment;
4277
4448
  delete this._markdownHeader;
@@ -4283,8 +4454,13 @@ CodeEditor.keywords = {
4283
4454
 
4284
4455
  'JavaScript': ['var', 'let', 'const', 'this', 'in', 'of', 'true', 'false', 'new', 'function', 'NaN', 'static', 'class', 'constructor', 'null', 'typeof', 'debugger', 'abstract',
4285
4456
  'arguments', 'extends', 'instanceof', 'Infinity'],
4286
- 'C++': ['int', 'float', 'double', 'bool', 'char', 'wchar_t', 'const', 'static_cast', 'dynamic_cast', 'new', 'delete', 'void', 'true', 'false', 'auto', 'struct', 'typedef', 'nullptr',
4287
- 'NULL', 'unsigned', 'namespace'],
4457
+ 'C': ['int', 'float', 'double', 'long', 'short', 'char', 'const', 'void', 'true', 'false', 'auto', 'struct', 'typedef', 'signed', 'volatile', 'unsigned', 'static', 'extern', 'enum', 'register',
4458
+ 'union'],
4459
+ 'C++': ['int', 'float', 'double', 'bool', 'long', 'short', 'char', 'wchar_t', 'const', 'static_cast', 'dynamic_cast', 'new', 'delete', 'void', 'true', 'false', 'auto', 'struct', 'typedef', 'nullptr',
4460
+ 'NULL', 'signed', 'unsigned', 'namespace', 'enum', 'extern', 'union', 'sizeof', 'static'],
4461
+ 'CMake': ['cmake_minimum_required', 'set', 'not', 'if', 'endif', 'exists', 'string', 'strequal', 'add_definitions', 'macro', 'endmacro', 'file', 'list', 'source_group', 'add_executable',
4462
+ 'target_include_directories', 'set_target_properties', 'set_property', 'add_compile_options', 'add_link_options', 'include_directories', 'add_library', 'target_link_libraries',
4463
+ 'target_link_options', 'add_subdirectory', 'add_compile_definitions', 'project', 'cache'],
4288
4464
  'JSON': ['true', 'false'],
4289
4465
  'GLSL': ['true', 'false', 'function', 'int', 'float', 'vec2', 'vec3', 'vec4', 'mat2x2', 'mat3x3', 'mat4x4', 'struct'],
4290
4466
  'CSS': ['body', 'html', 'canvas', 'div', 'input', 'span', '.'],
@@ -4292,10 +4468,10 @@ CodeEditor.keywords = {
4292
4468
  'sampler', 'sampler_comparison', 'texture_depth_2d', 'texture_depth_2d_array', 'texture_depth_cube', 'texture_depth_cube_array', 'texture_depth_multisampled_2d',
4293
4469
  'texture_external', 'texture_1d', 'texture_2d', 'texture_2d_array', 'texture_3d', 'texture_cube', 'texture_cube_array', 'texture_storage_1d', 'texture_storage_2d',
4294
4470
  'texture_storage_2d_array', 'texture_storage_3d', 'vec2u', 'vec3u', 'vec4u'],
4295
- 'Rust': ['as', 'const', 'crate', 'enum', 'extern', 'false', 'fn', 'impl', 'in', 'let', 'mod', 'move', 'mut', 'pub', 'ref', 'self', 'Self', 'static', 'struct', 'super', 'trait', 'true',
4471
+ 'Rust': ['as', 'const', 'crate', 'enum', 'extern', 'false', 'fn', 'impl', 'in', 'let', 'mod', 'move', 'mut', 'pub', 'ref', 'self', 'Self', 'static', 'struct', 'super', 'trait', 'true',
4296
4472
  'type', 'unsafe', 'use', 'where', 'abstract', 'become', 'box', 'final', 'macro', 'override', 'priv', 'typeof', 'unsized', 'virtual'],
4297
4473
  'Python': ['False', 'def', 'None', 'True', 'in', 'is', 'and', 'lambda', 'nonlocal', 'not', 'or'],
4298
- 'Batch': ['set', 'SET', 'echo', 'ECHO', 'off', 'OFF', 'del', 'DEL', 'defined', 'DEFINED', 'setlocal', 'SETLOCAL', 'enabledelayedexpansion', 'ENABLEDELAYEDEXPANSION', 'driverquery',
4474
+ 'Batch': ['set', 'SET', 'echo', 'ECHO', 'off', 'OFF', 'del', 'DEL', 'defined', 'DEFINED', 'setlocal', 'SETLOCAL', 'enabledelayedexpansion', 'ENABLEDELAYEDEXPANSION', 'driverquery',
4299
4475
  'DRIVERQUERY', 'print', 'PRINT'],
4300
4476
  'HTML': ['html', 'meta', 'title', 'link', 'script', 'body', 'DOCTYPE', 'head', 'br', 'i', 'a', 'li', 'img', 'tr', 'td', 'h1', 'h2', 'h3', 'h4', 'h5'],
4301
4477
  'Markdown': ['br', 'i', 'a', 'li', 'img', 'table', 'title', 'tr', 'td', 'h1', 'h2', 'h3', 'h4', 'h5'],
@@ -4307,9 +4483,9 @@ CodeEditor.utils = { // These ones don't have hightlight, used as suggestions to
4307
4483
  'bind', 'prototype', 'length', 'assign', 'entries', 'values', 'concat', 'substring', 'substr', 'splice', 'slice', 'buffer', 'appendChild', 'createElement', 'prompt',
4308
4484
  'alert'],
4309
4485
  'WGSL': ['textureSample'],
4310
- 'Python': ['abs', 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'delattr', 'dict', 'dir', 'divmod',
4486
+ 'Python': ['abs', 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'delattr', 'dict', 'dir', 'divmod',
4311
4487
  'enumerate', 'eval', 'exec', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance',
4312
- 'issubclass', 'iter', 'len', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'range', 'repr',
4488
+ 'issubclass', 'iter', 'len', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'range', 'repr',
4313
4489
  'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']
4314
4490
  };
4315
4491
 
@@ -4318,14 +4494,14 @@ CodeEditor.types = {
4318
4494
  'JavaScript': ['Object', 'String', 'Function', 'Boolean', 'Symbol', 'Error', 'Number', 'TextEncoder', 'TextDecoder', 'Array', 'ArrayBuffer', 'InputEvent', 'MouseEvent',
4319
4495
  'Int8Array', 'Int16Array', 'Int32Array', 'Float32Array', 'Float64Array', 'Element'],
4320
4496
  'Rust': ['u128'],
4321
- 'Python': ['int', 'type', 'float', 'map', 'list', 'ArithmeticError', 'AssertionError', 'AttributeError', 'Exception', 'EOFError', 'FloatingPointError', 'GeneratorExit',
4497
+ 'Python': ['int', 'type', 'float', 'map', 'list', 'ArithmeticError', 'AssertionError', 'AttributeError', 'Exception', 'EOFError', 'FloatingPointError', 'GeneratorExit',
4322
4498
  'ImportError', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'NotImplementedError', 'OSError',
4323
- 'OverflowError', 'ReferenceError', 'RuntimeError', 'StopIteration', 'SyntaxError', 'TabError', 'SystemError', 'SystemExit', 'TypeError', 'UnboundLocalError',
4499
+ 'OverflowError', 'ReferenceError', 'RuntimeError', 'StopIteration', 'SyntaxError', 'TabError', 'SystemError', 'SystemExit', 'TypeError', 'UnboundLocalError',
4324
4500
  'UnicodeError', 'UnicodeEncodeError', 'UnicodeDecodeError', 'UnicodeTranslateError', 'ValueError', 'ZeroDivisionError'],
4325
4501
  'C++': ['uint8_t', 'uint16_t', 'uint32_t']
4326
4502
  };
4327
4503
 
4328
- CodeEditor.builtin = {
4504
+ CodeEditor.builtIn = {
4329
4505
 
4330
4506
  'JavaScript': ['document', 'console', 'window', 'navigator', 'performance'],
4331
4507
  'CSS': ['*', '!important'],
@@ -4338,11 +4514,12 @@ CodeEditor.statementsAndDeclarations = {
4338
4514
 
4339
4515
  'JavaScript': ['for', 'if', 'else', 'case', 'switch', 'return', 'while', 'continue', 'break', 'do', 'import', 'from', 'throw', 'async', 'try', 'catch', 'await'],
4340
4516
  'CSS': ['@', 'import'],
4341
- 'C++': ['std', 'for', 'if', 'else', 'return', 'continue', 'break', 'case', 'switch', 'while', 'using', 'glm', 'spdlog'],
4517
+ 'C': ['for', 'if', 'else', 'return', 'continue', 'break', 'case', 'switch', 'while', 'using', 'default', 'goto', 'do'],
4518
+ 'C++': ['std', 'for', 'if', 'else', 'return', 'continue', 'break', 'case', 'switch', 'while', 'using', 'glm', 'spdlog', 'default'],
4342
4519
  'GLSL': ['for', 'if', 'else', 'return', 'continue', 'break'],
4343
4520
  'WGSL': ['const','for', 'if', 'else', 'return', 'continue', 'break', 'storage', 'read', 'read_write', 'uniform', 'function', 'workgroup'],
4344
4521
  'Rust': ['break', 'else', 'continue', 'for', 'if', 'loop', 'match', 'return', 'while', 'do', 'yield'],
4345
- 'Python': ['if', 'raise', 'del', 'import', 'return', 'elif', 'try', 'else', 'while', 'as', 'except', 'with', 'assert', 'finally', 'yield', 'break', 'for', 'class', 'continue',
4522
+ 'Python': ['if', 'raise', 'del', 'import', 'return', 'elif', 'try', 'else', 'while', 'as', 'except', 'with', 'assert', 'finally', 'yield', 'break', 'for', 'class', 'continue',
4346
4523
  'global', 'pass', 'from'],
4347
4524
  'Batch': ['if', 'IF', 'for', 'FOR', 'in', 'IN', 'do', 'DO', 'call', 'CALL', 'goto', 'GOTO', 'exit', 'EXIT']
4348
4525
  };
@@ -4350,7 +4527,9 @@ CodeEditor.statementsAndDeclarations = {
4350
4527
  CodeEditor.symbols = {
4351
4528
 
4352
4529
  'JavaScript': ['<', '>', '[', ']', '{', '}', '(', ')', ';', '=', '|', '||', '&', '&&', '?', '??'],
4530
+ 'C': ['<', '>', '[', ']', '{', '}', '(', ')', ';', '=', '|', '||', '&', '&&', '?', '*', '-', '+'],
4353
4531
  'C++': ['<', '>', '[', ']', '{', '}', '(', ')', ';', '=', '|', '||', '&', '&&', '?', '::', '*', '-', '+'],
4532
+ 'CMake': ['{', '}'],
4354
4533
  'JSON': ['[', ']', '{', '}', '(', ')'],
4355
4534
  'GLSL': ['[', ']', '{', '}', '(', ')'],
4356
4535
  'WGSL': ['[', ']', '{', '}', '(', ')', '->'],
@@ -4358,7 +4537,8 @@ CodeEditor.symbols = {
4358
4537
  'Rust': ['<', '>', '[', ']', '(', ')', '='],
4359
4538
  'Python': ['<', '>', '[', ']', '(', ')', '='],
4360
4539
  'Batch': ['[', ']', '(', ')', '%'],
4361
- 'HTML': ['<', '>', '/']
4540
+ 'HTML': ['<', '>', '/'],
4541
+ 'XML': ['<', '>', '/']
4362
4542
  };
4363
4543
 
4364
4544
  LX.CodeEditor = CodeEditor;