lexgui 0.7.3 → 0.7.5

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.
@@ -151,7 +151,7 @@ class ScrollBar {
151
151
  this.type = type;
152
152
 
153
153
  this.root = document.createElement( 'div' );
154
- this.root.className = "lexcodescrollbar";
154
+ this.root.className = "lexcodescrollbar hidden";
155
155
 
156
156
  if( type & ScrollBar.SCROLLBAR_VERTICAL )
157
157
  this.root.classList.add( 'vertical' );
@@ -338,7 +338,7 @@ class CodeEditor {
338
338
 
339
339
  let panel = new LX.Panel();
340
340
 
341
- panel.addTitle( "EXPLORER" );
341
+ panel.addTitle( options.explorerName ?? "EXPLORER" );
342
342
 
343
343
  let sceneData = {
344
344
  'id': 'WORKSPACE',
@@ -403,11 +403,11 @@ class CodeEditor {
403
403
 
404
404
  if( !this.disableEdition )
405
405
  {
406
- this.tabs.root.addEventListener( 'dblclick', (e) => {
406
+ this.tabs.root.parentElement.addEventListener( 'dblclick', (e) => {
407
407
  if( options.allowAddScripts ?? true )
408
408
  {
409
409
  e.preventDefault();
410
- this.addTab( "unnamed.js", true );
410
+ this._onCreateNewFile();
411
411
  }
412
412
  } );
413
413
  }
@@ -966,11 +966,14 @@ class CodeEditor {
966
966
  var spaces = firstNonspaceIndex( this.code.lines[ ln ]);
967
967
  var tabs = Math.floor( spaces / this.tabSpaces );
968
968
 
969
- if( _c0 == '{' && _c1 == '}' ) {
969
+ if( _c0 == '{' && _c1 == '}' )
970
+ {
970
971
  this.code.lines.splice( cursor.line, 0, "" );
971
972
  this._addSpaceTabs( cursor, tabs + 1 );
972
973
  this.code.lines[ cursor.line + 1 ] = " ".repeat(spaces) + this.code.lines[ cursor.line + 1 ];
973
- } else {
974
+ }
975
+ else
976
+ {
974
977
  this._addSpaceTabs( cursor, tabs );
975
978
  }
976
979
 
@@ -1206,9 +1209,10 @@ class CodeEditor {
1206
1209
  this.openedTabs = { };
1207
1210
 
1208
1211
  const onLoadAll = () => {
1212
+
1209
1213
  // Create inspector panel when the initial state is complete
1210
1214
  // and we have at least 1 tab opened
1211
- this.statusPanel = this._createStatusPanel();
1215
+ this.statusPanel = this._createStatusPanel( options );
1212
1216
  if( this.statusPanel )
1213
1217
  {
1214
1218
  area.attach( this.statusPanel );
@@ -1228,6 +1232,7 @@ class CodeEditor {
1228
1232
  const s = getComputedStyle( r );
1229
1233
  this.fontSize = parseInt( s.getPropertyValue( "--code-editor-font-size" ) );
1230
1234
  this.charWidth = this._measureChar( "a", true );
1235
+ this.processLines();
1231
1236
  }
1232
1237
 
1233
1238
  LX.emit( "@font-size", this.fontSize );
@@ -1240,10 +1245,10 @@ class CodeEditor {
1240
1245
 
1241
1246
  this.gutter.style.marginTop = `${ this._verticalTopOffset }px`;
1242
1247
  this.gutter.style.height = `calc(100% - ${ this._fullVerticalOffset }px)`;
1243
- this.vScrollbar.root.style.marginTop = `${ this.tabs?.root.getBoundingClientRect().height ?? 0 }px`;
1248
+ this.vScrollbar.root.style.marginTop = `${ this._verticalTopOffset }px`;
1244
1249
  this.vScrollbar.root.style.height = `calc(100% - ${ this._fullVerticalOffset }px)`;
1245
1250
  this.hScrollbar.root.style.bottom = `${ this._verticalBottomOffset }px`;
1246
- this.codeArea.root.style.height = `calc(100% - ${ this._verticalBottomOffset }px)`;
1251
+ this.codeArea.root.style.height = `calc(100% - ${ this._fullVerticalOffset }px)`;
1247
1252
  }, 50 );
1248
1253
 
1249
1254
  });
@@ -1253,7 +1258,9 @@ class CodeEditor {
1253
1258
 
1254
1259
  if( options.allowAddScripts ?? true )
1255
1260
  {
1256
- this.addTab("+", false, "New File");
1261
+ this.onCreateFile = options.onCreateFile;
1262
+
1263
+ this.addTab( "+", false, "Create file" );
1257
1264
  }
1258
1265
 
1259
1266
  if( options.files )
@@ -1264,11 +1271,16 @@ class CodeEditor {
1264
1271
 
1265
1272
  for( let url of options.files )
1266
1273
  {
1267
- this.loadFile( url, { callback: () => {
1274
+ this.loadFile( url, { callback: ( name, text ) => {
1268
1275
  filesLoaded++;
1269
1276
  if( filesLoaded == numFiles )
1270
1277
  {
1271
1278
  onLoadAll();
1279
+
1280
+ if( options.onFilesLoaded )
1281
+ {
1282
+ options.onFilesLoaded( this, numFiles );
1283
+ }
1272
1284
  }
1273
1285
  }});
1274
1286
  }
@@ -1322,12 +1334,15 @@ class CodeEditor {
1322
1334
 
1323
1335
  this.cursorToLine( cursor, newLines.length ); // Already substracted 1
1324
1336
  this.cursorToPosition( cursor, lastLine.length );
1325
- this.processLines();
1337
+
1338
+ this.mustProcessLines = true;
1326
1339
 
1327
1340
  if( lang )
1328
1341
  {
1329
1342
  this._changeLanguage( lang );
1330
1343
  }
1344
+
1345
+ this._processLinesIfNecessary();
1331
1346
  }
1332
1347
 
1333
1348
  appendText( text, cursor ) {
@@ -1401,7 +1416,7 @@ class CodeEditor {
1401
1416
  } );
1402
1417
  }
1403
1418
 
1404
- loadFile( file, options = {} ) {
1419
+ async loadFile( file, options = {} ) {
1405
1420
 
1406
1421
  const _innerAddTab = ( text, name, title ) => {
1407
1422
 
@@ -1429,10 +1444,6 @@ class CodeEditor {
1429
1444
  this.addExplorerItem( { id: name, skipVisibility: true, icon: this._getFileIcon( name, ext ) } );
1430
1445
  this.explorer.innerTree.frefresh( name );
1431
1446
  }
1432
- else
1433
- {
1434
-
1435
- }
1436
1447
  }
1437
1448
  else
1438
1449
  {
@@ -1448,17 +1459,19 @@ class CodeEditor {
1448
1459
 
1449
1460
  if( options.callback )
1450
1461
  {
1451
- options.callback( text );
1462
+ options.callback( name, text );
1452
1463
  }
1453
1464
  };
1454
1465
 
1455
1466
  if( file.constructor == String )
1456
1467
  {
1457
1468
  let filename = file;
1469
+
1458
1470
  LX.request({ url: filename, success: text => {
1459
1471
  const name = filename.substring(filename.lastIndexOf( '/' ) + 1);
1460
1472
  _innerAddTab( text, name, filename );
1461
1473
  } });
1474
+
1462
1475
  }
1463
1476
  else // File Blob
1464
1477
  {
@@ -1598,7 +1611,8 @@ class CodeEditor {
1598
1611
  }
1599
1612
 
1600
1613
  this._updateDataInfoPanel( "@highlight", lang );
1601
- this.processLines();
1614
+
1615
+ this.mustProcessLines = true;
1602
1616
 
1603
1617
  const ext = langExtension ?? CodeEditor.languages[ lang ].ext;
1604
1618
  const icon = this._getFileIcon( null, ext );
@@ -1663,7 +1677,7 @@ class CodeEditor {
1663
1677
  this._changeLanguage( 'Plain Text' );
1664
1678
  }
1665
1679
 
1666
- _createStatusPanel() {
1680
+ _createStatusPanel( options ) {
1667
1681
 
1668
1682
  if( this.skipInfo )
1669
1683
  {
@@ -1688,7 +1702,7 @@ class CodeEditor {
1688
1702
 
1689
1703
  let rightStatusPanel = new LX.Panel( { height: "auto" } );
1690
1704
  rightStatusPanel.sameLine();
1691
- rightStatusPanel.addLabel( this.code.title, { id: "EditorFilenameStatusComponent", fit: true, signal: "@tab-name" });
1705
+ rightStatusPanel.addLabel( this.code?.title ?? "", { id: "EditorFilenameStatusComponent", fit: true, signal: "@tab-name" });
1692
1706
  rightStatusPanel.addButton( null, "Ln 1, Col 1", this.showSearchLineBox.bind( this ), { id: "EditorSelectionStatusComponent", fit: true, signal: "@cursor-data" });
1693
1707
  rightStatusPanel.addButton( null, "Spaces: " + this.tabSpaces, ( value, event ) => {
1694
1708
  LX.addContextMenu( "Spaces", event, m => {
@@ -1715,13 +1729,24 @@ class CodeEditor {
1715
1729
  panel.attach( rightStatusPanel.root );
1716
1730
 
1717
1731
  const itemVisibilityMap = {
1718
- "Font Size Zoom": true,
1719
- "Editor Filename": true,
1720
- "Editor Selection": true,
1721
- "Editor Indentation": true,
1722
- "Editor Language": true,
1732
+ "Font Size Zoom": options.statusShowFontSizeZoom ?? true,
1733
+ "Editor Filename": options.statusShowEditorFilename ?? true,
1734
+ "Editor Selection": options.statusShowEditorSelection ?? true,
1735
+ "Editor Indentation": options.statusShowEditorIndentation ?? true,
1736
+ "Editor Language": options.statusShowEditorLanguage ?? true,
1723
1737
  };
1724
1738
 
1739
+ const _setVisibility = ( itemName ) => {
1740
+ const b = panel.root.querySelector( `#${ itemName.replaceAll( " ", "" ) }StatusComponent` );
1741
+ console.assert( b, `${ itemName } has no status button!` );
1742
+ b.classList.toggle( "hidden", !itemVisibilityMap[ itemName ] );
1743
+ }
1744
+
1745
+ for( const [ itemName, v ] of Object.entries( itemVisibilityMap ) )
1746
+ {
1747
+ _setVisibility( itemName );
1748
+ }
1749
+
1725
1750
  panel.root.addEventListener( "contextmenu", (e) => {
1726
1751
 
1727
1752
  if( e.target && ( e.target.classList.contains( "lexpanel" ) || e.target.classList.contains( "lexinlinecomponents" ) ) )
@@ -1735,9 +1760,7 @@ class CodeEditor {
1735
1760
  icon: "Check",
1736
1761
  callback: () => {
1737
1762
  itemVisibilityMap[ itemName ] = !itemVisibilityMap[ itemName ];
1738
- const b = panel.root.querySelector( `#${ itemName.replaceAll( " ", "" ) }StatusComponent` );
1739
- console.assert( b, `${ itemName } has no status button!` );
1740
- b.classList.toggle( "hidden", !itemVisibilityMap[ itemName ] );
1763
+ _setVisibility( itemName );
1741
1764
  }
1742
1765
  }
1743
1766
  if( !itemVisibilityMap[ itemName ] ) delete item.icon;
@@ -1808,10 +1831,23 @@ class CodeEditor {
1808
1831
 
1809
1832
  this.processFocus( false );
1810
1833
 
1811
- LX.addContextMenu( null, e, m => {
1812
- m.add( "Create", this.addTab.bind( this, "unnamed.js", true, "", { language: "JavaScript" } ) );
1813
- m.add( "Load", this.loadTabFromFile.bind( this, "unnamed.js", true ) );
1814
- });
1834
+ new LX.DropdownMenu( e.target, [
1835
+ { name: "Create file", icon: "FilePlus", callback: this._onCreateNewFile.bind( this ) },
1836
+ { name: "Load file", icon: "FileUp", callback: this.loadTabFromFile.bind( this ) },
1837
+ ], { side: "bottom", align: "start" });
1838
+ }
1839
+
1840
+ _onCreateNewFile() {
1841
+
1842
+ let options = {};
1843
+
1844
+ if( this.onCreateFile )
1845
+ {
1846
+ options = this.onCreateFile( this );
1847
+ }
1848
+
1849
+ const name = options.name ?? "unnamed.js";
1850
+ this.addTab( name, true, name, { language: options.language ?? "JavaScript" } );
1815
1851
  }
1816
1852
 
1817
1853
  _onSelectTab( isNewTabButton, event, name ) {
@@ -1848,18 +1884,38 @@ class CodeEditor {
1848
1884
  {
1849
1885
  this._changeLanguageFromExtension( LX.getExtension( name ) );
1850
1886
  }
1887
+
1888
+ this.processLines();
1851
1889
  }
1852
1890
 
1853
1891
  _onContextMenuTab( isNewTabButton, event, name, ) {
1854
1892
 
1855
1893
  if( isNewTabButton )
1894
+ {
1856
1895
  return;
1896
+ }
1857
1897
 
1858
- LX.addContextMenu( null, event, m => {
1859
- m.add( "Close", () => { this.tabs.delete( name ) } );
1860
- // m.add( "" );
1861
- // m.add( "Rename", () => { console.warn( "TODO" )} );
1862
- });
1898
+ new LX.DropdownMenu( event.target, [
1899
+ { name: "Close", kbd: "MWB", callback: () => { this.tabs.delete( name ) } },
1900
+ { name: "Close Others", callback: () => {
1901
+ for( const [ key, data ] of Object.entries( this.tabs.tabs ) )
1902
+ {
1903
+ if( key === '+' || key === name ) continue;
1904
+ this.tabs.delete( key )
1905
+ }
1906
+ } },
1907
+ { name: "Close All", callback: () => {
1908
+ for( const [ key, data ] of Object.entries( this.tabs.tabs ) )
1909
+ {
1910
+ if( key === '+' ) continue;
1911
+ this.tabs.delete( key )
1912
+ }
1913
+ } },
1914
+ null,
1915
+ { name: "Copy Path", icon: "Copy", callback: () => {
1916
+ navigator.clipboard.writeText( this.openedTabs[ name ].path ?? "" );
1917
+ } }
1918
+ ], { side: "bottom", align: "start", event });
1863
1919
  }
1864
1920
 
1865
1921
  addTab( name, selected, title, options = {} ) {
@@ -1879,20 +1935,25 @@ class CodeEditor {
1879
1935
 
1880
1936
  // Create code content
1881
1937
  let code = document.createElement( 'div' );
1882
- code.className = 'code';
1883
- code.lines = [ "" ];
1884
- code.language = options.language ?? "Plain Text";
1885
- code.cursorState = {};
1886
- code.undoSteps = [];
1887
- code.redoSteps = [];
1888
- code.lineScopes = [];
1889
- code.lineSymbols = [];
1890
- code.symbolsTable = new Map();
1891
- code.tabName = name;
1892
- code.title = title ?? name;
1893
- code.tokens = {};
1894
- code.style.left = "0px";
1895
- code.style.top = "0px";
1938
+ Object.assign( code, {
1939
+ path: options.path ?? "",
1940
+ className: 'code',
1941
+ lines: [ "" ],
1942
+ language: options.language ?? "Plain Text",
1943
+ cursorState: {},
1944
+ undoSteps: [],
1945
+ redoSteps: [],
1946
+ lineScopes: [],
1947
+ lineSymbols: [],
1948
+ lineSignatures: [],
1949
+ symbolsTable: new Map(),
1950
+ tabName: name,
1951
+ title: title ?? name,
1952
+ tokens: {}
1953
+ } );
1954
+
1955
+ code.style.left = "0px",
1956
+ code.style.top = "0px",
1896
1957
 
1897
1958
  code.addEventListener( 'dragenter', function(e) {
1898
1959
  e.preventDefault();
@@ -1942,15 +2003,18 @@ class CodeEditor {
1942
2003
  {
1943
2004
  this.code = code;
1944
2005
  this.resetCursorPos( CodeEditor.CURSOR_LEFT_TOP );
1945
- this.processLines();
2006
+ this.mustProcessLines = true;
1946
2007
  }
1947
2008
 
1948
2009
  if( options.language )
1949
2010
  {
1950
2011
  code.languageOverride = options.language;
1951
2012
  this._changeLanguage( code.languageOverride );
2013
+ this.mustProcessLines = true;
1952
2014
  }
1953
2015
 
2016
+ this._processLinesIfNecessary();
2017
+
1954
2018
  this._updateDataInfoPanel( "@tab-name", name );
1955
2019
 
1956
2020
  // Bc it could be overrided..
@@ -1993,6 +2057,8 @@ class CodeEditor {
1993
2057
  delete this._tabStorage[ name ];
1994
2058
  }
1995
2059
 
2060
+ this._processLinesIfNecessary();
2061
+
1996
2062
  return;
1997
2063
  }
1998
2064
 
@@ -2005,9 +2071,12 @@ class CodeEditor {
2005
2071
 
2006
2072
  // Select as current...
2007
2073
  this.code = code;
2074
+ this.mustProcessLines = true;
2075
+
2008
2076
  this.resetCursorPos( CodeEditor.CURSOR_LEFT_TOP );
2009
2077
  this.processLines();
2010
2078
  this._changeLanguageFromExtension( LX.getExtension( name ) );
2079
+ this._processLinesIfNecessary();
2011
2080
  this._updateDataInfoPanel( "@tab-name", code.tabName );
2012
2081
  }
2013
2082
 
@@ -2044,6 +2113,8 @@ class CodeEditor {
2044
2113
  delete this._tabStorage[ name ];
2045
2114
  }
2046
2115
 
2116
+ this._processLinesIfNecessary();
2117
+
2047
2118
  return;
2048
2119
  }
2049
2120
 
@@ -2098,7 +2169,7 @@ class CodeEditor {
2098
2169
  document.body.appendChild( input );
2099
2170
  input.click();
2100
2171
  input.addEventListener('change', e => {
2101
- if (e.target.files[ 0 ])
2172
+ if( e.target.files[ 0 ] )
2102
2173
  {
2103
2174
  this.loadFile( e.target.files[ 0 ] );
2104
2175
  }
@@ -3029,10 +3100,24 @@ class CodeEditor {
3029
3100
  return Math.max(...this.code.lines.map( v => v.length ));
3030
3101
  }
3031
3102
 
3103
+ _processLinesIfNecessary() {
3104
+ if( this.mustProcessLines )
3105
+ {
3106
+ this.mustProcessLines = false;
3107
+ this.processLines();
3108
+ }
3109
+ }
3110
+
3032
3111
  processLines( mode ) {
3033
3112
 
3113
+ if( !this.code )
3114
+ {
3115
+ return;
3116
+ }
3117
+
3034
3118
  var htmlCode = "";
3035
3119
  this._blockCommentCache.length = 0;
3120
+ this.mustProcessLines = false;
3036
3121
 
3037
3122
  // Reset all lines content
3038
3123
  this.code.innerHTML = "";
@@ -3080,13 +3165,6 @@ class CodeEditor {
3080
3165
 
3081
3166
  processLine( lineNumber, force, skipPropagation ) {
3082
3167
 
3083
- // Check if we are in block comment sections..
3084
- if( !force && this._inBlockCommentSection( lineNumber ) )
3085
- {
3086
- this.processLines();
3087
- return;
3088
- }
3089
-
3090
3168
  if( this._scopeStack )
3091
3169
  {
3092
3170
  this.code.lineScopes[ lineNumber ] = [ ...this._scopeStack ];
@@ -3100,6 +3178,12 @@ class CodeEditor {
3100
3178
  const lang = CodeEditor.languages[ this.highlight ];
3101
3179
  const localLineNum = this.toLocalLine( lineNumber );
3102
3180
  const lineString = this.code.lines[ lineNumber ];
3181
+ if( lineString === undefined )
3182
+ {
3183
+ return;
3184
+ }
3185
+
3186
+ this._lastProcessedLine = lineNumber;
3103
3187
 
3104
3188
  // multi-line strings not supported by now
3105
3189
  delete this._buildingString;
@@ -3132,6 +3216,18 @@ class CodeEditor {
3132
3216
  let lineInnerHtml = "";
3133
3217
  let pushedScope = false;
3134
3218
 
3219
+ const newSignature = this._getLineSignatureFromTokens( tokensToEvaluate );
3220
+ const cachedSignature = this.code.lineSignatures[ lineNumber ];
3221
+ const mustUpdateScopes = ( cachedSignature !== newSignature ) && !force;
3222
+ const blockComments = lang.blockComments ?? true;
3223
+ const blockCommentsTokens = lang.blockCommentsTokens ?? this.defaultBlockCommentTokens;
3224
+
3225
+ // Reset scope stack if structural changes in current line
3226
+ if( mustUpdateScopes )
3227
+ {
3228
+ this._scopeStack = [ { name: "", type: "global" } ];
3229
+ }
3230
+
3135
3231
  // Process all tokens
3136
3232
  for( let i = 0; i < tokensToEvaluate.length; ++i )
3137
3233
  {
@@ -3152,34 +3248,44 @@ class CodeEditor {
3152
3248
  }
3153
3249
 
3154
3250
  const token = tokensToEvaluate[ i ];
3251
+ const tokenIndex = i;
3252
+ const tokenStartIndex = this._currentTokenPositions[ tokenIndex ];;
3155
3253
 
3156
- if( lang.blockComments ?? true )
3254
+ if( blockComments )
3157
3255
  {
3158
- const blockCommentsToken = ( lang.blockCommentsTokens ?? this.defaultBlockCommentTokens )[ 0 ];
3159
- if( token.substr( 0, blockCommentsToken.length ) == blockCommentsToken )
3256
+ if( token.substr( 0, blockCommentsTokens[ 0 ].length ) == blockCommentsTokens[ 0 ] )
3160
3257
  {
3161
- this._buildingBlockComment = lineNumber;
3258
+ this._buildingBlockComment = [ lineNumber, tokenStartIndex ];
3162
3259
  }
3163
3260
  }
3164
3261
 
3165
- // Pop current scope if necessary
3262
+ // Compare line signature for structural changes
3263
+ // to pop current scope if necessary
3166
3264
  if( token === "}" && this._scopeStack.length > 1 )
3167
3265
  {
3168
3266
  this._scopeStack.pop();
3169
3267
  }
3170
3268
 
3171
3269
  lineInnerHtml += this._evaluateToken( {
3172
- token: token,
3173
- prev: prev,
3270
+ token,
3271
+ prev,
3174
3272
  prevWithSpaces: tokensToEvaluate[ i - 1 ],
3175
- next: next,
3273
+ next,
3176
3274
  nextWithSpaces: tokensToEvaluate[ i + 1 ],
3177
- tokenIndex: i,
3178
- isFirstToken: (i == 0),
3179
- isLastToken: (i == tokensToEvaluate.length - 1),
3275
+ tokenIndex,
3276
+ isFirstToken: ( tokenIndex == 0 ),
3277
+ isLastToken: ( tokenIndex == tokensToEvaluate.length - 1 ),
3180
3278
  tokens: tokensToEvaluate
3181
3279
  } );
3182
3280
 
3281
+ if( blockComments && this._buildingBlockComment != undefined
3282
+ && token.substr( 0, blockCommentsTokens[ 1 ].length ) == blockCommentsTokens[ 1 ] )
3283
+ {
3284
+ const [ commentLineNumber, tokenPos ] = this._buildingBlockComment;
3285
+ this._blockCommentCache.push( [ new LX.vec2( commentLineNumber, lineNumber ), new LX.vec2( tokenPos, tokenStartIndex ) ] );
3286
+ delete this._buildingBlockComment;
3287
+ }
3288
+
3183
3289
  if( token !== "{" )
3184
3290
  {
3185
3291
  continue;
@@ -3187,26 +3293,39 @@ class CodeEditor {
3187
3293
 
3188
3294
  // Store current scopes
3189
3295
 
3190
- // Get some context about the scope from previous lines
3191
3296
  let contextTokens = [
3192
- ...this._getTokensFromLine( this.code.lines[ lineNumber ].substring( 0, lineString.indexOf( token ) ) )
3297
+ ...this._getTokensFromLine( this.code.lines[ lineNumber ].substring( 0, tokenStartIndex ) )
3193
3298
  ];
3194
3299
 
3195
- for( let k = 1; k < 50; k++ )
3300
+ // Add token context from above lines in case we don't have information
3301
+ // in the same line to get the scope data
3302
+ if( !prev )
3196
3303
  {
3197
- let kLineString = this.code.lines[ lineNumber - k ];
3198
- if( !kLineString ) break;
3199
- const closeIdx = kLineString.lastIndexOf( '}' );
3200
- if( closeIdx > -1 )
3304
+ for( let k = 1; k < 50; k++ )
3201
3305
  {
3202
- kLineString = kLineString.substr( closeIdx );
3203
- }
3306
+ let kLineString = this.code.lines[ lineNumber - k ];
3307
+ if( !kLineString )
3308
+ {
3309
+ break;
3310
+ }
3204
3311
 
3205
- contextTokens = [ ...this._getTokensFromLine( kLineString ), ...contextTokens ];
3312
+ const openIdx = kLineString.lastIndexOf( '{' );
3313
+ const closeIdx = kLineString.lastIndexOf( '}' );
3314
+ if( openIdx > -1 )
3315
+ {
3316
+ kLineString = kLineString.substr( openIdx );
3317
+ }
3318
+ else if( closeIdx > -1 )
3319
+ {
3320
+ kLineString = kLineString.substr( closeIdx );
3321
+ }
3206
3322
 
3207
- if( kLineString.length !== this.code.lines[ lineNumber - k ] )
3208
- {
3209
- break;
3323
+ contextTokens = [ ...this._getTokensFromLine( kLineString ), ...contextTokens ];
3324
+
3325
+ if( kLineString.length !== this.code.lines[ lineNumber - k ] )
3326
+ {
3327
+ break;
3328
+ }
3210
3329
  }
3211
3330
  }
3212
3331
 
@@ -3249,28 +3368,111 @@ class CodeEditor {
3249
3368
  }
3250
3369
  }
3251
3370
 
3252
- if( scopeType )
3371
+ // Only push if it's not already reflected in the cached scopes
3372
+ const lastScope = this._scopeStack.at( -1 );
3373
+ if( lastScope?.lineNumber !== lineNumber )
3253
3374
  {
3254
- this._scopeStack.push( { name: scopeName ?? "", type: scopeType } );
3255
- }
3256
- else
3257
- {
3258
- this._scopeStack.push( { name: "", type: "anonymous" } ); // anonymous scope
3375
+ this._scopeStack.push( { name: scopeName ?? "", type: scopeType ?? "anonymous", lineNumber } );
3259
3376
  }
3260
3377
 
3261
3378
  pushedScope = true;
3262
3379
  }
3263
3380
 
3381
+ // Update scopes cache
3382
+ this.code.lineScopes[ lineNumber ] = [ ...this._scopeStack ];
3383
+
3264
3384
  const symbols = this._parseLineForSymbols( lineNumber, lineString, tokensToEvaluate, pushedScope );
3265
- return this._updateLine( force, lineNumber, lineInnerHtml, skipPropagation, symbols );
3385
+
3386
+ return this._updateLine( force, lineNumber, lineInnerHtml, skipPropagation, symbols, tokensToEvaluate );
3387
+ }
3388
+
3389
+ _getLineSignatureFromTokens( tokens ) {
3390
+ const structuralChars = new Set( [ '{', '}'] );
3391
+ const sign = tokens.filter( t => structuralChars.has( t ) );
3392
+ return sign.join( "_" );
3266
3393
  }
3267
3394
 
3268
- _processExtraLineIfNecessary( lineNumber, oldSymbols ) {
3395
+ _updateBlockComments( section, lineNumber, tokens ) {
3269
3396
 
3270
- if( ( (lineNumber + 1) === this.code.lines.length ) || !this.code.lineScopes[ lineNumber + 1 ] )
3397
+ const lang = CodeEditor.languages[ this.highlight ];
3398
+ const blockCommentsTokens = lang.blockCommentsTokens ?? this.defaultBlockCommentTokens;
3399
+ const lineOpensBlock = ( section[ 0 ].x === lineNumber );
3400
+ const lineClosesBlock = ( section[ 0 ].y === lineNumber );
3401
+ const lineInsideBlock = ( section[ 0 ].x !== lineNumber ) && ( section[ 0 ].y !== lineNumber );
3402
+
3403
+ delete this._buildingBlockComment;
3404
+
3405
+ /*
3406
+ Check if delimiters have been removed and process lines backwards/forward
3407
+ until reaching new delimiters
3408
+ */
3409
+
3410
+ if( lineOpensBlock )
3271
3411
  {
3272
- return;
3412
+ const r = tokens.filter( t => t.substr( 0, blockCommentsTokens[ 0 ].length ) == blockCommentsTokens[ 0 ] );
3413
+ if( !r.length )
3414
+ {
3415
+ this._buildingBlockComment = [ lineNumber - 1, 0 ];
3416
+
3417
+ this.mustProcessPreviousLine = ( tokens ) => {
3418
+ const idx = tokens.indexOf( blockCommentsTokens[ 0 ] );
3419
+ return ( idx === -1 );
3420
+ }
3421
+
3422
+ this.processLine( lineNumber - 1, false, true );
3423
+
3424
+ section[ 0 ].x = this._lastProcessedLine;
3425
+
3426
+ const lastProcessedString = this.code.lines[ this._lastProcessedLine ];
3427
+ const idx = lastProcessedString.indexOf( blockCommentsTokens[ 0 ] );
3428
+ section[ 1 ].x = idx > 0 ? idx : 0;
3429
+ }
3430
+ else
3431
+ {
3432
+ const tokenIndex = tokens.indexOf( blockCommentsTokens[ 0 ] );
3433
+ const tokenStartIndex = this._currentTokenPositions[ tokenIndex ];
3434
+ section[ 1 ].x = tokenStartIndex;
3435
+ console.log(tokenStartIndex)
3436
+ // Process current line to update new sections
3437
+ this.processLine( lineNumber, false, true );
3438
+ }
3273
3439
  }
3440
+ else if( lineClosesBlock )
3441
+ {
3442
+ const r = tokens.filter( t => t.substr( 0, blockCommentsTokens[ 1 ].length ) == blockCommentsTokens[ 1 ] );
3443
+ if( !r.length )
3444
+ {
3445
+ this._buildingBlockComment = [ section[ 0 ].x, section[ 1 ].x ];
3446
+
3447
+ this.mustProcessNextLine = ( tokens ) => {
3448
+ const idx = tokens.indexOf( blockCommentsTokens[ 1 ] );
3449
+ return ( idx === -1 );
3450
+ }
3451
+
3452
+ this.processLine( lineNumber + 1, false, true );
3453
+
3454
+ section[ 0 ].y = this._lastProcessedLine;
3455
+
3456
+ const lastProcessedString = this.code.lines[ this._lastProcessedLine ];
3457
+ const idx = lastProcessedString.indexOf( blockCommentsTokens[ 1 ] );
3458
+ section[ 1 ].y = idx > 0 ? idx : ( lastProcessedString.length - 1 );
3459
+ }
3460
+ else
3461
+ {
3462
+ const tokenIndex = tokens.indexOf( blockCommentsTokens[ 1 ] );
3463
+ const tokenStartIndex = this._currentTokenPositions[ tokenIndex ];
3464
+ section[ 1 ].y = tokenStartIndex;
3465
+ // Process current line to update new sections
3466
+ this.processLine( lineNumber, false, true );
3467
+ }
3468
+ }
3469
+ else if( lineInsideBlock )
3470
+ {
3471
+ // Here it can't modify delimiters..
3472
+ }
3473
+ }
3474
+
3475
+ _processExtraLineIfNecessary( lineNumber, tokens, oldSymbols, skipPropagation ) {
3274
3476
 
3275
3477
  if( !this._scopeStack )
3276
3478
  {
@@ -3278,11 +3480,45 @@ class CodeEditor {
3278
3480
  return;
3279
3481
  }
3280
3482
 
3483
+ // Update block comments if necessary
3484
+ {
3485
+ const commentBlockSection = this._inBlockCommentSection( lineNumber, 1e10, -1e10 );
3486
+ if( tokens && commentBlockSection !== undefined )
3487
+ {
3488
+ this._updateBlockComments( commentBlockSection, lineNumber, tokens );
3489
+
3490
+ // Get again correct scope
3491
+ this._scopeStack = [ ...this.code.lineScopes[ lineNumber ] ];
3492
+ }
3493
+ }
3494
+
3495
+ if( ( (lineNumber + 1) === this.code.lines.length ) || !this.code.lineScopes[ lineNumber + 1 ] )
3496
+ {
3497
+ return;
3498
+ }
3499
+
3500
+ const newSignature = this._getLineSignatureFromTokens( tokens );
3501
+ const cachedSignature = this.code.lineSignatures[ lineNumber ];
3502
+ const mustUpdateScopes = ( cachedSignature !== newSignature );
3503
+ const sameScopes = codeScopesEqual( this._scopeStack, this.code.lineScopes[ lineNumber + 1 ] );
3504
+
3281
3505
  // Only update scope stack if something changed when editing a single line
3282
- if( codeScopesEqual( this._scopeStack, this.code.lineScopes[ lineNumber + 1 ] ) )
3506
+ // Compare line signature for structural changes
3507
+ if( ( mustUpdateScopes || this._scopesUpdated ) && ( !sameScopes && !skipPropagation ) )
3283
3508
  {
3284
- // First check for occurrencies of the old symbols, to reprocess that lines
3509
+ if( mustUpdateScopes )
3510
+ {
3511
+ this._scopesUpdated = true;
3512
+ }
3513
+
3514
+ this.code.lineScopes[ lineNumber + 1 ] = [ ...this._scopeStack ];
3515
+ this.processLine( lineNumber + 1 );
3285
3516
 
3517
+ delete this._scopesUpdated;
3518
+ }
3519
+ else if( sameScopes )
3520
+ {
3521
+ // In case of same scope, check for occurrencies of the old symbols, to reprocess that lines
3286
3522
  for( const sym of oldSymbols )
3287
3523
  {
3288
3524
  const tableSymbol = this.code.symbolsTable.get( sym.name );
@@ -3301,20 +3537,15 @@ class CodeEditor {
3301
3537
  this.processLine( occ.line, false, true );
3302
3538
  }
3303
3539
  }
3304
-
3305
- return;
3306
3540
  }
3307
-
3308
- this.code.lineScopes[ lineNumber + 1 ] = [ ...this._scopeStack ];
3309
- this.processLine( lineNumber + 1 );
3310
3541
  }
3311
3542
 
3312
- _updateLine( force, lineNumber, html, skipPropagation, symbols = [] ) {
3543
+ _updateLine( force, lineNumber, html, skipPropagation, symbols = [], tokens = [] ) {
3313
3544
 
3314
3545
  const gutterLineHtml = `<span class='line-gutter'>${ lineNumber + 1 }</span>`;
3315
3546
  const oldSymbols = this._updateLineSymbols( lineNumber, symbols );
3316
- const lineScope = CodeEditor.debugScopes ? this.code.lineScopes[ lineNumber ].map( s => `${ s.type }` ).join( ", " ) : "";
3317
- const lineSymbols = CodeEditor.debugSymbols ? this.code.lineSymbols[ lineNumber ].map( s => `${ s.name }(${ s.kind })` ).join( ", " ) : "";
3547
+ const lineScope = CodeEditor.debugScopes && this.code.lineScopes[ lineNumber ] ? this.code.lineScopes[ lineNumber ].map( s => `${ s.type }` ).join( ", " ) : "";
3548
+ const lineSymbols = CodeEditor.debugSymbols && this.code.lineSymbols[ lineNumber ] ? this.code.lineSymbols[ lineNumber ].map( s => `${ s.name }(${ s.kind })` ).join( ", " ) : "";
3318
3549
  const debugString = lineScope + ( lineScope.length ? " - " : "" ) + lineSymbols;
3319
3550
 
3320
3551
  if( !force ) // Single line update
@@ -3323,17 +3554,46 @@ class CodeEditor {
3323
3554
 
3324
3555
  if( !skipPropagation )
3325
3556
  {
3326
- this._processExtraLineIfNecessary( lineNumber, oldSymbols );
3557
+ this._processExtraLineIfNecessary( lineNumber, tokens, oldSymbols, skipPropagation );
3558
+ }
3559
+
3560
+ if( this.mustProcessNextLine )
3561
+ {
3562
+ if( this.mustProcessNextLine( tokens ) && ( ( lineNumber + 1 ) < this.code.lines.length ) )
3563
+ {
3564
+ this.processLine( lineNumber + 1, false, true );
3565
+ }
3566
+ else
3567
+ {
3568
+ delete this.mustProcessNextLine;
3569
+ }
3570
+ }
3571
+
3572
+ if( this.mustProcessPreviousLine )
3573
+ {
3574
+ if( this.mustProcessPreviousLine( tokens ) && ( ( lineNumber - 1 ) >= 0 ) )
3575
+ {
3576
+ this.processLine( lineNumber - 1, false, true );
3577
+ }
3578
+ else
3579
+ {
3580
+ delete this.mustProcessPreviousLine;
3581
+ }
3582
+ }
3583
+
3584
+ if( CodeEditor.debugProcessedLines )
3585
+ {
3586
+ this.code.childNodes[ lineNumber ]?.classList.add( "debug" );
3327
3587
  }
3328
3588
 
3329
3589
  this._setActiveLine( lineNumber );
3330
3590
  this._clearTmpVariables();
3331
3591
  }
3332
- else // Update all lines at once
3333
- {
3334
3592
 
3335
- return `<pre>${ ( gutterLineHtml + html + debugString ) }</pre>`;
3336
- }
3593
+ this.code.lineSignatures[ lineNumber ] = this._getLineSignatureFromTokens( tokens );
3594
+
3595
+ // Update all lines at once
3596
+ return force ? `<pre>${ ( gutterLineHtml + html + debugString ) }</pre>` : undefined;
3337
3597
  }
3338
3598
 
3339
3599
  /**
@@ -3343,7 +3603,7 @@ class CodeEditor {
3343
3603
 
3344
3604
  const scope = this._scopeStack.at( pushedScope ? -2 : -1 );
3345
3605
 
3346
- if( !scope )
3606
+ if( !scope || this._inBlockCommentSection( lineNumber ) )
3347
3607
  {
3348
3608
  return [];
3349
3609
  }
@@ -3351,7 +3611,19 @@ class CodeEditor {
3351
3611
  const scopeName = scope.name;
3352
3612
  const scopeType = scope.type;
3353
3613
  const symbols = [];
3614
+ const symbolsMap = new Map();
3354
3615
  const text = lineString.trim();
3616
+ const previousLineScope = this.code.lineScopes[ lineNumber - 1 ];
3617
+
3618
+ const _pushSymbol = ( s ) => {
3619
+ const signature = `${ s.name }_${ s.kind }_${ s.scope }_${ s.line }`;
3620
+ if( symbolsMap.has( signature ) )
3621
+ {
3622
+ return;
3623
+ }
3624
+ symbolsMap.set( signature, s );
3625
+ symbols.push( s );
3626
+ };
3355
3627
 
3356
3628
  // Don't make symbols from preprocessor lines
3357
3629
  if( text.startsWith( "#" ) )
@@ -3359,6 +3631,8 @@ class CodeEditor {
3359
3631
  return [];
3360
3632
  }
3361
3633
 
3634
+ const nativeTypes = CodeEditor.nativeTypes[ this.highlight ];
3635
+
3362
3636
  const topLevelRegexes = [
3363
3637
  [/^class\s+([A-Za-z0-9_]+)/, "class"],
3364
3638
  [/^struct\s+([A-Za-z0-9_]+)/, "struct"],
@@ -3366,30 +3640,33 @@ class CodeEditor {
3366
3640
  [/^interface\s+([A-Za-z0-9_]+)/, "interface"],
3367
3641
  [/^type\s+([A-Za-z0-9_]+)/, "type"],
3368
3642
  [/^function\s+([A-Za-z0-9_]+)/, "method"],
3643
+ [/^fn\s+([A-Za-z0-9_]+)/, "method"],
3644
+ [/^def\s+([A-Za-z0-9_]+)/, "method"],
3369
3645
  [/^([A-Za-z0-9_]+)\s*=\s*\(?.*\)?\s*=>/, "method"] // arrow functions
3370
3646
  ];
3371
3647
 
3648
+ // Add regexes to detect methods, variables ( including "id : nativeType" )
3372
3649
  {
3373
- const nativeTypes = CodeEditor.nativeTypes[ this.highlight ];
3374
3650
  if( nativeTypes )
3375
3651
  {
3376
- const nativeTypes = ['int', 'float', 'double', 'bool', 'long', 'short', 'char', 'wchar_t', 'void'];
3377
- const regex = `^(?:${nativeTypes.join('|')})\\s+([A-Za-z0-9_]+)\s*[\(]+`;
3378
- topLevelRegexes.push( [ new RegExp( regex ), 'method' ] );
3652
+ topLevelRegexes.push( [ new RegExp( `^(?:${nativeTypes.join('|')})\\s+([A-Za-z0-9_]+)\s*[\(]+` ), 'method' ] );
3653
+
3654
+ if( this.highlight === "WGSL" )
3655
+ {
3656
+ topLevelRegexes.push( [ new RegExp( `[A-Za-z0-9]+(\\s*)+:(\\s*)+(${nativeTypes.join('|')})` ), 'variable', ( m ) => m[ 0 ].split( ":" )[ 0 ].trim() ] );
3657
+ }
3379
3658
  }
3380
3659
 
3381
3660
  const declarationKeywords = CodeEditor.declarationKeywords[ this.highlight ] ?? [ "const", "let", "var" ];
3382
- const regex = `^(?:${declarationKeywords.join('|')})\\s+([A-Za-z0-9_]+)`;
3383
- topLevelRegexes.push( [ new RegExp( regex ), 'variable' ] );
3661
+ topLevelRegexes.push( [ new RegExp( `^(?:${ declarationKeywords.join('|') })\\s+([A-Za-z0-9_]+)` ), 'variable' ] );
3384
3662
  }
3385
3663
 
3386
- for( let [ regex, kind ] of topLevelRegexes )
3664
+ for( let [ regex, kind, fn ] of topLevelRegexes )
3387
3665
  {
3388
3666
  const m = text.match( regex );
3389
3667
  if( m )
3390
3668
  {
3391
- symbols.push( { name: m[ 1 ], kind, scope: scopeName, line: lineNumber } );
3392
- break;
3669
+ _pushSymbol( { name: fn ? fn( m ) : m[ 1 ], kind, scope: scopeName, line: lineNumber } );
3393
3670
  }
3394
3671
  }
3395
3672
 
@@ -3398,15 +3675,28 @@ class CodeEditor {
3398
3675
  [/this.([A-Za-z_][A-Za-z0-9_]*)\s*\=/, "class-property"],
3399
3676
  ];
3400
3677
 
3401
- for( let [ regex, kind ] of usageRegexes )
3678
+ for( let [ regex, kind, fn ] of usageRegexes )
3402
3679
  {
3403
3680
  const m = text.match( regex );
3404
3681
  if( m )
3405
3682
  {
3406
- symbols.push( { name: m[ 1 ], kind, scope: scopeName, line: lineNumber } );
3683
+ _pushSymbol( { name: fn ? fn( m ) : m[ 1 ], kind, scope: scopeName, line: lineNumber } );
3407
3684
  }
3408
3685
  }
3409
3686
 
3687
+ // Detect method calls
3688
+ const regex = /([A-Za-z0-9_]+)\s*\(/g;
3689
+ let match;
3690
+ while( match = regex.exec( text ) )
3691
+ {
3692
+ const name = match[ 1 ];
3693
+ const before = text.slice( 0, match.index );
3694
+ if( /(new|function|fn|def)\s+$/.test( before ) ) continue; // skip constructor calls
3695
+ if( [ "constructor", "location", ...( nativeTypes ?? [] ) ].indexOf( name ) > -1 ) continue; // skip hardcoded non method symbol
3696
+ if( previousLineScope && previousLineScope.at( -1 )?.type === "class" ) continue; // skip class methods
3697
+ _pushSymbol( { name, kind: "method-call", scope: scopeName, line: lineNumber } );
3698
+ }
3699
+
3410
3700
  // Stop after matches for top-level declarations and usage symbols
3411
3701
  if( symbols.length )
3412
3702
  {
@@ -3425,14 +3715,15 @@ class CodeEditor {
3425
3715
  {
3426
3716
  if( next === "(" && /^[a-zA-Z_]\w*$/.test( token ) && prev === undefined )
3427
3717
  {
3428
- symbols.push( { name: token, kind: "method", scope: scopeName, line: lineNumber } );
3718
+ if( token === "constructor" ) continue; // skip constructor symbol
3719
+ _pushSymbol( { name: token, kind: "method", scope: scopeName, line: lineNumber } );
3429
3720
  }
3430
3721
  }
3431
3722
  else if( scopeType.startsWith("enum") )
3432
3723
  {
3433
3724
  if( !isSymbol( token ) && !this._isNumber( token ) && !this._mustHightlightWord( token, CodeEditor.statements ) )
3434
3725
  {
3435
- symbols.push({ name: token, kind: "enum_value", scope: scopeName, line: lineNumber });
3726
+ _pushSymbol({ name: token, kind: "enum_value", scope: scopeName, line: lineNumber });
3436
3727
  }
3437
3728
  }
3438
3729
  }
@@ -3547,13 +3838,14 @@ class CodeEditor {
3547
3838
  let idx = 0;
3548
3839
  while( subtokens.value != undefined )
3549
3840
  {
3550
- const _pt = lineString.substring(idx, subtokens.value.index);
3841
+ const _pt = lineString.substring( idx, subtokens.value.index );
3551
3842
  if( _pt.length ) pushToken( _pt );
3552
3843
  pushToken( subtokens.value[ 0 ] );
3553
3844
  idx = subtokens.value.index + subtokens.value[ 0 ].length;
3554
3845
  subtokens = iter.next();
3555
- if(!subtokens.value) {
3556
- const _at = lineString.substring(idx);
3846
+ if( !subtokens.value )
3847
+ {
3848
+ const _at = lineString.substring( idx );
3557
3849
  if( _at.length ) pushToken( _at );
3558
3850
  }
3559
3851
  }
@@ -3620,6 +3912,22 @@ class CodeEditor {
3620
3912
  offset = offsetIdx;
3621
3913
  }
3622
3914
  }
3915
+ else if( this.highlight == 'WGSL' )
3916
+ {
3917
+ let offset = 0;
3918
+ let atIdx = tokens.indexOf( '@' );
3919
+
3920
+ while( atIdx > -1 )
3921
+ {
3922
+ const offsetIdx = atIdx + offset;
3923
+
3924
+ tokens[ offsetIdx ] += ( tokens[ offsetIdx + 1 ] ?? "" );
3925
+ tokens.splice( offsetIdx + 1, 1 );
3926
+
3927
+ atIdx = tokens.slice( offsetIdx ).indexOf( '$' );
3928
+ offset = offsetIdx;
3929
+ }
3930
+ }
3623
3931
 
3624
3932
  return tokens;
3625
3933
  }
@@ -3664,10 +3972,12 @@ class CodeEditor {
3664
3972
 
3665
3973
  let { token, prev, next, tokenIndex, isFirstToken, isLastToken } = ctxData;
3666
3974
 
3667
- const lang = CodeEditor.languages[ this.highlight ],
3668
- highlight = this.highlight.replace( /\s/g, '' ).replaceAll( "+", "p" ).toLowerCase(),
3669
- customStringKeys = Object.assign( {}, this.stringKeys ),
3670
- lineNumber = this._currentLineNumber;
3975
+ const lang = CodeEditor.languages[ this.highlight ];
3976
+ const highlight = this.highlight.replace( /\s/g, '' ).replaceAll( "+", "p" ).toLowerCase();
3977
+ const customStringKeys = Object.assign( {}, this.stringKeys );
3978
+ const lineNumber = this._currentLineNumber;
3979
+ const tokenStartIndex = this._currentTokenPositions[ tokenIndex ];
3980
+ const inBlockComment = ( this._buildingBlockComment ?? this._inBlockCommentSection( lineNumber, tokenStartIndex, token.length ) !== undefined )
3671
3981
 
3672
3982
  var usePreviousTokenToCheckString = false;
3673
3983
 
@@ -3685,7 +3995,7 @@ class CodeEditor {
3685
3995
  // Manage strings
3686
3996
  this._stringEnded = false;
3687
3997
 
3688
- if( usePreviousTokenToCheckString || ( this._buildingBlockComment === undefined && ( lang.tags ?? false ? ( this._enclosedByTokens( token, tokenIndex, '<', '>' ) ) : true ) ) )
3998
+ if( usePreviousTokenToCheckString || ( !inBlockComment && ( lang.tags ?? false ? ( this._enclosedByTokens( token, tokenIndex, '<', '>' ) ) : true ) ) )
3689
3999
  {
3690
4000
  const _checkIfStringEnded = t => {
3691
4001
  const idx = Object.values( customStringKeys ).indexOf( t );
@@ -3711,9 +4021,9 @@ class CodeEditor {
3711
4021
 
3712
4022
  // Update context data for next tests
3713
4023
  ctxData.discardToken = false;
3714
- ctxData.inBlockComment = this._buildingBlockComment;
4024
+ ctxData.inBlockComment = inBlockComment;
3715
4025
  ctxData.markdownHeader = this._markdownHeader;
3716
- ctxData.inString = this._buildingString;
4026
+ ctxData.inString = ( this._buildingString !== undefined );
3717
4027
  ctxData.singleLineCommentToken = lang.singleLineCommentToken ?? this.defaultSingleLineCommentToken;
3718
4028
  ctxData.lang = lang;
3719
4029
  ctxData.scope = this._scopeStack.at( -1 );
@@ -3728,16 +4038,8 @@ class CodeEditor {
3728
4038
  // Get highlighting class based on language common and specific rules
3729
4039
  let tokenClass = this._getTokenHighlighting( ctxData, highlight );
3730
4040
 
3731
- const blockCommentsTokens = lang.blockCommentsTokens ?? this.defaultBlockCommentTokens;
3732
- if( ( lang.blockComments ?? true ) && this._buildingBlockComment != undefined
3733
- && token.substr( 0, blockCommentsTokens[ 1 ].length ) == blockCommentsTokens[ 1 ] )
3734
- {
3735
- this._blockCommentCache.push( new LX.vec2( this._buildingBlockComment, lineNumber ) );
3736
- delete this._buildingBlockComment;
3737
- }
3738
-
3739
4041
  // We finished constructing a string
3740
- if( this._buildingString && ( this._stringEnded || isLastToken ) )
4042
+ if( this._buildingString && ( this._stringEnded || isLastToken ) && !inBlockComment )
3741
4043
  {
3742
4044
  token = this._getCurrentString();
3743
4045
  tokenClass = "cm-str";
@@ -3801,17 +4103,32 @@ class CodeEditor {
3801
4103
  }
3802
4104
  }
3803
4105
 
3804
- _inBlockCommentSection( line ) {
4106
+ _inBlockCommentSection( lineNumber, tokenPosition, tokenLength ) {
4107
+
4108
+ const lang = CodeEditor.languages[ this.highlight ];
4109
+ const blockCommentsTokens = lang.blockCommentsTokens ?? this.defaultBlockCommentTokens;
3805
4110
 
3806
- for( var section of this._blockCommentCache )
4111
+ for( let section of this._blockCommentCache )
3807
4112
  {
3808
- if( line >= section.x && line <= section.y )
4113
+ const lineRange = section[ 0 ];
4114
+ const posRange = section[ 1 ];
4115
+
4116
+ // Outside the lines range
4117
+ const meetsLineRange = ( lineNumber >= lineRange.x && lineNumber <= lineRange.y );
4118
+ if( !meetsLineRange )
3809
4119
  {
3810
- return true;
4120
+ continue;
3811
4121
  }
3812
- }
3813
4122
 
3814
- return false;
4123
+ if( ( lineNumber != lineRange.x && lineNumber != lineRange.y ) || // Inside the block, not first nor last line
4124
+ ( lineNumber == lineRange.x && tokenPosition >= posRange.x &&
4125
+ (( lineNumber == lineRange.y && ( tokenPosition + tokenLength ) <= ( posRange.y + blockCommentsTokens[ 1 ].length ) ) || lineNumber !== lineRange.y) ) ||
4126
+ ( lineNumber == lineRange.y && ( ( tokenPosition + tokenLength ) <= ( posRange.y + blockCommentsTokens[ 1 ].length ) ) ) &&
4127
+ (( lineNumber == lineRange.x && tokenPosition >= posRange.x ) || lineNumber !== lineRange.x) )
4128
+ {
4129
+ return section;
4130
+ }
4131
+ }
3815
4132
  }
3816
4133
 
3817
4134
  _isKeyword( ctxData ) {
@@ -4479,7 +4796,7 @@ class CodeEditor {
4479
4796
 
4480
4797
  if( flag & CodeEditor.RESIZE_SCROLLBAR_V )
4481
4798
  {
4482
- scrollHeight = this.code.lines.length * this.lineHeight + this._fullVerticalOffset;
4799
+ scrollHeight = this.code.lines.length * this.lineHeight;
4483
4800
  this.codeSizer.style.minHeight = scrollHeight + "px";
4484
4801
  }
4485
4802
 
@@ -4538,7 +4855,7 @@ class CodeEditor {
4538
4855
  }
4539
4856
 
4540
4857
  this.hScrollbar.root.classList.toggle( 'hidden', !needsHorizontalScrollbar );
4541
- this.codeArea.root.style.height = `calc(100% - ${ this._verticalBottomOffset + ( needsHorizontalScrollbar ? ScrollBar.SCROLLBAR_HORIZONTAL_HEIGHT : 0 ) }px)`;
4858
+ this.codeArea.root.style.height = `calc(100% - ${ this._fullVerticalOffset + ( needsHorizontalScrollbar ? ScrollBar.SCROLLBAR_HORIZONTAL_HEIGHT : 0 ) }px)`;
4542
4859
  }
4543
4860
  }
4544
4861
 
@@ -4754,8 +5071,6 @@ class CodeEditor {
4754
5071
  ...Array.from( CodeEditor.utils[ this.highlight ] ?? [] )
4755
5072
  ];
4756
5073
 
4757
- suggestions = suggestions.concat( Object.keys(this.code.tokens).filter( a => a != word ) );
4758
-
4759
5074
  const scopeStack = [ ...this.code.lineScopes[ cursor.line ] ];
4760
5075
  const scope = scopeStack.at( -1 );
4761
5076
  if( scope.type.startsWith( "enum" ) )
@@ -4769,10 +5084,8 @@ class CodeEditor {
4769
5084
  suggestions = suggestions.concat( otherValues.slice( 0, -1 ) );
4770
5085
  }
4771
5086
 
4772
- const prefix = word.toLowerCase();
4773
-
4774
5087
  // Remove 1/2 char words and duplicates...
4775
- suggestions = Array.from( new Set( suggestions )).filter( s => s.length > 2 && s.toLowerCase().includes( prefix ) );
5088
+ suggestions = Array.from( new Set( suggestions )).filter( s => s.length > 2 && s.toLowerCase().includes( word.toLowerCase() ) );
4776
5089
 
4777
5090
  // Order...
4778
5091
 
@@ -4782,21 +5095,49 @@ class CodeEditor {
4782
5095
  return 2; // worst
4783
5096
  }
4784
5097
 
4785
- suggestions = suggestions.sort( ( a, b ) => scoreSuggestion( a, prefix ) - scoreSuggestion( b, prefix ) || a.localeCompare( b ) );
5098
+ suggestions = suggestions.sort( ( a, b ) => ( scoreSuggestion( a, word ) - scoreSuggestion( b, word ) ) || a.localeCompare( b ) );
4786
5099
 
4787
5100
  for( let s of suggestions )
4788
5101
  {
4789
- var pre = document.createElement( 'pre' );
5102
+ const pre = document.createElement( 'pre' );
4790
5103
  this.autocomplete.appendChild( pre );
4791
5104
 
4792
- var icon = "Type";
4793
- if( this._mustHightlightWord( s, CodeEditor.utils ) )
4794
- icon = "Box";
4795
- else if( this._mustHightlightWord( s, CodeEditor.types ) )
4796
- icon = "Code";
5105
+ const symbol = this.code.symbolsTable.get( s );
4797
5106
 
4798
- pre.appendChild( LX.makeIcon( icon, { iconClass: "mr-1", svgClass: "xs" } ) );
5107
+ let iconName = "CaseLower";
5108
+ let iconClass = "foo";
5109
+
5110
+ if( symbol )
5111
+ {
5112
+ switch( symbol[ 0 ].kind ) // Get first occurrence
5113
+ {
5114
+ case "variable":
5115
+ iconName = "Cuboid";
5116
+ iconClass = "lightblue";
5117
+ break;
5118
+ case "method":
5119
+ iconName = "Box";
5120
+ iconClass = "heliotrope";
5121
+ break;
5122
+ case "class":
5123
+ iconName = "CircleNodes";
5124
+ iconClass = "orange";
5125
+ break;
5126
+ }
5127
+ }
5128
+ else
5129
+ {
5130
+ if( this._mustHightlightWord( s, CodeEditor.utils ) )
5131
+ iconName = "ToolCase";
5132
+ else if( this._mustHightlightWord( s, CodeEditor.types ) )
5133
+ {
5134
+ iconName = "Type";
5135
+ iconClass = "lightblue";
5136
+ }
5137
+ }
4799
5138
 
5139
+ pre.appendChild( LX.makeIcon( iconName, { iconClass: "mr-1", svgClass: "sm " + iconClass } ) );
5140
+ s
4800
5141
  pre.addEventListener( 'click', () => {
4801
5142
  this.autoCompleteWord( s );
4802
5143
  } );
@@ -5284,7 +5625,8 @@ CodeEditor.languages = {
5284
5625
  };
5285
5626
 
5286
5627
  CodeEditor.nativeTypes = {
5287
- 'C++': ['int', 'float', 'double', 'bool', 'long', 'short', 'char', 'wchar_t', 'void']
5628
+ 'C++': ['int', 'float', 'double', 'bool', 'long', 'short', 'char', 'wchar_t', 'void'],
5629
+ 'WGSL': ['bool', 'u32', 'i32', 'f16', 'f32', 'vec2', 'vec3', 'vec4', 'vec2f', 'vec3f', 'vec4f', 'mat2x2f', 'mat3x3f', 'mat4x4f', 'array', 'vec2u', 'vec3u', 'vec4u', 'ptr', 'sampler']
5288
5630
  };
5289
5631
 
5290
5632
  CodeEditor.declarationKeywords = {
@@ -5308,10 +5650,9 @@ CodeEditor.keywords = {
5308
5650
  'GLSL': ['true', 'false', 'function', 'int', 'float', 'vec2', 'vec3', 'vec4', 'mat2x2', 'mat3x3', 'mat4x4', 'struct'],
5309
5651
  'CSS': ['body', 'html', 'canvas', 'div', 'input', 'span', '.', 'table', 'tr', 'td', 'th', 'label', 'video', 'img', 'code', 'button', 'select', 'option', 'svg', 'media', 'all',
5310
5652
  'i', 'a', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'last-child', 'tbody', 'pre', 'monospace', 'font-face'],
5311
- 'WGSL': ['var', 'let', 'true', 'false', 'fn', 'bool', 'u32', 'i32', 'f16', 'f32', 'vec2', 'vec3', 'vec4', 'vec2f', 'vec3f', 'vec4f', 'mat2x2f', 'mat3x3f', 'mat4x4f', 'array', 'atomic', 'struct',
5312
- 'sampler', 'sampler_comparison', 'texture_depth_2d', 'texture_depth_2d_array', 'texture_depth_cube', 'texture_depth_cube_array', 'texture_depth_multisampled_2d',
5313
- 'texture_external', 'texture_1d', 'texture_2d', 'texture_2d_array', 'texture_3d', 'texture_cube', 'texture_cube_array', 'texture_storage_1d', 'texture_storage_2d',
5314
- 'texture_storage_2d_array', 'texture_storage_3d', 'vec2u', 'vec3u', 'vec4u', 'ptr'],
5653
+ 'WGSL': [...CodeEditor.nativeTypes["WGSL"], 'var', 'let', 'true', 'false', 'fn', 'atomic', 'struct', 'sampler_comparison', 'texture_depth_2d', 'texture_depth_2d_array', 'texture_depth_cube',
5654
+ 'texture_depth_cube_array', 'texture_depth_multisampled_2d', 'texture_external', 'texture_1d', 'texture_2d', 'texture_2d_array', 'texture_3d', 'texture_cube', 'texture_cube_array',
5655
+ 'texture_storage_1d', 'texture_storage_2d', 'texture_storage_2d_array', 'texture_storage_3d'],
5315
5656
  'Rust': ['as', 'const', 'crate', 'enum', 'extern', 'false', 'fn', 'impl', 'in', 'let', 'mod', 'move', 'mut', 'pub', 'ref', 'self', 'Self', 'static', 'struct', 'super', 'trait', 'true',
5316
5657
  'type', 'unsafe', 'use', 'where', 'abstract', 'become', 'box', 'final', 'macro', 'override', 'priv', 'typeof', 'unsized', 'virtual'],
5317
5658
  'Python': ['False', 'def', 'None', 'True', 'in', 'is', 'and', 'lambda', 'nonlocal', 'not', 'or'],
@@ -5330,7 +5671,14 @@ CodeEditor.utils = { // These ones don't have hightlight, used as suggestions to
5330
5671
  'Python': ['abs', 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'delattr', 'dict', 'dir', 'divmod',
5331
5672
  'enumerate', 'eval', 'exec', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance',
5332
5673
  'issubclass', 'iter', 'len', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'range', 'repr',
5333
- 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']
5674
+ 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip'],
5675
+ 'CSS': [ ...Object.keys( document.body.style ).map( LX.toKebabCase ), 'block', 'inline', 'inline-block', 'flex', 'grid', 'none', 'inherit', 'initial', 'unset', 'revert', 'sticky',
5676
+ 'relative', 'absolute', 'fixed', 'static', 'auto', 'visible', 'hidden', 'scroll', 'clip', 'ellipsis', 'nowrap', 'wrap', 'break-word', 'solid', 'dashed', 'dotted', 'double',
5677
+ 'groove', 'ridge', 'inset', 'outset', 'left', 'right', 'center', 'top', 'bottom', 'start', 'end', 'justify', 'stretch', 'space-between', 'space-around', 'space-evenly',
5678
+ 'baseline', 'middle', 'normal', 'bold', 'lighter', 'bolder', 'italic', 'blur', 'uppercase', 'lowercase', 'capitalize', 'transparent', 'currentColor', 'pointer', 'default',
5679
+ 'move', 'grab', 'grabbing', 'not-allowed', 'none', 'cover', 'contain', 'repeat', 'no-repeat', 'repeat-x', 'repeat-y', 'round', 'space', 'linear-gradient', 'radial-gradient',
5680
+ 'conic-gradient', 'url', 'calc', 'min', 'max', 'clamp', 'red', 'blue', 'green', 'black', 'white', 'gray', 'silver', 'yellow', 'orange', 'purple', 'pink', 'cyan', 'magenta',
5681
+ 'lime', 'teal', 'navy', 'transparent', 'currentcolor', 'inherit', 'initial', 'unset', 'revert', 'none', 'auto', 'fit-content', 'min-content', 'max-content']
5334
5682
  };
5335
5683
 
5336
5684
  CodeEditor.types = {
@@ -5351,6 +5699,7 @@ CodeEditor.builtIn = {
5351
5699
  'JavaScript': ['document', 'console', 'window', 'navigator', 'performance'],
5352
5700
  'CSS': ['*', '!important'],
5353
5701
  'C++': ['vector', 'list', 'map'],
5702
+ 'WGSL': ['@vertex', '@fragment'],
5354
5703
  'HTML': ['type', 'xmlns', 'PUBLIC', 'http-equiv', 'src', 'style', 'lang', 'href', 'rel', 'content', 'xml', 'alt'], // attributes
5355
5704
  'Markdown': ['type', 'src', 'style', 'lang', 'href', 'rel', 'content', 'valign', 'alt'], // attributes
5356
5705
  'PHP': ['echo', 'print'],