lexgui 0.1.13 → 0.1.15

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.
@@ -29,6 +29,10 @@ function firstNonspaceIndex(str) {
29
29
  return str.search(/\S|$/);
30
30
  }
31
31
 
32
+ function deleteElement(el) {
33
+ if(el) el.remove();
34
+ }
35
+
32
36
  let ASYNC_ENABLED = true;
33
37
 
34
38
  function doAsync( fn, ms ) {
@@ -106,9 +110,6 @@ class CodeEditor {
106
110
 
107
111
  CodeEditor.__instances.push( this );
108
112
 
109
- this.disable_edition = options.disable_edition ?? false;
110
-
111
- this.skip_info = options.skip_info;
112
113
  this.base_area = area;
113
114
  this.area = new LX.Area( { className: "lexcodeeditor", height: "auto", no_append: true } );
114
115
 
@@ -129,14 +130,21 @@ class CodeEditor {
129
130
  this.root.tabIndex = -1;
130
131
  area.attach( this.root );
131
132
 
132
- this.root.addEventListener( 'keydown', this.processKey.bind(this), true);
133
+ this.skipCodeInfo = options.skip_info ?? false;
134
+ this.disableEdition = options.disable_edition ?? false;
135
+
136
+ if( !this.disableEdition )
137
+ {
138
+ this.root.addEventListener( 'keydown', this.processKey.bind(this), true);
139
+ this.root.addEventListener( 'focus', this.processFocus.bind(this, true) );
140
+ this.root.addEventListener( 'focusout', this.processFocus.bind(this, false) );
141
+ }
142
+
133
143
  this.root.addEventListener( 'mousedown', this.processMouse.bind(this) );
134
144
  this.root.addEventListener( 'mouseup', this.processMouse.bind(this) );
135
145
  this.root.addEventListener( 'mousemove', this.processMouse.bind(this) );
136
146
  this.root.addEventListener( 'click', this.processMouse.bind(this) );
137
147
  this.root.addEventListener( 'contextmenu', this.processMouse.bind(this) );
138
- this.root.addEventListener( 'focus', this.processFocus.bind(this, true) );
139
- this.root.addEventListener( 'focusout', this.processFocus.bind(this, false) );
140
148
 
141
149
  // Cursors and selection
142
150
 
@@ -195,50 +203,100 @@ class CodeEditor {
195
203
  this.tabSpaces = 4;
196
204
  this.maxUndoSteps = 16;
197
205
  this.lineHeight = 20;
206
+ this.defaultSingleLineCommentToken = "//";
198
207
  this.charWidth = 8; //this.measureChar();
199
208
  this._lastTime = null;
200
209
 
201
- this.languages = [
202
- 'Plain Text', 'JavaScript', 'CSS', 'GLSL', 'WGSL', 'JSON', 'XML'
203
- ];
210
+ this.pairKeys = {
211
+ "\"": "\"",
212
+ "'": "'",
213
+ "(": ")",
214
+ "{": "}",
215
+ "[": "]"
216
+ };
217
+
218
+ this.stringKeys = { // adding @ because some words are always true in (e.g. constructor..)
219
+ "@\"": "\"",
220
+ "@'": "'"
221
+ };
222
+
223
+ // Scan tokens..
224
+ setInterval( this.scanWordSuggestions.bind(this), 2000 );
225
+
226
+ this.languages = {
227
+ 'Plain Text': { },
228
+ 'JavaScript': { },
229
+ 'C++': { },
230
+ 'CSS': { },
231
+ 'GLSL': { },
232
+ 'WGSL': { },
233
+ 'JSON': { },
234
+ 'XML': { },
235
+ 'Python': { },
236
+ 'Batch': { blockComments: false, singleLineCommentToken: '::' }
237
+ };
238
+
204
239
  this.specialKeys = [
205
240
  'Backspace', 'Enter', 'ArrowUp', 'ArrowDown',
206
241
  'ArrowRight', 'ArrowLeft', 'Delete', 'Home',
207
242
  'End', 'Tab', 'Escape'
208
243
  ];
209
244
  this.keywords = {
210
- 'JavaScript': ['var', 'let', 'const', 'this', 'in', 'of', 'true', 'false', 'new', 'function', 'NaN', 'static', 'class', 'constructor', 'null', 'typeof', 'debugger'],
245
+ 'JavaScript': ['var', 'let', 'const', 'this', 'in', 'of', 'true', 'false', 'new', 'function', 'NaN', 'static', 'class', 'constructor', 'null', 'typeof', 'debugger', 'abstract',
246
+ 'arguments', 'extends', 'instanceof'],
247
+ 'C++': ['int', 'float', 'double', 'bool', 'char', 'wchar_t', 'const', 'static_cast', 'dynamic_cast', 'new', 'delete', 'void', 'true', 'false', 'auto', 'struct', 'typedef', 'nullptr',
248
+ 'NULL', 'unsigned'],
211
249
  'GLSL': ['true', 'false', 'function', 'int', 'float', 'vec2', 'vec3', 'vec4', 'mat2x2', 'mat3x3', 'mat4x4', 'struct'],
212
250
  'CSS': ['body', 'html', 'canvas', 'div', 'input', 'span', '.'],
213
251
  'WGSL': ['var', 'let', 'true', 'false', 'fn', 'bool', 'u32', 'i32', 'f16', 'f32', 'vec2f', 'vec3f', 'vec4f', 'mat2x2f', 'mat3x3f', 'mat4x4f', 'array', 'atomic', 'struct',
214
252
  'sampler', 'sampler_comparison', 'texture_depth_2d', 'texture_depth_2d_array', 'texture_depth_cube', 'texture_depth_cube_array', 'texture_depth_multisampled_2d',
215
253
  'texture_external', 'texture_1d', 'texture_2d', 'texture_2d_array', 'texture_3d', 'texture_cube', 'texture_cube_array', 'texture_storage_1d', 'texture_storage_2d',
216
- 'texture_storage_2d_array', 'texture_storage_3d']
254
+ 'texture_storage_2d_array', 'texture_storage_3d'],
255
+ 'Python': ['False', 'def', 'None', 'True', 'in', 'is', 'and', 'lambda', 'nonlocal', 'not', 'or'],
256
+ 'Batch': ['set', 'SET', 'echo', 'ECHO', 'off', 'OFF', 'del', 'DEL', 'defined', 'DEFINED', 'setlocal', 'SETLOCAL', 'enabledelayedexpansion', 'ENABLEDELAYEDEXPANSION', 'driverquery',
257
+ 'DRIVERQUERY', 'print', 'PRINT']
217
258
  };
218
259
  this.utils = { // These ones don't have hightlight, used as suggestions to autocomplete only...
219
260
  'JavaScript': ['querySelector', 'body', 'addEventListener', 'removeEventListener', 'remove', 'sort', 'keys', 'filter', 'isNaN', 'parseFloat', 'parseInt', 'EPSILON', 'isFinite',
220
- 'bind', 'prototype', 'length', 'assign', 'entries', 'values', 'concat', 'substring', 'substr', 'splice', 'slice', 'buffer', 'appendChild', 'createElement'],
221
- 'WGSL': ['textureSample']
261
+ 'bind', 'prototype', 'length', 'assign', 'entries', 'values', 'concat', 'substring', 'substr', 'splice', 'slice', 'buffer', 'appendChild', 'createElement', 'prompt',
262
+ 'alert'],
263
+ 'WGSL': ['textureSample'],
264
+ 'Python': ['abs', 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'delattr', 'dict', 'dir', 'divmod',
265
+ 'enumerate', 'eval', 'exec', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance',
266
+ 'issubclass', 'iter', 'len', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'range', 'repr',
267
+ 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']
222
268
  };
223
269
  this.types = {
224
- 'JavaScript': ['Object', 'String', 'Function', 'Boolean', 'Symbol', 'Error', 'Number']
270
+ 'JavaScript': ['Object', 'String', 'Function', 'Boolean', 'Symbol', 'Error', 'Number', 'TextEncoder', 'TextDecoder'],
271
+ 'Python': ['int', 'type', 'float', 'map', 'list', 'ArithmeticError', 'AssertionError', 'AttributeError', 'Exception', 'EOFError', 'FloatingPointError', 'GeneratorExit',
272
+ 'ImportError', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'NotImplementedError', 'OSError',
273
+ 'OverflowError', 'ReferenceError', 'RuntimeError', 'StopIteration', 'SyntaxError', 'TabError', 'SystemError', 'SystemExit', 'TypeError', 'UnboundLocalError',
274
+ 'UnicodeError', 'UnicodeEncodeError', 'UnicodeDecodeError', 'UnicodeTranslateError', 'ValueError', 'ZeroDivisionError' ],
275
+ 'C++': ['uint8_t', 'uint16_t', 'uint32_t']
225
276
  };
226
277
  this.builtin = {
227
- 'JavaScript': ['document', 'console', 'window', 'navigator'],
228
- 'CSS': ['*', '!important']
278
+ 'JavaScript': ['document', 'console', 'window', 'navigator', 'performance'],
279
+ 'CSS': ['*', '!important'],
280
+ 'C++': ['vector', 'list', 'map']
229
281
  };
230
282
  this.statementsAndDeclarations = {
231
- 'JavaScript': ['for', 'if', 'else', 'case', 'switch', 'return', 'while', 'continue', 'break', 'do', 'import',
232
- 'from', 'throw', 'async', 'try', 'catch'],
283
+ 'JavaScript': ['for', 'if', 'else', 'case', 'switch', 'return', 'while', 'continue', 'break', 'do', 'import', 'from', 'throw', 'async', 'try', 'catch', 'await'],
284
+ 'C++': ['std', 'for', 'if', 'else', 'return', 'continue', 'break', 'case', 'switch', 'while', 'glm'],
233
285
  'GLSL': ['for', 'if', 'else', 'return', 'continue', 'break'],
234
- 'WGSL': ['const','for', 'if', 'else', 'return', 'continue', 'break', 'storage', 'read', 'uniform']
286
+ 'WGSL': ['const','for', 'if', 'else', 'return', 'continue', 'break', 'storage', 'read', 'uniform'],
287
+ 'Python': ['if', 'raise', 'del', 'import', 'return', 'elif', 'try', 'else', 'while', 'as', 'except', 'with', 'assert', 'finally', 'yield', 'break', 'for', 'class', 'continue',
288
+ 'global', 'pass'],
289
+ 'Batch': ['if', 'IF', 'for', 'FOR', 'in', 'IN', 'do', 'DO', 'call', 'CALL', 'goto', 'GOTO', 'exit', 'EXIT']
235
290
  };
236
291
  this.symbols = {
237
292
  'JavaScript': ['<', '>', '[', ']', '{', '}', '(', ')', ';', '=', '|', '||', '&', '&&', '?', '??'],
293
+ 'C++': ['<', '>', '[', ']', '{', '}', '(', ')', ';', '=', '|', '||', '&', '&&', '?', '::'],
238
294
  'JSON': ['[', ']', '{', '}', '(', ')'],
239
295
  'GLSL': ['[', ']', '{', '}', '(', ')'],
240
296
  'WGSL': ['[', ']', '{', '}', '(', ')', '->'],
241
- 'CSS': ['{', '}', '(', ')', '*']
297
+ 'CSS': ['{', '}', '(', ')', '*'],
298
+ 'Python': ['<', '>', '[', ']', '(', ')', '='],
299
+ 'Batch': ['[', ']', '(', ')', '%'],
242
300
  };
243
301
 
244
302
  // Action keys
@@ -323,7 +381,7 @@ class CodeEditor {
323
381
 
324
382
  this.resetCursorPos( CodeEditor.CURSOR_LEFT );
325
383
  if(idx > 0) this.cursorToString(cursor, prestring);
326
- this._refreshCodeInfo(cursor.line + 1, cursor.position);
384
+ this._refreshCodeInfo(cursor.line, cursor.position);
327
385
  this.code.scrollLeft = 0;
328
386
 
329
387
  if( e.shiftKey && !e.cancelShift )
@@ -335,7 +393,7 @@ class CodeEditor {
335
393
  this.startSelection(cursor);
336
394
  var string = this.code.lines[ln].substring(idx, last_pos);
337
395
  this.selection.selectInline(idx, cursor.line, this.measureString(string));
338
- } else
396
+ } else if( !e.keepSelection )
339
397
  this.endSelection();
340
398
  });
341
399
 
@@ -411,7 +469,7 @@ class CodeEditor {
411
469
 
412
470
  var letter = this.getCharAtPos( cursor );
413
471
  if(!letter) {
414
- this.selection.toX = (this.code.lines[cursor.line].length - 1);
472
+ this.selection.toX = this.code.lines[cursor.line].length;
415
473
  this.cursorToPosition(cursor, this.selection.toX);
416
474
  }
417
475
 
@@ -517,13 +575,16 @@ class CodeEditor {
517
575
  }
518
576
  }
519
577
  else if( cursor.line > 0 ) {
520
-
578
+
579
+ if( e.shiftKey ) {
580
+ if(!this.selection) this.startSelection(cursor);
581
+ }
582
+
521
583
  this.lineUp( cursor );
522
584
  this.resetCursorPos( CodeEditor.CURSOR_LEFT );
523
585
  this.cursorToPosition( cursor, this.code.lines[cursor.line].length );
524
586
 
525
587
  if( e.shiftKey ) {
526
- if(!this.selection) this.startSelection(cursor);
527
588
  this.selection.toX = cursor.position;
528
589
  this.selection.toY--;
529
590
  this.processSelection(null, true);
@@ -582,17 +643,22 @@ class CodeEditor {
582
643
  }
583
644
  else if( this.code.lines[ cursor.line + 1 ] !== undefined ) {
584
645
 
646
+ if( e.shiftKey ) {
647
+ if(!this.selection) this.startSelection(cursor);
648
+ e.cancelShift = true;
649
+ e.keepSelection = true;
650
+ }
651
+
585
652
  this.lineDown( cursor );
586
- e.cancelShift = true;
587
653
  this.actions['Home'].callback(cursor.line, cursor, e);
588
- this.hideAutoCompleteBox();
589
-
654
+
590
655
  if( e.shiftKey ) {
591
- if(!this.selection) this.startSelection(cursor);
592
656
  this.selection.toX = cursor.position;
593
657
  this.selection.toY++;
594
658
  this.processSelection(null, true);
595
659
  }
660
+
661
+ this.hideAutoCompleteBox();
596
662
  }
597
663
  }
598
664
  });
@@ -621,7 +687,7 @@ class CodeEditor {
621
687
  }
622
688
 
623
689
  // This can be used to empty all text...
624
- setText( text = "" ) {
690
+ setText( text = "", lang ) {
625
691
 
626
692
  let new_lines = text.split('\n');
627
693
  this.code.lines = [].concat(new_lines);
@@ -632,6 +698,11 @@ class CodeEditor {
632
698
  this.cursorToLine(cursor, new_lines.length); // Already substracted 1
633
699
  this.cursorToPosition(cursor, lastLine.length);
634
700
  this.processLines();
701
+
702
+ if( lang )
703
+ {
704
+ this._changeLanguage( lang );
705
+ }
635
706
  }
636
707
 
637
708
  appendText( text ) {
@@ -703,8 +774,9 @@ class CodeEditor {
703
774
  this.addTab(name, true, title);
704
775
  text = text.replaceAll('\r', '');
705
776
  this.code.lines = text.split('\n');
706
- this.processLines();
707
777
  this._refreshCodeInfo();
778
+ this._changeLanguageFromExtension( LX.getExtension(name) );
779
+ this.processLines();
708
780
  };
709
781
 
710
782
  if(file.constructor == String)
@@ -713,7 +785,6 @@ class CodeEditor {
713
785
  LX.request({ url: filename, success: text => {
714
786
 
715
787
  const name = filename.substring(filename.lastIndexOf('/') + 1);
716
- this._changeLanguageFromExtension( LX.getExtension(name) );
717
788
  inner_add_tab( text, name, filename );
718
789
  } });
719
790
  }
@@ -722,7 +793,6 @@ class CodeEditor {
722
793
  const fr = new FileReader();
723
794
  fr.readAsText( file );
724
795
  fr.onload = e => {
725
- this._changeLanguageFromExtension( LX.getExtension(file.name) );
726
796
  const text = e.currentTarget.result;
727
797
  inner_add_tab( text, file.name );
728
798
  };
@@ -742,6 +812,8 @@ class CodeEditor {
742
812
  }
743
813
 
744
814
  _changeLanguage( lang ) {
815
+
816
+ this.code.lang = lang;
745
817
  this.highlight = lang;
746
818
  this._refreshCodeInfo();
747
819
  this.processLines();
@@ -749,14 +821,21 @@ class CodeEditor {
749
821
 
750
822
  _changeLanguageFromExtension( ext ) {
751
823
 
824
+ if( !ext )
825
+ return this._changeLanguage( this.code.lang );
826
+
752
827
  switch(ext.toLowerCase())
753
828
  {
754
829
  case 'js': return this._changeLanguage('JavaScript');
830
+ case 'cpp': return this._changeLanguage('C++');
831
+ case 'h': return this._changeLanguage('C++');
755
832
  case 'glsl': return this._changeLanguage('GLSL');
756
833
  case 'css': return this._changeLanguage('CSS');
757
834
  case 'json': return this._changeLanguage('JSON');
758
835
  case 'xml': return this._changeLanguage('XML');
759
836
  case 'wgsl': return this._changeLanguage('WGSL');
837
+ case 'py': return this._changeLanguage('Python');
838
+ case 'bat': return this._changeLanguage('Batch');
760
839
  case 'txt':
761
840
  default:
762
841
  this._changeLanguage('Plain Text');
@@ -765,23 +844,23 @@ class CodeEditor {
765
844
 
766
845
  _createPanelInfo() {
767
846
 
768
- if( !this.skip_info )
847
+ if( !this.skipCodeInfo )
769
848
  {
770
849
  let panel = new LX.Panel({ className: "lexcodetabinfo", width: "calc(100%)", height: "auto" });
771
850
  panel.ln = 0;
772
851
  panel.col = 0;
773
852
 
774
853
  this._refreshCodeInfo = (ln = panel.ln, col = panel.col) => {
775
- panel.ln = ln;
776
- panel.col = col;
854
+ panel.ln = ln + 1;
855
+ panel.col = col + 1;
777
856
  panel.clear();
778
857
  panel.sameLine();
779
858
  panel.addLabel(this.code.title, { float: 'right' });
780
- panel.addLabel("Ln " + ln, { width: "64px" });
781
- panel.addLabel("Col " + col, { width: "64px" });
859
+ panel.addLabel("Ln " + panel.ln, { width: "64px" });
860
+ panel.addLabel("Col " + panel.col, { width: "64px" });
782
861
  panel.addButton("<b>{ }</b>", this.highlight, (value, event) => {
783
862
  LX.addContextMenu( "Language", event, m => {
784
- for( const lang of this.languages )
863
+ for( const lang of Object.keys(this.languages) )
785
864
  m.add( lang, this._changeLanguage.bind(this) );
786
865
  });
787
866
  }, { width: "25%", nameWidth: "15%" });
@@ -829,6 +908,7 @@ class CodeEditor {
829
908
  let code = document.createElement('div');
830
909
  code.className = 'code';
831
910
  code.lines = [""];
911
+ code.lang = "Plain Text";
832
912
  code.cursorState = {};
833
913
  code.undoSteps = [];
834
914
  code.tabName = name;
@@ -882,7 +962,7 @@ class CodeEditor {
882
962
  this.restoreCursor(cursor, this.code.cursorState);
883
963
  this.endSelection();
884
964
  this._changeLanguageFromExtension( LX.getExtension(tabname) );
885
- this._refreshCodeInfo(cursor.line + 1, cursor.position);
965
+ this._refreshCodeInfo(cursor.line, cursor.position);
886
966
 
887
967
  // Restore scroll
888
968
  this.gutter.scrollTop = this.code.scrollTop;
@@ -1012,15 +1092,18 @@ class CodeEditor {
1012
1092
  return;
1013
1093
 
1014
1094
  LX.addContextMenu( null, e, m => {
1015
- m.add( "Format/JSON", () => {
1016
- let json = this.toJSONFormat(this.getText());
1017
- this.code.lines = json.split("\n");
1018
- this.processLines();
1019
- } );
1020
- m.add( "" )
1021
- m.add( "Cut", () => { this._cutContent(); } );
1022
1095
  m.add( "Copy", () => { this._copyContent(); } );
1023
- m.add( "Paste", () => { this._pasteContent(); } );
1096
+ if( !this.disableEdition )
1097
+ {
1098
+ m.add( "Cut", () => { this._cutContent(); } );
1099
+ m.add( "Paste", () => { this._pasteContent(); } );
1100
+ m.add( "" );
1101
+ m.add( "Format/JSON", () => {
1102
+ let json = this.toJSONFormat(this.getText());
1103
+ this.code.lines = json.split("\n");
1104
+ this.processLines();
1105
+ } );
1106
+ }
1024
1107
  });
1025
1108
 
1026
1109
  this.canOpenContextMenu = false;
@@ -1048,7 +1131,7 @@ class CodeEditor {
1048
1131
  this.hideAutoCompleteBox();
1049
1132
 
1050
1133
  if(!skip_refresh)
1051
- this._refreshCodeInfo( ln + 1, cursor.position );
1134
+ this._refreshCodeInfo( ln, cursor.position );
1052
1135
  }
1053
1136
 
1054
1137
  processSelection( e, keep_range ) {
@@ -1078,7 +1161,7 @@ class CodeEditor {
1078
1161
  if( deltaY >= 0 )
1079
1162
  {
1080
1163
  while( deltaY < (this.selections.childElementCount - 1) )
1081
- this.selections.lastChild.remove();
1164
+ deleteElement( this.selections.lastChild );
1082
1165
 
1083
1166
  for(let i = fromY; i <= toY; i++){
1084
1167
 
@@ -1121,7 +1204,7 @@ class CodeEditor {
1121
1204
  else // Selection goes up...
1122
1205
  {
1123
1206
  while( Math.abs(deltaY) < (this.selections.childElementCount - 1) )
1124
- this.selections.firstChild.remove();
1207
+ deleteElement( this.selections.firstChild );
1125
1208
 
1126
1209
  for(let i = toY; i <= fromY; i++){
1127
1210
 
@@ -1303,36 +1386,33 @@ class CodeEditor {
1303
1386
  lidx = cursor.line; // Update this, since it's from the old code
1304
1387
  }
1305
1388
 
1306
- // Append key
1389
+ // Append key
1307
1390
 
1308
- this.code.lines[lidx] = [
1309
- this.code.lines[lidx].slice(0, cursor.position),
1310
- key,
1311
- this.code.lines[lidx].slice(cursor.position)
1312
- ].join('');
1391
+ const isPairKey = (Object.values( this.pairKeys ).indexOf( key ) > -1) && !this.wasKeyPaired;
1392
+ const sameKeyNext = isPairKey && (this.code.lines[lidx][cursor.position] === key);
1393
+
1394
+ if( !sameKeyNext )
1395
+ {
1396
+ this.code.lines[lidx] = [
1397
+ this.code.lines[lidx].slice(0, cursor.position),
1398
+ key,
1399
+ this.code.lines[lidx].slice(cursor.position)
1400
+ ].join('');
1401
+ }
1313
1402
 
1314
1403
  this.cursorToRight( key );
1315
1404
 
1316
1405
  // Some custom cases for auto key pair (), {}, "", '', ...
1317
-
1318
- const pairKeys = ["\"", "'", "(", "{"];
1319
- if( pairKeys.indexOf( key ) > -1 && !this.wasKeyPaired )
1406
+
1407
+ const keyMustPair = (this.pairKeys[ key ] !== undefined);
1408
+ if( keyMustPair && !this.wasKeyPaired )
1320
1409
  {
1321
- // Find pair key
1322
- let pair = key;
1323
- switch(key)
1324
- {
1325
- case "'":
1326
- case "\"":
1327
- break;
1328
- case "(": pair = ")"; break;
1329
- case "{": pair = "}"; break;
1330
- }
1331
-
1332
1410
  // Make sure to detect later that the key is paired automatically to avoid loops...
1333
1411
  this.wasKeyPaired = true;
1334
1412
 
1335
- this.root.dispatchEvent(new KeyboardEvent('keydown', { 'key': pair }));
1413
+ if(sameKeyNext) return;
1414
+
1415
+ this.root.dispatchEvent(new KeyboardEvent('keydown', { 'key': this.pairKeys[ key ] }));
1336
1416
  this.cursorToLeft( key, cursor );
1337
1417
  return;
1338
1418
  }
@@ -1424,68 +1504,143 @@ class CodeEditor {
1424
1504
  };
1425
1505
  }
1426
1506
 
1427
- processLines( from ) {
1507
+ scanWordSuggestions() {
1428
1508
 
1429
- if( !from )
1430
- {
1431
- this.gutter.innerHTML = "";
1432
- this.code.innerHTML = "";
1433
- this.code.tokens = {};
1434
- }
1509
+ this.code.tokens = {};
1435
1510
 
1436
- for( let i = from ?? 0; i < this.code.lines.length; ++i )
1511
+ for( let i = 0; i < this.code.lines.length; ++i )
1437
1512
  {
1438
- this.processLine( i );
1513
+ const linestring = this.code.lines[ i ];
1514
+ const tokens = this._getTokensFromString( linestring, true );
1515
+ tokens.forEach( t => this.code.tokens[ t ] = 1 );
1439
1516
  }
1517
+ }
1440
1518
 
1441
- // Clean up...
1442
- if( from )
1519
+ processLines( from ) {
1520
+
1521
+ // const start = performance.now();
1522
+
1523
+ this.gutter.innerHTML = "";
1524
+ this.code.innerHTML = "";
1525
+
1526
+ for( let i = 0; i < this.code.lines.length; ++i )
1443
1527
  {
1444
- while( this.code.lines.length != this.gutter.children.length )
1445
- this.gutter.lastChild.remove();
1446
- while( this.code.lines.length != this.code.children.length )
1447
- this.code.lastChild.remove();
1528
+ this.processLine( i, true );
1448
1529
  }
1449
1530
 
1531
+ // console.log( performance.now() - start );
1450
1532
  }
1451
1533
 
1452
- processLine( linenum ) {
1534
+ processLine( linenum, force ) {
1453
1535
 
1454
- delete this._building_string; // multi-line strings not supported by now
1536
+ delete this._buildingString; // multi-line strings not supported by now
1455
1537
 
1456
1538
  // It's allowed to process only 1 line to optimize
1457
1539
  let linestring = this.code.lines[ linenum ];
1458
- var _lines = this.code.querySelectorAll('pre');
1459
- var pre = null, single_update = false;
1460
- if( _lines[linenum] ) {
1461
- pre = _lines[linenum];
1462
- single_update = true;
1463
- }
1464
-
1465
- if(!pre)
1540
+
1541
+ var pre = document.createElement('pre');
1542
+ pre.dataset['linenum'] = linenum;
1543
+
1544
+ // Line gutter
1545
+ var linenumspan = document.createElement('span');
1546
+ linenumspan.innerHTML = (linenum + 1);
1547
+
1548
+ if( force )
1466
1549
  {
1467
- var pre = document.createElement('pre');
1468
- pre.dataset['linenum'] = linenum;
1469
- this.code.appendChild(pre);
1470
- }
1471
- else
1550
+ this.gutter.appendChild(linenumspan);
1551
+ this.code.appendChild( pre );
1552
+ } else
1472
1553
  {
1473
- pre.children[0].remove(); // Remove token list
1554
+ deleteElement( this.gutter.childNodes[ linenum ] );
1555
+ deleteElement( this.code.childNodes[ linenum ] );
1556
+ this.gutter.insertChildAtIndex( linenumspan, linenum );
1557
+ this.code.insertChildAtIndex( pre, linenum );
1474
1558
  }
1475
1559
 
1476
1560
  var linespan = document.createElement('span');
1477
1561
  pre.appendChild(linespan);
1478
1562
 
1563
+ const tokensToEvaluate = this._getTokensFromString( linestring );
1564
+
1565
+ let line_inner_html = "";
1566
+
1567
+ // Process all tokens
1568
+ for( var i = 0; i < tokensToEvaluate.length; ++i )
1569
+ {
1570
+ let it = i - 1;
1571
+ let prev = tokensToEvaluate[it];
1572
+ while( prev == ' ' ) {
1573
+ it--;
1574
+ prev = tokensToEvaluate[it];
1575
+ }
1576
+
1577
+ it = i + 1;
1578
+ let next = tokensToEvaluate[it];
1579
+ while( next == ' ' || next == '"') {
1580
+ it++;
1581
+ next = tokensToEvaluate[it];
1582
+ }
1583
+
1584
+ let token = tokensToEvaluate[i];
1585
+
1586
+ if( this.languages[ this.highlight ].blockComments ?? true )
1587
+ {
1588
+ if( token.substr(0, 2) == '/*' )
1589
+ this._buildingBlockComment = true;
1590
+ if( token.substr(token.length - 2) == '*/' )
1591
+ delete this._buildingBlockComment;
1592
+ }
1593
+
1594
+ line_inner_html += this.evaluateToken(token, prev, next);
1595
+ }
1596
+
1597
+ linespan.innerHTML = line_inner_html;
1598
+ }
1599
+
1600
+ _processTokens( tokens, offset = 0 ) {
1601
+
1602
+ if( this.highlight == 'C++' )
1603
+ {
1604
+ var idx = tokens.slice(offset).findIndex( (value, index) => this.isNumber(value) );
1605
+ if( idx > -1 )
1606
+ {
1607
+ idx += offset; // Add offset to compute within the whole array of tokens
1608
+ let data = tokens[idx] + tokens[++idx];
1609
+ while( this.isNumber( data ) )
1610
+ {
1611
+ tokens[ idx - 1 ] += tokens[ idx ];
1612
+ tokens.splice( idx, 1 );
1613
+ data += tokens[idx];
1614
+ }
1615
+ // Scan for numbers again
1616
+ return this._processTokens( tokens, idx );
1617
+ }
1618
+ }
1619
+
1620
+ return tokens;
1621
+ }
1622
+
1623
+ _getTokensFromString( linestring, skipNonWords ) {
1624
+
1479
1625
  // Check if line comment
1480
- const is_comment = linestring.split('//');
1626
+ const singleLineCommentToken = this.languages[ this.highlight ].singleLineCommentToken ?? this.defaultSingleLineCommentToken;
1627
+ const is_comment = linestring.split(singleLineCommentToken);
1481
1628
  linestring = ( is_comment.length > 1 ) ? is_comment[0] : linestring;
1482
1629
 
1483
1630
  const tokens = linestring.split(' ').join('¬ ¬').split('¬'); // trick to split without losing spaces
1484
- const to_process = []; // store in a temp array so we know prev and next tokens...
1631
+ let tokensToEvaluate = []; // store in a temp array so we know prev and next tokens...
1485
1632
 
1486
1633
  for( let t of tokens )
1487
1634
  {
1488
- let iter = t.matchAll(/[\[\](){}<>.,;:"']/g);
1635
+ if( !t.length || (skipNonWords && ( t.includes('"') || t.length < 3 )) )
1636
+ continue;
1637
+
1638
+ if( t == ' ' ) {
1639
+ tokensToEvaluate.push( t );
1640
+ continue;
1641
+ }
1642
+
1643
+ let iter = t.matchAll(/(::|[\[\](){}<>.,;:"'])/g);
1489
1644
  let subtokens = iter.next();
1490
1645
  if( subtokens.value )
1491
1646
  {
@@ -1493,56 +1648,23 @@ class CodeEditor {
1493
1648
  while( subtokens.value != undefined )
1494
1649
  {
1495
1650
  const _pt = t.substring(idx, subtokens.value.index);
1496
- to_process.push( _pt );
1497
- to_process.push( subtokens.value[0] );
1498
- idx = subtokens.value.index + 1;
1651
+ if( _pt.length ) tokensToEvaluate.push( _pt );
1652
+ tokensToEvaluate.push( subtokens.value[0] );
1653
+ idx = subtokens.value.index + subtokens.value[0].length;
1499
1654
  subtokens = iter.next();
1500
1655
  if(!subtokens.value) {
1501
1656
  const _at = t.substring(idx);
1502
- to_process.push( _at );
1657
+ if( _at.length ) tokensToEvaluate.push( _at );
1503
1658
  }
1504
1659
  }
1505
1660
  }
1506
- else
1507
- to_process.push( t );
1661
+ else tokensToEvaluate.push( t );
1508
1662
  }
1509
1663
 
1510
- if( is_comment.length > 1 )
1511
- to_process.push( "//" + is_comment[1] );
1512
-
1513
- // Process all tokens
1514
- for( var i = 0; i < to_process.length; ++i )
1515
- {
1516
- let it = i - 1;
1517
- let prev = to_process[it];
1518
- while( prev == '' || prev == ' ' ) {
1519
- it--;
1520
- prev = to_process[it];
1521
- }
1664
+ if( is_comment.length > 1 && !skipNonWords )
1665
+ tokensToEvaluate.push( singleLineCommentToken + is_comment[1] );
1522
1666
 
1523
- it = i + 1;
1524
- let next = to_process[it];
1525
- while( next == '' || next == ' ' || next == '"') {
1526
- it++;
1527
- next = to_process[it];
1528
- }
1529
-
1530
- let token = to_process[i];
1531
- if( token.substr(0, 2) == '/*' )
1532
- this._building_block_comment = true;
1533
- if( token.substr(token.length - 2) == '*/' )
1534
- delete this._building_block_comment;
1535
-
1536
- this.processToken(token, linespan, prev, next);
1537
- }
1538
-
1539
- // add line gutter
1540
- if(!single_update)
1541
- {
1542
- var linenumspan = document.createElement('span');
1543
- linenumspan.innerHTML = (linenum + 1);
1544
- this.gutter.appendChild(linenumspan);
1545
- }
1667
+ return this._processTokens( tokensToEvaluate );
1546
1668
  }
1547
1669
 
1548
1670
  _mustHightlightWord( token, kindArray ) {
@@ -1550,88 +1672,97 @@ class CodeEditor {
1550
1672
  return kindArray[this.highlight] && kindArray[this.highlight].indexOf(token) > -1;
1551
1673
  }
1552
1674
 
1553
- processToken(token, linespan, prev, next) {
1675
+ evaluateToken( token, prev, next ) {
1554
1676
 
1555
- let sString = false;
1556
- let highlight = this.highlight.replace(/\s/g, '').toLowerCase();
1677
+ let stringEnded = false;
1678
+ let highlight = this.highlight.replace(/\s/g, '').replaceAll("+", "p").toLowerCase();
1679
+ let inner_html = "", token_classname = "";
1680
+
1681
+ const singleLineCommentToken = this.languages[ this.highlight ].singleLineCommentToken ?? this.defaultSingleLineCommentToken;
1682
+ const usesBlockComments = this.languages[ this.highlight ].blockComments ?? true;
1683
+ const customStringKeys = Object.assign( {}, this.stringKeys );
1684
+
1685
+ if( highlight == 'cpp' && prev && prev.includes('#') ) // preprocessor code..
1686
+ {
1687
+ customStringKeys['@<'] = '>';
1688
+ }
1557
1689
 
1558
- if(token == '"' || token == "'")
1690
+ // Manage strings
1691
+ if( this._buildingString != undefined )
1559
1692
  {
1560
- sString = (this._building_string == token); // stop string if i was building it
1561
- this._building_string = this._building_string ? this._building_string : token;
1693
+ const idx = Object.values(customStringKeys).indexOf( token );
1694
+ stringEnded = (idx > -1) && (idx == Object.values(customStringKeys).indexOf( customStringKeys[ '@' + this._buildingString ] ));
1695
+ }
1696
+ else if( customStringKeys[ '@' + token ] )
1697
+ {
1698
+ // Start new string
1699
+ this._buildingString = token;
1562
1700
  }
1563
1701
 
1564
1702
  if(token == ' ')
1565
1703
  {
1566
- linespan.innerHTML += token;
1704
+ inner_html += token;
1567
1705
  }
1568
1706
  else
1569
1707
  {
1570
- var span = document.createElement('span');
1571
- span.innerHTML = token;
1572
-
1573
- if( this._building_block_comment )
1574
- span.classList.add("cm-com");
1708
+ if( this._buildingBlockComment != undefined )
1709
+ token_classname += "cm-com";
1575
1710
 
1576
- else if( this._building_string )
1577
- span.classList.add("cm-str");
1711
+ else if( this._buildingString != undefined )
1712
+ token_classname += "cm-str";
1578
1713
 
1579
1714
  else if( this._mustHightlightWord( token, this.keywords ) )
1580
- span.classList.add("cm-kwd");
1715
+ token_classname += "cm-kwd";
1581
1716
 
1582
1717
  else if( this._mustHightlightWord( token, this.builtin ) )
1583
- span.classList.add("cm-bln");
1718
+ token_classname += "cm-bln";
1584
1719
 
1585
1720
  else if( this._mustHightlightWord( token, this.statementsAndDeclarations ) )
1586
- span.classList.add("cm-std");
1721
+ token_classname += "cm-std";
1587
1722
 
1588
- else if( this._mustHightlightWord( token, this.symbols ) )
1589
- span.classList.add("cm-sym");
1723
+ else if( this._mustHightlightWord( token, this.symbols ) )
1724
+ token_classname += "cm-sym";
1590
1725
 
1591
- else if( token.substr(0, 2) == '//' )
1592
- span.classList.add("cm-com");
1726
+ else if( token.substr(0, 2) == singleLineCommentToken )
1727
+ token_classname += "cm-com";
1593
1728
 
1594
- else if( token.substr(0, 2) == '/*' )
1595
- span.classList.add("cm-com");
1729
+ else if( usesBlockComments && token.substr(0, 2) == '/*' )
1730
+ token_classname += "cm-com";
1596
1731
 
1597
- else if( token.substr(token.length - 2) == '*/' )
1598
- span.classList.add("cm-com");
1732
+ else if( usesBlockComments && token.substr(token.length - 2) == '*/' )
1733
+ token_classname += "cm-com";
1599
1734
 
1600
1735
  else if( this.isNumber(token) || this.isNumber( token.replace(/[px]|[em]|%/g,'') ) )
1601
- span.classList.add("cm-dec");
1736
+ token_classname += "cm-dec";
1602
1737
 
1603
1738
  else if( this.isCSSClass(token, prev, next) )
1604
- span.classList.add("cm-kwd");
1739
+ token_classname += "cm-kwd";
1605
1740
 
1606
- else if ( this.isType(token, prev, next) ) {
1607
- span.classList.add("cm-typ");
1608
- this.code.tokens[ token ] = CodeEditor.WORD_TYPE_CLASS;
1609
- }
1741
+ else if ( this.isType(token, prev, next) )
1742
+ token_classname += "cm-typ";
1610
1743
 
1611
- else if ( token[0] != '@' && next == '(' ) {
1612
- span.classList.add("cm-mtd");
1613
- this.code.tokens[ token ] = CodeEditor.WORD_TYPE_METHOD;
1614
- }
1744
+ else if ( token[0] != '@' && next == '(' )
1745
+ token_classname += "cm-mtd";
1746
+
1747
+ else if ( highlight == 'cpp' && token.includes('#') ) // C++ preprocessor
1748
+ token_classname += "cm-ppc";
1749
+
1750
+ else if ( highlight == 'cpp' && prev != 'WDWD:' && next == '::' ) // C++ Class
1751
+ token_classname += "cm-typ";
1615
1752
 
1616
1753
  else if ( highlight == 'css' && prev == ':' && (next == ';' || next == '!important') ) // CSS value
1617
- span.classList.add("cm-str");
1754
+ token_classname += "cm-str";
1618
1755
 
1619
1756
  else if ( highlight == 'css' && prev == undefined && next == ':' ) // CSS attribute
1620
- span.classList.add("cm-typ");
1621
- else {
1622
-
1623
- if( token.length > 1 )
1624
- {
1625
- // Store in token map to show later as autocomplete suggestions
1626
- this.code.tokens[ token ] = -1;
1627
- }
1628
- }
1757
+ token_classname += "cm-typ";
1629
1758
 
1630
- span.classList.add(highlight);
1631
- linespan.appendChild(span);
1759
+ token_classname += " " + highlight;
1760
+ inner_html += "<span class=' " + token_classname + "'>" + token + "</span>";
1632
1761
  }
1633
1762
 
1634
- if(sString) delete this._building_string;
1763
+ if(stringEnded) delete this._buildingString;
1764
+
1765
+ return inner_html;
1635
1766
  }
1636
1767
 
1637
1768
  isCSSClass( token, prev, next ) {
@@ -1639,7 +1770,16 @@ class CodeEditor {
1639
1770
  }
1640
1771
 
1641
1772
  isNumber( token ) {
1642
- return token.length && !Number.isNaN(+token);
1773
+
1774
+ if(this.highlight == 'C++')
1775
+ {
1776
+ if( token.lastChar == 'f' )
1777
+ return this.isNumber( token.substring(0, token.length - 1) )
1778
+ else if( token.lastChar == 'u' )
1779
+ return !(token.includes('.')) && this.isNumber( token.substring(0, token.length - 1) );
1780
+ }
1781
+
1782
+ return token.length && token != ' ' && !Number.isNaN(+token);
1643
1783
  }
1644
1784
 
1645
1785
  isType( token, prev, next ) {
@@ -1652,6 +1792,10 @@ class CodeEditor {
1652
1792
  {
1653
1793
  return (prev == 'class' && next == '{') || (prev == 'new' && next == '(');
1654
1794
  }
1795
+ else if( this.highlight == 'C++' )
1796
+ {
1797
+ return (prev == 'class' && next == '{') || (prev == 'struct' && next == '{');
1798
+ }
1655
1799
  else if ( this.highlight == 'WGSL' )
1656
1800
  {
1657
1801
  const is_kwd = (this.keywords[this.highlight] && this.keywords[this.highlight].indexOf(token) == -1);
@@ -1667,7 +1811,7 @@ class CodeEditor {
1667
1811
  if( !this.selection || (this.selection.fromY != this.selection.toY) )
1668
1812
  return false;
1669
1813
 
1670
- const _lastLeft = cursor._left;
1814
+ this.selection.invertIfNecessary();
1671
1815
 
1672
1816
  // Insert first..
1673
1817
  this.code.lines[lidx] = [
@@ -1752,8 +1896,10 @@ class CodeEditor {
1752
1896
 
1753
1897
  deleteSelection( cursor ) {
1754
1898
 
1755
- if(this.disable_edition)
1899
+ // I think it's not necessary but...
1900
+ if(this.disableEdition)
1756
1901
  return;
1902
+
1757
1903
  // Some selections don't depend on mouse up..
1758
1904
  if(this.selection) this.selection.invertIfNecessary();
1759
1905
 
@@ -1796,7 +1942,7 @@ class CodeEditor {
1796
1942
  cursor.style.left = "calc(" + (cursor._left - this.getScrollLeft()) + "px + " + this.xPadding + ")";
1797
1943
  cursor.position++;
1798
1944
  this.restartBlink();
1799
- this._refreshCodeInfo( cursor.line + 1, cursor.position );
1945
+ this._refreshCodeInfo( cursor.line, cursor.position );
1800
1946
 
1801
1947
  // Add horizontal scroll
1802
1948
 
@@ -1817,7 +1963,7 @@ class CodeEditor {
1817
1963
  cursor.position--;
1818
1964
  cursor.position = Math.max(cursor.position, 0);
1819
1965
  this.restartBlink();
1820
- this._refreshCodeInfo( cursor.line + 1, cursor.position );
1966
+ this._refreshCodeInfo( cursor.line, cursor.position );
1821
1967
 
1822
1968
  doAsync(() => {
1823
1969
  var first_char = (this.code.scrollLeft / this.charWidth)|0;
@@ -1837,7 +1983,7 @@ class CodeEditor {
1837
1983
  if(resetLeft)
1838
1984
  this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
1839
1985
 
1840
- this._refreshCodeInfo( cursor.line + 1, cursor.position );
1986
+ this._refreshCodeInfo( cursor.line, cursor.position );
1841
1987
 
1842
1988
  doAsync(() => {
1843
1989
  var first_line = (this.code.scrollTop / this.lineHeight)|0;
@@ -1856,7 +2002,7 @@ class CodeEditor {
1856
2002
  if(resetLeft)
1857
2003
  this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
1858
2004
 
1859
- this._refreshCodeInfo( cursor.line + 1, cursor.position );
2005
+ this._refreshCodeInfo( cursor.line, cursor.position );
1860
2006
 
1861
2007
  doAsync(() => {
1862
2008
  var last_line = ((this.code.scrollTop + this.code.offsetHeight) / this.lineHeight)|0;
@@ -1970,7 +2116,7 @@ class CodeEditor {
1970
2116
  const words = this.code.lines[col];
1971
2117
 
1972
2118
  const is_char = (char) => {
1973
- const exceptions = ['_'];
2119
+ const exceptions = ['_', '#', '!'];
1974
2120
  const code = char.charCodeAt(0);
1975
2121
  return (exceptions.indexOf(char) > - 1) || (code > 47 && code < 58) || (code > 64 && code < 91) || (code > 96 && code < 123);
1976
2122
  }
@@ -1998,7 +2144,7 @@ class CodeEditor {
1998
2144
  test.innerHTML = char;
1999
2145
  document.body.appendChild(test);
2000
2146
  var rect = test.getBoundingClientRect();
2001
- test.remove();
2147
+ deleteElement( test );
2002
2148
  const bb = [Math.floor(rect.width), Math.floor(rect.height)];
2003
2149
  return get_bb ? bb : bb[0];
2004
2150
  }
@@ -2073,10 +2219,10 @@ class CodeEditor {
2073
2219
  );
2074
2220
 
2075
2221
  // Add words in current tab plus remove current word
2076
- // suggestions = suggestions.concat( Object.keys(this.code.tokens).filter( a => a != word ) );
2222
+ suggestions = suggestions.concat( Object.keys(this.code.tokens).filter( a => a != word ) );
2077
2223
 
2078
- // Remove single chars and duplicates...
2079
- suggestions = suggestions.filter( (value, index) => value.length > 1 && suggestions.indexOf(value) === index );
2224
+ // Remove 1/2 char words and duplicates...
2225
+ suggestions = suggestions.filter( (value, index) => value.length > 2 && suggestions.indexOf(value) === index );
2080
2226
 
2081
2227
  // Order...
2082
2228
  suggestions = suggestions.sort( (a, b) => a.localeCompare(b) );