lexgui 0.7.3 → 0.7.4

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' );
@@ -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 );
@@ -1240,10 +1244,10 @@ class CodeEditor {
1240
1244
 
1241
1245
  this.gutter.style.marginTop = `${ this._verticalTopOffset }px`;
1242
1246
  this.gutter.style.height = `calc(100% - ${ this._fullVerticalOffset }px)`;
1243
- this.vScrollbar.root.style.marginTop = `${ this.tabs?.root.getBoundingClientRect().height ?? 0 }px`;
1247
+ this.vScrollbar.root.style.marginTop = `${ this._verticalTopOffset }px`;
1244
1248
  this.vScrollbar.root.style.height = `calc(100% - ${ this._fullVerticalOffset }px)`;
1245
1249
  this.hScrollbar.root.style.bottom = `${ this._verticalBottomOffset }px`;
1246
- this.codeArea.root.style.height = `calc(100% - ${ this._verticalBottomOffset }px)`;
1250
+ this.codeArea.root.style.height = `calc(100% - ${ this._fullVerticalOffset }px)`;
1247
1251
  }, 50 );
1248
1252
 
1249
1253
  });
@@ -1264,11 +1268,16 @@ class CodeEditor {
1264
1268
 
1265
1269
  for( let url of options.files )
1266
1270
  {
1267
- this.loadFile( url, { callback: () => {
1271
+ this.loadFile( url, { callback: ( name, text ) => {
1268
1272
  filesLoaded++;
1269
1273
  if( filesLoaded == numFiles )
1270
1274
  {
1271
1275
  onLoadAll();
1276
+
1277
+ if( options.onFilesLoaded )
1278
+ {
1279
+ options.onFilesLoaded( this, numFiles );
1280
+ }
1272
1281
  }
1273
1282
  }});
1274
1283
  }
@@ -1322,12 +1331,15 @@ class CodeEditor {
1322
1331
 
1323
1332
  this.cursorToLine( cursor, newLines.length ); // Already substracted 1
1324
1333
  this.cursorToPosition( cursor, lastLine.length );
1325
- this.processLines();
1334
+
1335
+ this.mustProcessLines = true;
1326
1336
 
1327
1337
  if( lang )
1328
1338
  {
1329
1339
  this._changeLanguage( lang );
1330
1340
  }
1341
+
1342
+ this._processLinesIfNecessary();
1331
1343
  }
1332
1344
 
1333
1345
  appendText( text, cursor ) {
@@ -1401,7 +1413,7 @@ class CodeEditor {
1401
1413
  } );
1402
1414
  }
1403
1415
 
1404
- loadFile( file, options = {} ) {
1416
+ async loadFile( file, options = {} ) {
1405
1417
 
1406
1418
  const _innerAddTab = ( text, name, title ) => {
1407
1419
 
@@ -1429,10 +1441,6 @@ class CodeEditor {
1429
1441
  this.addExplorerItem( { id: name, skipVisibility: true, icon: this._getFileIcon( name, ext ) } );
1430
1442
  this.explorer.innerTree.frefresh( name );
1431
1443
  }
1432
- else
1433
- {
1434
-
1435
- }
1436
1444
  }
1437
1445
  else
1438
1446
  {
@@ -1448,17 +1456,19 @@ class CodeEditor {
1448
1456
 
1449
1457
  if( options.callback )
1450
1458
  {
1451
- options.callback( text );
1459
+ options.callback( name, text );
1452
1460
  }
1453
1461
  };
1454
1462
 
1455
1463
  if( file.constructor == String )
1456
1464
  {
1457
1465
  let filename = file;
1466
+
1458
1467
  LX.request({ url: filename, success: text => {
1459
1468
  const name = filename.substring(filename.lastIndexOf( '/' ) + 1);
1460
1469
  _innerAddTab( text, name, filename );
1461
1470
  } });
1471
+
1462
1472
  }
1463
1473
  else // File Blob
1464
1474
  {
@@ -1598,7 +1608,8 @@ class CodeEditor {
1598
1608
  }
1599
1609
 
1600
1610
  this._updateDataInfoPanel( "@highlight", lang );
1601
- this.processLines();
1611
+
1612
+ this.mustProcessLines = true;
1602
1613
 
1603
1614
  const ext = langExtension ?? CodeEditor.languages[ lang ].ext;
1604
1615
  const icon = this._getFileIcon( null, ext );
@@ -1663,7 +1674,7 @@ class CodeEditor {
1663
1674
  this._changeLanguage( 'Plain Text' );
1664
1675
  }
1665
1676
 
1666
- _createStatusPanel() {
1677
+ _createStatusPanel( options ) {
1667
1678
 
1668
1679
  if( this.skipInfo )
1669
1680
  {
@@ -1688,7 +1699,7 @@ class CodeEditor {
1688
1699
 
1689
1700
  let rightStatusPanel = new LX.Panel( { height: "auto" } );
1690
1701
  rightStatusPanel.sameLine();
1691
- rightStatusPanel.addLabel( this.code.title, { id: "EditorFilenameStatusComponent", fit: true, signal: "@tab-name" });
1702
+ rightStatusPanel.addLabel( this.code?.title ?? "", { id: "EditorFilenameStatusComponent", fit: true, signal: "@tab-name" });
1692
1703
  rightStatusPanel.addButton( null, "Ln 1, Col 1", this.showSearchLineBox.bind( this ), { id: "EditorSelectionStatusComponent", fit: true, signal: "@cursor-data" });
1693
1704
  rightStatusPanel.addButton( null, "Spaces: " + this.tabSpaces, ( value, event ) => {
1694
1705
  LX.addContextMenu( "Spaces", event, m => {
@@ -1715,13 +1726,24 @@ class CodeEditor {
1715
1726
  panel.attach( rightStatusPanel.root );
1716
1727
 
1717
1728
  const itemVisibilityMap = {
1718
- "Font Size Zoom": true,
1719
- "Editor Filename": true,
1720
- "Editor Selection": true,
1721
- "Editor Indentation": true,
1722
- "Editor Language": true,
1729
+ "Font Size Zoom": options.statusShowFontSizeZoom ?? true,
1730
+ "Editor Filename": options.statusShowEditorFilename ?? true,
1731
+ "Editor Selection": options.statusShowEditorSelection ?? true,
1732
+ "Editor Indentation": options.statusShowEditorIndentation ?? true,
1733
+ "Editor Language": options.statusShowEditorLanguage ?? true,
1723
1734
  };
1724
1735
 
1736
+ const _setVisibility = ( itemName ) => {
1737
+ const b = panel.root.querySelector( `#${ itemName.replaceAll( " ", "" ) }StatusComponent` );
1738
+ console.assert( b, `${ itemName } has no status button!` );
1739
+ b.classList.toggle( "hidden", !itemVisibilityMap[ itemName ] );
1740
+ }
1741
+
1742
+ for( const [ itemName, v ] of Object.entries( itemVisibilityMap ) )
1743
+ {
1744
+ _setVisibility( itemName );
1745
+ }
1746
+
1725
1747
  panel.root.addEventListener( "contextmenu", (e) => {
1726
1748
 
1727
1749
  if( e.target && ( e.target.classList.contains( "lexpanel" ) || e.target.classList.contains( "lexinlinecomponents" ) ) )
@@ -1735,9 +1757,7 @@ class CodeEditor {
1735
1757
  icon: "Check",
1736
1758
  callback: () => {
1737
1759
  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 ] );
1760
+ _setVisibility( itemName );
1741
1761
  }
1742
1762
  }
1743
1763
  if( !itemVisibilityMap[ itemName ] ) delete item.icon;
@@ -1879,20 +1899,24 @@ class CodeEditor {
1879
1899
 
1880
1900
  // Create code content
1881
1901
  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";
1902
+ Object.assign( code, {
1903
+ className: 'code',
1904
+ lines: [ "" ],
1905
+ language: options.language ?? "Plain Text",
1906
+ cursorState: {},
1907
+ undoSteps: [],
1908
+ redoSteps: [],
1909
+ lineScopes: [],
1910
+ lineSymbols: [],
1911
+ lineSignatures: [],
1912
+ symbolsTable: new Map(),
1913
+ tabName: name,
1914
+ title: title ?? name,
1915
+ tokens: {}
1916
+ } );
1917
+
1918
+ code.style.left = "0px",
1919
+ code.style.top = "0px",
1896
1920
 
1897
1921
  code.addEventListener( 'dragenter', function(e) {
1898
1922
  e.preventDefault();
@@ -1942,15 +1966,18 @@ class CodeEditor {
1942
1966
  {
1943
1967
  this.code = code;
1944
1968
  this.resetCursorPos( CodeEditor.CURSOR_LEFT_TOP );
1945
- this.processLines();
1969
+ this.mustProcessLines = true;
1946
1970
  }
1947
1971
 
1948
1972
  if( options.language )
1949
1973
  {
1950
1974
  code.languageOverride = options.language;
1951
1975
  this._changeLanguage( code.languageOverride );
1976
+ this.mustProcessLines = true;
1952
1977
  }
1953
1978
 
1979
+ this._processLinesIfNecessary();
1980
+
1954
1981
  this._updateDataInfoPanel( "@tab-name", name );
1955
1982
 
1956
1983
  // Bc it could be overrided..
@@ -1993,6 +2020,8 @@ class CodeEditor {
1993
2020
  delete this._tabStorage[ name ];
1994
2021
  }
1995
2022
 
2023
+ this._processLinesIfNecessary();
2024
+
1996
2025
  return;
1997
2026
  }
1998
2027
 
@@ -2005,9 +2034,12 @@ class CodeEditor {
2005
2034
 
2006
2035
  // Select as current...
2007
2036
  this.code = code;
2037
+ this.mustProcessLines = true;
2038
+
2008
2039
  this.resetCursorPos( CodeEditor.CURSOR_LEFT_TOP );
2009
2040
  this.processLines();
2010
2041
  this._changeLanguageFromExtension( LX.getExtension( name ) );
2042
+ this._processLinesIfNecessary();
2011
2043
  this._updateDataInfoPanel( "@tab-name", code.tabName );
2012
2044
  }
2013
2045
 
@@ -2044,6 +2076,8 @@ class CodeEditor {
2044
2076
  delete this._tabStorage[ name ];
2045
2077
  }
2046
2078
 
2079
+ this._processLinesIfNecessary();
2080
+
2047
2081
  return;
2048
2082
  }
2049
2083
 
@@ -3029,10 +3063,24 @@ class CodeEditor {
3029
3063
  return Math.max(...this.code.lines.map( v => v.length ));
3030
3064
  }
3031
3065
 
3066
+ _processLinesIfNecessary() {
3067
+ if( this.mustProcessLines )
3068
+ {
3069
+ this.mustProcessLines = false;
3070
+ this.processLines();
3071
+ }
3072
+ }
3073
+
3032
3074
  processLines( mode ) {
3033
3075
 
3076
+ if( !this.code )
3077
+ {
3078
+ return;
3079
+ }
3080
+
3034
3081
  var htmlCode = "";
3035
3082
  this._blockCommentCache.length = 0;
3083
+ this.mustProcessLines = false;
3036
3084
 
3037
3085
  // Reset all lines content
3038
3086
  this.code.innerHTML = "";
@@ -3080,13 +3128,6 @@ class CodeEditor {
3080
3128
 
3081
3129
  processLine( lineNumber, force, skipPropagation ) {
3082
3130
 
3083
- // Check if we are in block comment sections..
3084
- if( !force && this._inBlockCommentSection( lineNumber ) )
3085
- {
3086
- this.processLines();
3087
- return;
3088
- }
3089
-
3090
3131
  if( this._scopeStack )
3091
3132
  {
3092
3133
  this.code.lineScopes[ lineNumber ] = [ ...this._scopeStack ];
@@ -3100,6 +3141,12 @@ class CodeEditor {
3100
3141
  const lang = CodeEditor.languages[ this.highlight ];
3101
3142
  const localLineNum = this.toLocalLine( lineNumber );
3102
3143
  const lineString = this.code.lines[ lineNumber ];
3144
+ if( lineString === undefined )
3145
+ {
3146
+ return;
3147
+ }
3148
+
3149
+ this._lastProcessedLine = lineNumber;
3103
3150
 
3104
3151
  // multi-line strings not supported by now
3105
3152
  delete this._buildingString;
@@ -3132,6 +3179,18 @@ class CodeEditor {
3132
3179
  let lineInnerHtml = "";
3133
3180
  let pushedScope = false;
3134
3181
 
3182
+ const newSignature = this._getLineSignatureFromTokens( tokensToEvaluate );
3183
+ const cachedSignature = this.code.lineSignatures[ lineNumber ];
3184
+ const mustUpdateScopes = ( cachedSignature !== newSignature ) && !force;
3185
+ const blockComments = lang.blockComments ?? true;
3186
+ const blockCommentsTokens = lang.blockCommentsTokens ?? this.defaultBlockCommentTokens;
3187
+
3188
+ // Reset scope stack if structural changes in current line
3189
+ if( mustUpdateScopes )
3190
+ {
3191
+ this._scopeStack = [ { name: "", type: "global" } ];
3192
+ }
3193
+
3135
3194
  // Process all tokens
3136
3195
  for( let i = 0; i < tokensToEvaluate.length; ++i )
3137
3196
  {
@@ -3152,34 +3211,44 @@ class CodeEditor {
3152
3211
  }
3153
3212
 
3154
3213
  const token = tokensToEvaluate[ i ];
3214
+ const tokenIndex = i;
3215
+ const tokenStartIndex = this._currentTokenPositions[ tokenIndex ];;
3155
3216
 
3156
- if( lang.blockComments ?? true )
3217
+ if( blockComments )
3157
3218
  {
3158
- const blockCommentsToken = ( lang.blockCommentsTokens ?? this.defaultBlockCommentTokens )[ 0 ];
3159
- if( token.substr( 0, blockCommentsToken.length ) == blockCommentsToken )
3219
+ if( token.substr( 0, blockCommentsTokens[ 0 ].length ) == blockCommentsTokens[ 0 ] )
3160
3220
  {
3161
- this._buildingBlockComment = lineNumber;
3221
+ this._buildingBlockComment = [ lineNumber, tokenStartIndex ];
3162
3222
  }
3163
3223
  }
3164
3224
 
3165
- // Pop current scope if necessary
3225
+ // Compare line signature for structural changes
3226
+ // to pop current scope if necessary
3166
3227
  if( token === "}" && this._scopeStack.length > 1 )
3167
3228
  {
3168
3229
  this._scopeStack.pop();
3169
3230
  }
3170
3231
 
3171
3232
  lineInnerHtml += this._evaluateToken( {
3172
- token: token,
3173
- prev: prev,
3233
+ token,
3234
+ prev,
3174
3235
  prevWithSpaces: tokensToEvaluate[ i - 1 ],
3175
- next: next,
3236
+ next,
3176
3237
  nextWithSpaces: tokensToEvaluate[ i + 1 ],
3177
- tokenIndex: i,
3178
- isFirstToken: (i == 0),
3179
- isLastToken: (i == tokensToEvaluate.length - 1),
3238
+ tokenIndex,
3239
+ isFirstToken: ( tokenIndex == 0 ),
3240
+ isLastToken: ( tokenIndex == tokensToEvaluate.length - 1 ),
3180
3241
  tokens: tokensToEvaluate
3181
3242
  } );
3182
3243
 
3244
+ if( blockComments && this._buildingBlockComment != undefined
3245
+ && token.substr( 0, blockCommentsTokens[ 1 ].length ) == blockCommentsTokens[ 1 ] )
3246
+ {
3247
+ const [ commentLineNumber, tokenPos ] = this._buildingBlockComment;
3248
+ this._blockCommentCache.push( [ new LX.vec2( commentLineNumber, lineNumber ), new LX.vec2( tokenPos, tokenStartIndex ) ] );
3249
+ delete this._buildingBlockComment;
3250
+ }
3251
+
3183
3252
  if( token !== "{" )
3184
3253
  {
3185
3254
  continue;
@@ -3187,26 +3256,39 @@ class CodeEditor {
3187
3256
 
3188
3257
  // Store current scopes
3189
3258
 
3190
- // Get some context about the scope from previous lines
3191
3259
  let contextTokens = [
3192
- ...this._getTokensFromLine( this.code.lines[ lineNumber ].substring( 0, lineString.indexOf( token ) ) )
3260
+ ...this._getTokensFromLine( this.code.lines[ lineNumber ].substring( 0, tokenStartIndex ) )
3193
3261
  ];
3194
3262
 
3195
- for( let k = 1; k < 50; k++ )
3263
+ // Add token context from above lines in case we don't have information
3264
+ // in the same line to get the scope data
3265
+ if( !prev )
3196
3266
  {
3197
- let kLineString = this.code.lines[ lineNumber - k ];
3198
- if( !kLineString ) break;
3199
- const closeIdx = kLineString.lastIndexOf( '}' );
3200
- if( closeIdx > -1 )
3267
+ for( let k = 1; k < 50; k++ )
3201
3268
  {
3202
- kLineString = kLineString.substr( closeIdx );
3203
- }
3269
+ let kLineString = this.code.lines[ lineNumber - k ];
3270
+ if( !kLineString )
3271
+ {
3272
+ break;
3273
+ }
3274
+
3275
+ const openIdx = kLineString.lastIndexOf( '{' );
3276
+ const closeIdx = kLineString.lastIndexOf( '}' );
3277
+ if( openIdx > -1 )
3278
+ {
3279
+ kLineString = kLineString.substr( openIdx );
3280
+ }
3281
+ else if( closeIdx > -1 )
3282
+ {
3283
+ kLineString = kLineString.substr( closeIdx );
3284
+ }
3204
3285
 
3205
- contextTokens = [ ...this._getTokensFromLine( kLineString ), ...contextTokens ];
3286
+ contextTokens = [ ...this._getTokensFromLine( kLineString ), ...contextTokens ];
3206
3287
 
3207
- if( kLineString.length !== this.code.lines[ lineNumber - k ] )
3208
- {
3209
- break;
3288
+ if( kLineString.length !== this.code.lines[ lineNumber - k ] )
3289
+ {
3290
+ break;
3291
+ }
3210
3292
  }
3211
3293
  }
3212
3294
 
@@ -3249,28 +3331,111 @@ class CodeEditor {
3249
3331
  }
3250
3332
  }
3251
3333
 
3252
- if( scopeType )
3334
+ // Only push if it's not already reflected in the cached scopes
3335
+ const lastScope = this._scopeStack.at( -1 );
3336
+ if( lastScope?.lineNumber !== lineNumber )
3253
3337
  {
3254
- this._scopeStack.push( { name: scopeName ?? "", type: scopeType } );
3255
- }
3256
- else
3257
- {
3258
- this._scopeStack.push( { name: "", type: "anonymous" } ); // anonymous scope
3338
+ this._scopeStack.push( { name: scopeName ?? "", type: scopeType ?? "anonymous", lineNumber } );
3259
3339
  }
3260
3340
 
3261
3341
  pushedScope = true;
3262
3342
  }
3263
3343
 
3344
+ // Update scopes cache
3345
+ this.code.lineScopes[ lineNumber ] = [ ...this._scopeStack ];
3346
+
3264
3347
  const symbols = this._parseLineForSymbols( lineNumber, lineString, tokensToEvaluate, pushedScope );
3265
- return this._updateLine( force, lineNumber, lineInnerHtml, skipPropagation, symbols );
3348
+
3349
+ return this._updateLine( force, lineNumber, lineInnerHtml, skipPropagation, symbols, tokensToEvaluate );
3266
3350
  }
3267
3351
 
3268
- _processExtraLineIfNecessary( lineNumber, oldSymbols ) {
3352
+ _getLineSignatureFromTokens( tokens ) {
3353
+ const structuralChars = new Set( [ '{', '}'] );
3354
+ const sign = tokens.filter( t => structuralChars.has( t ) );
3355
+ return sign.join( "_" );
3356
+ }
3269
3357
 
3270
- if( ( (lineNumber + 1) === this.code.lines.length ) || !this.code.lineScopes[ lineNumber + 1 ] )
3358
+ _updateBlockComments( section, lineNumber, tokens ) {
3359
+
3360
+ const lang = CodeEditor.languages[ this.highlight ];
3361
+ const blockCommentsTokens = lang.blockCommentsTokens ?? this.defaultBlockCommentTokens;
3362
+ const lineOpensBlock = ( section[ 0 ].x === lineNumber );
3363
+ const lineClosesBlock = ( section[ 0 ].y === lineNumber );
3364
+ const lineInsideBlock = ( section[ 0 ].x !== lineNumber ) && ( section[ 0 ].y !== lineNumber );
3365
+
3366
+ delete this._buildingBlockComment;
3367
+
3368
+ /*
3369
+ Check if delimiters have been removed and process lines backwards/forward
3370
+ until reaching new delimiters
3371
+ */
3372
+
3373
+ if( lineOpensBlock )
3271
3374
  {
3272
- return;
3375
+ const r = tokens.filter( t => t.substr( 0, blockCommentsTokens[ 0 ].length ) == blockCommentsTokens[ 0 ] );
3376
+ if( !r.length )
3377
+ {
3378
+ this._buildingBlockComment = [ lineNumber - 1, 0 ];
3379
+
3380
+ this.mustProcessPreviousLine = ( tokens ) => {
3381
+ const idx = tokens.indexOf( blockCommentsTokens[ 0 ] );
3382
+ return ( idx === -1 );
3383
+ }
3384
+
3385
+ this.processLine( lineNumber - 1, false, true );
3386
+
3387
+ section[ 0 ].x = this._lastProcessedLine;
3388
+
3389
+ const lastProcessedString = this.code.lines[ this._lastProcessedLine ];
3390
+ const idx = lastProcessedString.indexOf( blockCommentsTokens[ 0 ] );
3391
+ section[ 1 ].x = idx > 0 ? idx : 0;
3392
+ }
3393
+ else
3394
+ {
3395
+ const tokenIndex = tokens.indexOf( blockCommentsTokens[ 0 ] );
3396
+ const tokenStartIndex = this._currentTokenPositions[ tokenIndex ];
3397
+ section[ 1 ].x = tokenStartIndex;
3398
+ console.log(tokenStartIndex)
3399
+ // Process current line to update new sections
3400
+ this.processLine( lineNumber, false, true );
3401
+ }
3402
+ }
3403
+ else if( lineClosesBlock )
3404
+ {
3405
+ const r = tokens.filter( t => t.substr( 0, blockCommentsTokens[ 1 ].length ) == blockCommentsTokens[ 1 ] );
3406
+ if( !r.length )
3407
+ {
3408
+ this._buildingBlockComment = [ section[ 0 ].x, section[ 1 ].x ];
3409
+
3410
+ this.mustProcessNextLine = ( tokens ) => {
3411
+ const idx = tokens.indexOf( blockCommentsTokens[ 1 ] );
3412
+ return ( idx === -1 );
3413
+ }
3414
+
3415
+ this.processLine( lineNumber + 1, false, true );
3416
+
3417
+ section[ 0 ].y = this._lastProcessedLine;
3418
+
3419
+ const lastProcessedString = this.code.lines[ this._lastProcessedLine ];
3420
+ const idx = lastProcessedString.indexOf( blockCommentsTokens[ 1 ] );
3421
+ section[ 1 ].y = idx > 0 ? idx : ( lastProcessedString.length - 1 );
3422
+ }
3423
+ else
3424
+ {
3425
+ const tokenIndex = tokens.indexOf( blockCommentsTokens[ 1 ] );
3426
+ const tokenStartIndex = this._currentTokenPositions[ tokenIndex ];
3427
+ section[ 1 ].y = tokenStartIndex;
3428
+ // Process current line to update new sections
3429
+ this.processLine( lineNumber, false, true );
3430
+ }
3431
+ }
3432
+ else if( lineInsideBlock )
3433
+ {
3434
+ // Here it can't modify delimiters..
3273
3435
  }
3436
+ }
3437
+
3438
+ _processExtraLineIfNecessary( lineNumber, tokens, oldSymbols, skipPropagation ) {
3274
3439
 
3275
3440
  if( !this._scopeStack )
3276
3441
  {
@@ -3278,11 +3443,45 @@ class CodeEditor {
3278
3443
  return;
3279
3444
  }
3280
3445
 
3446
+ // Update block comments if necessary
3447
+ {
3448
+ const commentBlockSection = this._inBlockCommentSection( lineNumber, 1e10, -1e10 );
3449
+ if( tokens && commentBlockSection !== undefined )
3450
+ {
3451
+ this._updateBlockComments( commentBlockSection, lineNumber, tokens );
3452
+
3453
+ // Get again correct scope
3454
+ this._scopeStack = [ ...this.code.lineScopes[ lineNumber ] ];
3455
+ }
3456
+ }
3457
+
3458
+ if( ( (lineNumber + 1) === this.code.lines.length ) || !this.code.lineScopes[ lineNumber + 1 ] )
3459
+ {
3460
+ return;
3461
+ }
3462
+
3463
+ const newSignature = this._getLineSignatureFromTokens( tokens );
3464
+ const cachedSignature = this.code.lineSignatures[ lineNumber ];
3465
+ const mustUpdateScopes = ( cachedSignature !== newSignature );
3466
+ const sameScopes = codeScopesEqual( this._scopeStack, this.code.lineScopes[ lineNumber + 1 ] );
3467
+
3281
3468
  // Only update scope stack if something changed when editing a single line
3282
- if( codeScopesEqual( this._scopeStack, this.code.lineScopes[ lineNumber + 1 ] ) )
3469
+ // Compare line signature for structural changes
3470
+ if( ( mustUpdateScopes || this._scopesUpdated ) && ( !sameScopes && !skipPropagation ) )
3283
3471
  {
3284
- // First check for occurrencies of the old symbols, to reprocess that lines
3472
+ if( mustUpdateScopes )
3473
+ {
3474
+ this._scopesUpdated = true;
3475
+ }
3285
3476
 
3477
+ this.code.lineScopes[ lineNumber + 1 ] = [ ...this._scopeStack ];
3478
+ this.processLine( lineNumber + 1 );
3479
+
3480
+ delete this._scopesUpdated;
3481
+ }
3482
+ else if( sameScopes )
3483
+ {
3484
+ // In case of same scope, check for occurrencies of the old symbols, to reprocess that lines
3286
3485
  for( const sym of oldSymbols )
3287
3486
  {
3288
3487
  const tableSymbol = this.code.symbolsTable.get( sym.name );
@@ -3301,20 +3500,15 @@ class CodeEditor {
3301
3500
  this.processLine( occ.line, false, true );
3302
3501
  }
3303
3502
  }
3304
-
3305
- return;
3306
3503
  }
3307
-
3308
- this.code.lineScopes[ lineNumber + 1 ] = [ ...this._scopeStack ];
3309
- this.processLine( lineNumber + 1 );
3310
3504
  }
3311
3505
 
3312
- _updateLine( force, lineNumber, html, skipPropagation, symbols = [] ) {
3506
+ _updateLine( force, lineNumber, html, skipPropagation, symbols = [], tokens = [] ) {
3313
3507
 
3314
3508
  const gutterLineHtml = `<span class='line-gutter'>${ lineNumber + 1 }</span>`;
3315
3509
  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( ", " ) : "";
3510
+ const lineScope = CodeEditor.debugScopes && this.code.lineScopes[ lineNumber ] ? this.code.lineScopes[ lineNumber ].map( s => `${ s.type }` ).join( ", " ) : "";
3511
+ const lineSymbols = CodeEditor.debugSymbols && this.code.lineSymbols[ lineNumber ] ? this.code.lineSymbols[ lineNumber ].map( s => `${ s.name }(${ s.kind })` ).join( ", " ) : "";
3318
3512
  const debugString = lineScope + ( lineScope.length ? " - " : "" ) + lineSymbols;
3319
3513
 
3320
3514
  if( !force ) // Single line update
@@ -3323,17 +3517,46 @@ class CodeEditor {
3323
3517
 
3324
3518
  if( !skipPropagation )
3325
3519
  {
3326
- this._processExtraLineIfNecessary( lineNumber, oldSymbols );
3520
+ this._processExtraLineIfNecessary( lineNumber, tokens, oldSymbols, skipPropagation );
3521
+ }
3522
+
3523
+ if( this.mustProcessNextLine )
3524
+ {
3525
+ if( this.mustProcessNextLine( tokens ) && ( ( lineNumber + 1 ) < this.code.lines.length ) )
3526
+ {
3527
+ this.processLine( lineNumber + 1, false, true );
3528
+ }
3529
+ else
3530
+ {
3531
+ delete this.mustProcessNextLine;
3532
+ }
3533
+ }
3534
+
3535
+ if( this.mustProcessPreviousLine )
3536
+ {
3537
+ if( this.mustProcessPreviousLine( tokens ) && ( ( lineNumber - 1 ) >= 0 ) )
3538
+ {
3539
+ this.processLine( lineNumber - 1, false, true );
3540
+ }
3541
+ else
3542
+ {
3543
+ delete this.mustProcessPreviousLine;
3544
+ }
3545
+ }
3546
+
3547
+ if( CodeEditor.debugProcessedLines )
3548
+ {
3549
+ this.code.childNodes[ lineNumber ]?.classList.add( "debug" );
3327
3550
  }
3328
3551
 
3329
3552
  this._setActiveLine( lineNumber );
3330
3553
  this._clearTmpVariables();
3331
3554
  }
3332
- else // Update all lines at once
3333
- {
3334
3555
 
3335
- return `<pre>${ ( gutterLineHtml + html + debugString ) }</pre>`;
3336
- }
3556
+ this.code.lineSignatures[ lineNumber ] = this._getLineSignatureFromTokens( tokens );
3557
+
3558
+ // Update all lines at once
3559
+ return force ? `<pre>${ ( gutterLineHtml + html + debugString ) }</pre>` : undefined;
3337
3560
  }
3338
3561
 
3339
3562
  /**
@@ -3343,7 +3566,7 @@ class CodeEditor {
3343
3566
 
3344
3567
  const scope = this._scopeStack.at( pushedScope ? -2 : -1 );
3345
3568
 
3346
- if( !scope )
3569
+ if( !scope || this._inBlockCommentSection( lineNumber ) )
3347
3570
  {
3348
3571
  return [];
3349
3572
  }
@@ -3351,7 +3574,19 @@ class CodeEditor {
3351
3574
  const scopeName = scope.name;
3352
3575
  const scopeType = scope.type;
3353
3576
  const symbols = [];
3577
+ const symbolsMap = new Map();
3354
3578
  const text = lineString.trim();
3579
+ const previousLineScope = this.code.lineScopes[ lineNumber - 1 ];
3580
+
3581
+ const _pushSymbol = ( s ) => {
3582
+ const signature = `${ s.name }_${ s.kind }_${ s.scope }_${ s.line }`;
3583
+ if( symbolsMap.has( signature ) )
3584
+ {
3585
+ return;
3586
+ }
3587
+ symbolsMap.set( signature, s );
3588
+ symbols.push( s );
3589
+ };
3355
3590
 
3356
3591
  // Don't make symbols from preprocessor lines
3357
3592
  if( text.startsWith( "#" ) )
@@ -3359,6 +3594,8 @@ class CodeEditor {
3359
3594
  return [];
3360
3595
  }
3361
3596
 
3597
+ const nativeTypes = CodeEditor.nativeTypes[ this.highlight ];
3598
+
3362
3599
  const topLevelRegexes = [
3363
3600
  [/^class\s+([A-Za-z0-9_]+)/, "class"],
3364
3601
  [/^struct\s+([A-Za-z0-9_]+)/, "struct"],
@@ -3366,30 +3603,33 @@ class CodeEditor {
3366
3603
  [/^interface\s+([A-Za-z0-9_]+)/, "interface"],
3367
3604
  [/^type\s+([A-Za-z0-9_]+)/, "type"],
3368
3605
  [/^function\s+([A-Za-z0-9_]+)/, "method"],
3606
+ [/^fn\s+([A-Za-z0-9_]+)/, "method"],
3607
+ [/^def\s+([A-Za-z0-9_]+)/, "method"],
3369
3608
  [/^([A-Za-z0-9_]+)\s*=\s*\(?.*\)?\s*=>/, "method"] // arrow functions
3370
3609
  ];
3371
3610
 
3611
+ // Add regexes to detect methods, variables ( including "id : nativeType" )
3372
3612
  {
3373
- const nativeTypes = CodeEditor.nativeTypes[ this.highlight ];
3374
3613
  if( nativeTypes )
3375
3614
  {
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' ] );
3615
+ topLevelRegexes.push( [ new RegExp( `^(?:${nativeTypes.join('|')})\\s+([A-Za-z0-9_]+)\s*[\(]+` ), 'method' ] );
3616
+
3617
+ if( this.highlight === "WGSL" )
3618
+ {
3619
+ topLevelRegexes.push( [ new RegExp( `[A-Za-z0-9]+(\\s*)+:(\\s*)+(${nativeTypes.join('|')})` ), 'variable', ( m ) => m[ 0 ].split( ":" )[ 0 ].trim() ] );
3620
+ }
3379
3621
  }
3380
3622
 
3381
3623
  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' ] );
3624
+ topLevelRegexes.push( [ new RegExp( `^(?:${ declarationKeywords.join('|') })\\s+([A-Za-z0-9_]+)` ), 'variable' ] );
3384
3625
  }
3385
3626
 
3386
- for( let [ regex, kind ] of topLevelRegexes )
3627
+ for( let [ regex, kind, fn ] of topLevelRegexes )
3387
3628
  {
3388
3629
  const m = text.match( regex );
3389
3630
  if( m )
3390
3631
  {
3391
- symbols.push( { name: m[ 1 ], kind, scope: scopeName, line: lineNumber } );
3392
- break;
3632
+ _pushSymbol( { name: fn ? fn( m ) : m[ 1 ], kind, scope: scopeName, line: lineNumber } );
3393
3633
  }
3394
3634
  }
3395
3635
 
@@ -3398,15 +3638,28 @@ class CodeEditor {
3398
3638
  [/this.([A-Za-z_][A-Za-z0-9_]*)\s*\=/, "class-property"],
3399
3639
  ];
3400
3640
 
3401
- for( let [ regex, kind ] of usageRegexes )
3641
+ for( let [ regex, kind, fn ] of usageRegexes )
3402
3642
  {
3403
3643
  const m = text.match( regex );
3404
3644
  if( m )
3405
3645
  {
3406
- symbols.push( { name: m[ 1 ], kind, scope: scopeName, line: lineNumber } );
3646
+ _pushSymbol( { name: fn ? fn( m ) : m[ 1 ], kind, scope: scopeName, line: lineNumber } );
3407
3647
  }
3408
3648
  }
3409
3649
 
3650
+ // Detect method calls
3651
+ const regex = /([A-Za-z0-9_]+)\s*\(/g;
3652
+ let match;
3653
+ while( match = regex.exec( text ) )
3654
+ {
3655
+ const name = match[ 1 ];
3656
+ const before = text.slice( 0, match.index );
3657
+ if( /(new|function|fn|def)\s+$/.test( before ) ) continue; // skip constructor calls
3658
+ if( [ "constructor", "location", ...( nativeTypes ?? [] ) ].indexOf( name ) > -1 ) continue; // skip hardcoded non method symbol
3659
+ if( previousLineScope && previousLineScope.at( -1 )?.type === "class" ) continue; // skip class methods
3660
+ _pushSymbol( { name, kind: "method-call", scope: scopeName, line: lineNumber } );
3661
+ }
3662
+
3410
3663
  // Stop after matches for top-level declarations and usage symbols
3411
3664
  if( symbols.length )
3412
3665
  {
@@ -3425,14 +3678,15 @@ class CodeEditor {
3425
3678
  {
3426
3679
  if( next === "(" && /^[a-zA-Z_]\w*$/.test( token ) && prev === undefined )
3427
3680
  {
3428
- symbols.push( { name: token, kind: "method", scope: scopeName, line: lineNumber } );
3681
+ if( token === "constructor" ) continue; // skip constructor symbol
3682
+ _pushSymbol( { name: token, kind: "method", scope: scopeName, line: lineNumber } );
3429
3683
  }
3430
3684
  }
3431
3685
  else if( scopeType.startsWith("enum") )
3432
3686
  {
3433
3687
  if( !isSymbol( token ) && !this._isNumber( token ) && !this._mustHightlightWord( token, CodeEditor.statements ) )
3434
3688
  {
3435
- symbols.push({ name: token, kind: "enum_value", scope: scopeName, line: lineNumber });
3689
+ _pushSymbol({ name: token, kind: "enum_value", scope: scopeName, line: lineNumber });
3436
3690
  }
3437
3691
  }
3438
3692
  }
@@ -3547,13 +3801,14 @@ class CodeEditor {
3547
3801
  let idx = 0;
3548
3802
  while( subtokens.value != undefined )
3549
3803
  {
3550
- const _pt = lineString.substring(idx, subtokens.value.index);
3804
+ const _pt = lineString.substring( idx, subtokens.value.index );
3551
3805
  if( _pt.length ) pushToken( _pt );
3552
3806
  pushToken( subtokens.value[ 0 ] );
3553
3807
  idx = subtokens.value.index + subtokens.value[ 0 ].length;
3554
3808
  subtokens = iter.next();
3555
- if(!subtokens.value) {
3556
- const _at = lineString.substring(idx);
3809
+ if( !subtokens.value )
3810
+ {
3811
+ const _at = lineString.substring( idx );
3557
3812
  if( _at.length ) pushToken( _at );
3558
3813
  }
3559
3814
  }
@@ -3620,6 +3875,22 @@ class CodeEditor {
3620
3875
  offset = offsetIdx;
3621
3876
  }
3622
3877
  }
3878
+ else if( this.highlight == 'WGSL' )
3879
+ {
3880
+ let offset = 0;
3881
+ let atIdx = tokens.indexOf( '@' );
3882
+
3883
+ while( atIdx > -1 )
3884
+ {
3885
+ const offsetIdx = atIdx + offset;
3886
+
3887
+ tokens[ offsetIdx ] += ( tokens[ offsetIdx + 1 ] ?? "" );
3888
+ tokens.splice( offsetIdx + 1, 1 );
3889
+
3890
+ atIdx = tokens.slice( offsetIdx ).indexOf( '$' );
3891
+ offset = offsetIdx;
3892
+ }
3893
+ }
3623
3894
 
3624
3895
  return tokens;
3625
3896
  }
@@ -3664,10 +3935,12 @@ class CodeEditor {
3664
3935
 
3665
3936
  let { token, prev, next, tokenIndex, isFirstToken, isLastToken } = ctxData;
3666
3937
 
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;
3938
+ const lang = CodeEditor.languages[ this.highlight ];
3939
+ const highlight = this.highlight.replace( /\s/g, '' ).replaceAll( "+", "p" ).toLowerCase();
3940
+ const customStringKeys = Object.assign( {}, this.stringKeys );
3941
+ const lineNumber = this._currentLineNumber;
3942
+ const tokenStartIndex = this._currentTokenPositions[ tokenIndex ];
3943
+ const inBlockComment = ( this._buildingBlockComment ?? this._inBlockCommentSection( lineNumber, tokenStartIndex, token.length ) !== undefined )
3671
3944
 
3672
3945
  var usePreviousTokenToCheckString = false;
3673
3946
 
@@ -3685,7 +3958,7 @@ class CodeEditor {
3685
3958
  // Manage strings
3686
3959
  this._stringEnded = false;
3687
3960
 
3688
- if( usePreviousTokenToCheckString || ( this._buildingBlockComment === undefined && ( lang.tags ?? false ? ( this._enclosedByTokens( token, tokenIndex, '<', '>' ) ) : true ) ) )
3961
+ if( usePreviousTokenToCheckString || ( !inBlockComment && ( lang.tags ?? false ? ( this._enclosedByTokens( token, tokenIndex, '<', '>' ) ) : true ) ) )
3689
3962
  {
3690
3963
  const _checkIfStringEnded = t => {
3691
3964
  const idx = Object.values( customStringKeys ).indexOf( t );
@@ -3711,9 +3984,9 @@ class CodeEditor {
3711
3984
 
3712
3985
  // Update context data for next tests
3713
3986
  ctxData.discardToken = false;
3714
- ctxData.inBlockComment = this._buildingBlockComment;
3987
+ ctxData.inBlockComment = inBlockComment;
3715
3988
  ctxData.markdownHeader = this._markdownHeader;
3716
- ctxData.inString = this._buildingString;
3989
+ ctxData.inString = ( this._buildingString !== undefined );
3717
3990
  ctxData.singleLineCommentToken = lang.singleLineCommentToken ?? this.defaultSingleLineCommentToken;
3718
3991
  ctxData.lang = lang;
3719
3992
  ctxData.scope = this._scopeStack.at( -1 );
@@ -3728,16 +4001,8 @@ class CodeEditor {
3728
4001
  // Get highlighting class based on language common and specific rules
3729
4002
  let tokenClass = this._getTokenHighlighting( ctxData, highlight );
3730
4003
 
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
4004
  // We finished constructing a string
3740
- if( this._buildingString && ( this._stringEnded || isLastToken ) )
4005
+ if( this._buildingString && ( this._stringEnded || isLastToken ) && !inBlockComment )
3741
4006
  {
3742
4007
  token = this._getCurrentString();
3743
4008
  tokenClass = "cm-str";
@@ -3801,17 +4066,32 @@ class CodeEditor {
3801
4066
  }
3802
4067
  }
3803
4068
 
3804
- _inBlockCommentSection( line ) {
4069
+ _inBlockCommentSection( lineNumber, tokenPosition, tokenLength ) {
3805
4070
 
3806
- for( var section of this._blockCommentCache )
4071
+ const lang = CodeEditor.languages[ this.highlight ];
4072
+ const blockCommentsTokens = lang.blockCommentsTokens ?? this.defaultBlockCommentTokens;
4073
+
4074
+ for( let section of this._blockCommentCache )
3807
4075
  {
3808
- if( line >= section.x && line <= section.y )
4076
+ const lineRange = section[ 0 ];
4077
+ const posRange = section[ 1 ];
4078
+
4079
+ // Outside the lines range
4080
+ const meetsLineRange = ( lineNumber >= lineRange.x && lineNumber <= lineRange.y );
4081
+ if( !meetsLineRange )
3809
4082
  {
3810
- return true;
4083
+ continue;
3811
4084
  }
3812
- }
3813
4085
 
3814
- return false;
4086
+ if( ( lineNumber != lineRange.x && lineNumber != lineRange.y ) || // Inside the block, not first nor last line
4087
+ ( lineNumber == lineRange.x && tokenPosition >= posRange.x &&
4088
+ (( lineNumber == lineRange.y && ( tokenPosition + tokenLength ) <= ( posRange.y + blockCommentsTokens[ 1 ].length ) ) || lineNumber !== lineRange.y) ) ||
4089
+ ( lineNumber == lineRange.y && ( ( tokenPosition + tokenLength ) <= ( posRange.y + blockCommentsTokens[ 1 ].length ) ) ) &&
4090
+ (( lineNumber == lineRange.x && tokenPosition >= posRange.x ) || lineNumber !== lineRange.x) )
4091
+ {
4092
+ return section;
4093
+ }
4094
+ }
3815
4095
  }
3816
4096
 
3817
4097
  _isKeyword( ctxData ) {
@@ -4479,7 +4759,7 @@ class CodeEditor {
4479
4759
 
4480
4760
  if( flag & CodeEditor.RESIZE_SCROLLBAR_V )
4481
4761
  {
4482
- scrollHeight = this.code.lines.length * this.lineHeight + this._fullVerticalOffset;
4762
+ scrollHeight = this.code.lines.length * this.lineHeight;
4483
4763
  this.codeSizer.style.minHeight = scrollHeight + "px";
4484
4764
  }
4485
4765
 
@@ -4538,7 +4818,7 @@ class CodeEditor {
4538
4818
  }
4539
4819
 
4540
4820
  this.hScrollbar.root.classList.toggle( 'hidden', !needsHorizontalScrollbar );
4541
- this.codeArea.root.style.height = `calc(100% - ${ this._verticalBottomOffset + ( needsHorizontalScrollbar ? ScrollBar.SCROLLBAR_HORIZONTAL_HEIGHT : 0 ) }px)`;
4821
+ this.codeArea.root.style.height = `calc(100% - ${ this._fullVerticalOffset + ( needsHorizontalScrollbar ? ScrollBar.SCROLLBAR_HORIZONTAL_HEIGHT : 0 ) }px)`;
4542
4822
  }
4543
4823
  }
4544
4824
 
@@ -4754,8 +5034,6 @@ class CodeEditor {
4754
5034
  ...Array.from( CodeEditor.utils[ this.highlight ] ?? [] )
4755
5035
  ];
4756
5036
 
4757
- suggestions = suggestions.concat( Object.keys(this.code.tokens).filter( a => a != word ) );
4758
-
4759
5037
  const scopeStack = [ ...this.code.lineScopes[ cursor.line ] ];
4760
5038
  const scope = scopeStack.at( -1 );
4761
5039
  if( scope.type.startsWith( "enum" ) )
@@ -4769,10 +5047,8 @@ class CodeEditor {
4769
5047
  suggestions = suggestions.concat( otherValues.slice( 0, -1 ) );
4770
5048
  }
4771
5049
 
4772
- const prefix = word.toLowerCase();
4773
-
4774
5050
  // Remove 1/2 char words and duplicates...
4775
- suggestions = Array.from( new Set( suggestions )).filter( s => s.length > 2 && s.toLowerCase().includes( prefix ) );
5051
+ suggestions = Array.from( new Set( suggestions )).filter( s => s.length > 2 && s.toLowerCase().includes( word.toLowerCase() ) );
4776
5052
 
4777
5053
  // Order...
4778
5054
 
@@ -4782,21 +5058,49 @@ class CodeEditor {
4782
5058
  return 2; // worst
4783
5059
  }
4784
5060
 
4785
- suggestions = suggestions.sort( ( a, b ) => scoreSuggestion( a, prefix ) - scoreSuggestion( b, prefix ) || a.localeCompare( b ) );
5061
+ suggestions = suggestions.sort( ( a, b ) => ( scoreSuggestion( a, word ) - scoreSuggestion( b, word ) ) || a.localeCompare( b ) );
4786
5062
 
4787
5063
  for( let s of suggestions )
4788
5064
  {
4789
- var pre = document.createElement( 'pre' );
5065
+ const pre = document.createElement( 'pre' );
4790
5066
  this.autocomplete.appendChild( pre );
4791
5067
 
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";
5068
+ const symbol = this.code.symbolsTable.get( s );
5069
+
5070
+ let iconName = "CaseLower";
5071
+ let iconClass = "foo";
4797
5072
 
4798
- pre.appendChild( LX.makeIcon( icon, { iconClass: "mr-1", svgClass: "xs" } ) );
5073
+ if( symbol )
5074
+ {
5075
+ switch( symbol[ 0 ].kind ) // Get first occurrence
5076
+ {
5077
+ case "variable":
5078
+ iconName = "Cuboid";
5079
+ iconClass = "lightblue";
5080
+ break;
5081
+ case "method":
5082
+ iconName = "Box";
5083
+ iconClass = "heliotrope";
5084
+ break;
5085
+ case "class":
5086
+ iconName = "CircleNodes";
5087
+ iconClass = "orange";
5088
+ break;
5089
+ }
5090
+ }
5091
+ else
5092
+ {
5093
+ if( this._mustHightlightWord( s, CodeEditor.utils ) )
5094
+ iconName = "ToolCase";
5095
+ else if( this._mustHightlightWord( s, CodeEditor.types ) )
5096
+ {
5097
+ iconName = "Type";
5098
+ iconClass = "lightblue";
5099
+ }
5100
+ }
4799
5101
 
5102
+ pre.appendChild( LX.makeIcon( iconName, { iconClass: "mr-1", svgClass: "sm " + iconClass } ) );
5103
+ s
4800
5104
  pre.addEventListener( 'click', () => {
4801
5105
  this.autoCompleteWord( s );
4802
5106
  } );
@@ -5284,7 +5588,8 @@ CodeEditor.languages = {
5284
5588
  };
5285
5589
 
5286
5590
  CodeEditor.nativeTypes = {
5287
- 'C++': ['int', 'float', 'double', 'bool', 'long', 'short', 'char', 'wchar_t', 'void']
5591
+ 'C++': ['int', 'float', 'double', 'bool', 'long', 'short', 'char', 'wchar_t', 'void'],
5592
+ 'WGSL': ['bool', 'u32', 'i32', 'f16', 'f32', 'vec2', 'vec3', 'vec4', 'vec2f', 'vec3f', 'vec4f', 'mat2x2f', 'mat3x3f', 'mat4x4f', 'array', 'vec2u', 'vec3u', 'vec4u', 'ptr', 'sampler']
5288
5593
  };
5289
5594
 
5290
5595
  CodeEditor.declarationKeywords = {
@@ -5308,10 +5613,9 @@ CodeEditor.keywords = {
5308
5613
  'GLSL': ['true', 'false', 'function', 'int', 'float', 'vec2', 'vec3', 'vec4', 'mat2x2', 'mat3x3', 'mat4x4', 'struct'],
5309
5614
  'CSS': ['body', 'html', 'canvas', 'div', 'input', 'span', '.', 'table', 'tr', 'td', 'th', 'label', 'video', 'img', 'code', 'button', 'select', 'option', 'svg', 'media', 'all',
5310
5615
  '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'],
5616
+ 'WGSL': [...CodeEditor.nativeTypes["WGSL"], 'var', 'let', 'true', 'false', 'fn', 'atomic', 'struct', 'sampler_comparison', 'texture_depth_2d', 'texture_depth_2d_array', 'texture_depth_cube',
5617
+ 'texture_depth_cube_array', 'texture_depth_multisampled_2d', 'texture_external', 'texture_1d', 'texture_2d', 'texture_2d_array', 'texture_3d', 'texture_cube', 'texture_cube_array',
5618
+ 'texture_storage_1d', 'texture_storage_2d', 'texture_storage_2d_array', 'texture_storage_3d'],
5315
5619
  'Rust': ['as', 'const', 'crate', 'enum', 'extern', 'false', 'fn', 'impl', 'in', 'let', 'mod', 'move', 'mut', 'pub', 'ref', 'self', 'Self', 'static', 'struct', 'super', 'trait', 'true',
5316
5620
  'type', 'unsafe', 'use', 'where', 'abstract', 'become', 'box', 'final', 'macro', 'override', 'priv', 'typeof', 'unsized', 'virtual'],
5317
5621
  'Python': ['False', 'def', 'None', 'True', 'in', 'is', 'and', 'lambda', 'nonlocal', 'not', 'or'],
@@ -5330,7 +5634,14 @@ CodeEditor.utils = { // These ones don't have hightlight, used as suggestions to
5330
5634
  'Python': ['abs', 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'delattr', 'dict', 'dir', 'divmod',
5331
5635
  'enumerate', 'eval', 'exec', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance',
5332
5636
  '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']
5637
+ 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip'],
5638
+ 'CSS': [ ...Object.keys( document.body.style ).map( LX.toKebabCase ), 'block', 'inline', 'inline-block', 'flex', 'grid', 'none', 'inherit', 'initial', 'unset', 'revert', 'sticky',
5639
+ 'relative', 'absolute', 'fixed', 'static', 'auto', 'visible', 'hidden', 'scroll', 'clip', 'ellipsis', 'nowrap', 'wrap', 'break-word', 'solid', 'dashed', 'dotted', 'double',
5640
+ 'groove', 'ridge', 'inset', 'outset', 'left', 'right', 'center', 'top', 'bottom', 'start', 'end', 'justify', 'stretch', 'space-between', 'space-around', 'space-evenly',
5641
+ 'baseline', 'middle', 'normal', 'bold', 'lighter', 'bolder', 'italic', 'blur', 'uppercase', 'lowercase', 'capitalize', 'transparent', 'currentColor', 'pointer', 'default',
5642
+ 'move', 'grab', 'grabbing', 'not-allowed', 'none', 'cover', 'contain', 'repeat', 'no-repeat', 'repeat-x', 'repeat-y', 'round', 'space', 'linear-gradient', 'radial-gradient',
5643
+ 'conic-gradient', 'url', 'calc', 'min', 'max', 'clamp', 'red', 'blue', 'green', 'black', 'white', 'gray', 'silver', 'yellow', 'orange', 'purple', 'pink', 'cyan', 'magenta',
5644
+ 'lime', 'teal', 'navy', 'transparent', 'currentcolor', 'inherit', 'initial', 'unset', 'revert', 'none', 'auto', 'fit-content', 'min-content', 'max-content']
5334
5645
  };
5335
5646
 
5336
5647
  CodeEditor.types = {
@@ -5351,6 +5662,7 @@ CodeEditor.builtIn = {
5351
5662
  'JavaScript': ['document', 'console', 'window', 'navigator', 'performance'],
5352
5663
  'CSS': ['*', '!important'],
5353
5664
  'C++': ['vector', 'list', 'map'],
5665
+ 'WGSL': ['@vertex', '@fragment'],
5354
5666
  'HTML': ['type', 'xmlns', 'PUBLIC', 'http-equiv', 'src', 'style', 'lang', 'href', 'rel', 'content', 'xml', 'alt'], // attributes
5355
5667
  'Markdown': ['type', 'src', 'style', 'lang', 'href', 'rel', 'content', 'valign', 'alt'], // attributes
5356
5668
  'PHP': ['echo', 'print'],