lexgui 0.1.16 → 0.1.17

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.
@@ -13,23 +13,23 @@ function flushCss(element) {
13
13
  element.offsetHeight;
14
14
  }
15
15
 
16
- function swapElements (obj, a, b) {
16
+ function swapElements( obj, a, b ) {
17
17
  [obj[a], obj[b]] = [obj[b], obj[a]];
18
18
  }
19
19
 
20
- function swapArrayElements (array, id0, id1) {
20
+ function swapArrayElements( array, id0, id1 ) {
21
21
  [array[id0], array[id1]] = [array[id1], array[id0]];
22
22
  };
23
23
 
24
- function sliceChar(str, idx) {
24
+ function sliceChar( str, idx ) {
25
25
  return str.substr(0, idx) + str.substr(idx + 1);
26
26
  }
27
27
 
28
- function firstNonspaceIndex(str) {
28
+ function firstNonspaceIndex( str ) {
29
29
  return str.search(/\S|$/);
30
30
  }
31
31
 
32
- function deleteElement(el) {
32
+ function deleteElement( el ) {
33
33
  if(el) el.remove();
34
34
  }
35
35
 
@@ -42,9 +42,9 @@ function doAsync( fn, ms ) {
42
42
  fn();
43
43
  }
44
44
 
45
- class ISelection {
45
+ class CodeSelection {
46
46
 
47
- constructor(editor, ix, iy) {
47
+ constructor( editor, ix, iy ) {
48
48
 
49
49
  this.editor = editor;
50
50
  this.chars = 0;
@@ -66,25 +66,83 @@ class ISelection {
66
66
  swapElements(this, 'fromY', 'toY');
67
67
  }
68
68
 
69
- selectInline(x, y, width) {
69
+ selectInline( x, y, width ) {
70
70
 
71
71
  this.chars = width / this.editor.charWidth;
72
72
  this.fromX = x;
73
73
  this.toX = x + this.chars;
74
74
  this.fromY = this.toY = y;
75
75
 
76
- var domEl = document.createElement('div');
76
+ var domEl = document.createElement( 'div' );
77
77
  domEl.className = "lexcodeselection";
78
78
 
79
- domEl._top = 4 + y * this.editor.lineHeight;
80
- domEl.style.top = (domEl._top - this.editor.getScrollTop()) + "px";
79
+ domEl._top = y * this.editor.lineHeight;
80
+ domEl.style.top = domEl._top + "px";
81
81
  domEl._left = x * this.editor.charWidth;
82
- domEl.style.left = "calc(" + (domEl._left - this.editor.getScrollLeft()) + "px + " + this.editor.xPadding + ")";
82
+ domEl.style.left = "calc(" + domEl._left + "px + " + this.editor.xPadding + ")";
83
83
  domEl.style.width = width + "px";
84
84
  this.editor.selections.appendChild(domEl);
85
85
  }
86
86
  };
87
87
 
88
+ class ScrollBar {
89
+
90
+ static SCROLLBAR_VERTICAL = 1;
91
+ static SCROLLBAR_HORIZONTAL = 2;
92
+
93
+ constructor( editor, type ) {
94
+
95
+ this.editor = editor;
96
+ this.type = type;
97
+
98
+ this.root = document.createElement( 'div' );
99
+ this.root.className = "lexcodescrollbar";
100
+ if( type & ScrollBar.SCROLLBAR_HORIZONTAL )
101
+ this.root.classList.add( 'horizontal' );
102
+
103
+ this.thumb = document.createElement( 'div' );
104
+ this.thumb._top = 0;
105
+ this.thumb._left = 0;
106
+ this.root.appendChild( this.thumb );
107
+
108
+ this.thumb.addEventListener( "mousedown", inner_mousedown );
109
+
110
+ this.lastPosition = new LX.vec2( 0, 0 );
111
+
112
+ let that = this;
113
+
114
+ function inner_mousedown( e )
115
+ {
116
+ var doc = editor.root.ownerDocument;
117
+ doc.addEventListener( "mousemove",inner_mousemove );
118
+ doc.addEventListener( "mouseup",inner_mouseup );
119
+ that.lastPosition.set( e.x, e.y );
120
+ e.stopPropagation();
121
+ e.preventDefault();
122
+ }
123
+
124
+ function inner_mousemove( e )
125
+ {
126
+ var dt = that.lastPosition.sub( new LX.vec2( e.x, e.y ) );
127
+ if( that.type & ScrollBar.SCROLLBAR_VERTICAL )
128
+ editor.updateVerticalScrollFromScrollBar( dt.y )
129
+ else
130
+ editor.updateHorizontalScrollFromScrollBar( dt.x )
131
+ that.lastPosition.set( e.x, e.y );
132
+ e.stopPropagation();
133
+ e.preventDefault();
134
+ }
135
+
136
+ function inner_mouseup( e )
137
+ {
138
+ var doc = editor.root.ownerDocument;
139
+ doc.removeEventListener( "mousemove", inner_mousemove );
140
+ doc.removeEventListener( "mouseup", inner_mouseup );
141
+ }
142
+ }
143
+
144
+ }
145
+
88
146
  /**
89
147
  * @class CodeEditor
90
148
  */
@@ -96,6 +154,9 @@ class CodeEditor {
96
154
  static CURSOR_LEFT = 1;
97
155
  static CURSOR_TOP = 2;
98
156
 
157
+ static KEEP_VISIBLE_LINES = 1;
158
+ static UPDATE_VISIBLE_LINES = 2;
159
+
99
160
  static WORD_TYPE_METHOD = 0;
100
161
  static WORD_TYPE_CLASS = 1;
101
162
 
@@ -106,35 +167,14 @@ class CodeEditor {
106
167
 
107
168
  constructor( area, options = {} ) {
108
169
 
109
- // var a = [];
110
-
111
- // var map = {};
112
-
113
- // for( var i = 0; i < 1000000; ++i )
114
- // map[i] = 1;
115
-
116
- // const start = performance.now();
117
-
118
- // for( var i = 0; i < 3000; ++i ) {
119
- // const b = map[Math.floor( Math.random() * 1000000 )];
120
- // }
121
-
122
- // console.log( performance.now() - start );
123
-
124
-
125
- // debugger;
126
-
127
-
128
170
  window.editor = this;
129
171
 
130
172
  CodeEditor.__instances.push( this );
131
173
 
132
- var that = this;
133
-
134
174
  this.base_area = area;
135
175
  this.area = new LX.Area( { className: "lexcodeeditor", height: "auto", no_append: true } );
136
176
 
137
- this.tabs = this.area.addTabs( { onclose: (name) => delete this.openedTabs[name] } );
177
+ this.tabs = this.area.addTabs( { onclose: (name) => delete this.openedTabs[ name ] } );
138
178
  this.tabs.root.addEventListener( 'dblclick', (e) => {
139
179
  if( options.allow_add_scripts ?? true ) {
140
180
  e.preventDefault();
@@ -142,10 +182,11 @@ class CodeEditor {
142
182
  }
143
183
  } );
144
184
 
185
+ // Full editor
145
186
  area.root.classList.add('codebasearea');
146
- this.gutter = document.createElement('div');
147
- this.gutter.className = "lexcodegutter";
148
- area.attach( this.gutter );
187
+
188
+ // Code area
189
+ this.tabs.area.root.classList.add( 'codetabsarea' );
149
190
 
150
191
  this.root = this.area.root;
151
192
  this.root.tabIndex = -1;
@@ -156,9 +197,9 @@ class CodeEditor {
156
197
 
157
198
  if( !this.disableEdition )
158
199
  {
159
- this.root.addEventListener( 'keydown', this.processKey.bind(this), true);
160
- this.root.addEventListener( 'focus', this.processFocus.bind(this, true) );
161
- this.root.addEventListener( 'focusout', this.processFocus.bind(this, false) );
200
+ this.root.addEventListener( 'keydown', this.processKey.bind( this), true );
201
+ this.root.addEventListener( 'focus', this.processFocus.bind( this, true ) );
202
+ this.root.addEventListener( 'focusout', this.processFocus.bind( this, false ) );
162
203
  }
163
204
 
164
205
  this.root.addEventListener( 'mousedown', this.processMouse.bind(this) );
@@ -167,130 +208,119 @@ class CodeEditor {
167
208
  this.root.addEventListener( 'click', this.processMouse.bind(this) );
168
209
  this.root.addEventListener( 'contextmenu', this.processMouse.bind(this) );
169
210
 
170
- // Take into account the scrollbar..
171
- this.tabs.area.root.classList.add( 'codetabsarea' );
172
-
173
211
  // Cursors and selection
174
212
 
175
- this.cursors = document.createElement('div');
213
+ this.cursors = document.createElement( 'div' );
176
214
  this.cursors.className = 'cursors';
177
- this.tabs.area.attach(this.cursors);
215
+ this.tabs.area.attach( this.cursors );
178
216
 
179
- this.selections = document.createElement('div');
217
+ this.selections = document.createElement( 'div' );
180
218
  this.selections.className = 'selections';
181
- this.tabs.area.attach(this.selections);
219
+ this.tabs.area.attach( this.selections );
182
220
 
183
221
  // Css char synchronization
184
- this.xPadding = "0.25em";
222
+ this.xPadding = "48px";
185
223
 
186
224
  // Add main cursor
187
225
  {
188
- var cursor = document.createElement('div');
226
+ var cursor = document.createElement( 'div' );
189
227
  cursor.className = "cursor";
190
228
  cursor.innerHTML = "&nbsp;";
191
229
  cursor._left = 0;
192
230
  cursor.style.left = this.xPadding;
193
- cursor._top = 4;
194
- cursor.style.top = "4px";
231
+ cursor._top = 0;
232
+ cursor.style.top = cursor._top + "px";
195
233
  cursor.position = 0;
196
- cursor.line = 0;
197
- cursor.print = (function() { console.log( this.line, this.position ) }).bind(cursor);
198
- this.cursors.appendChild(cursor);
234
+ cursor._line = 0;
235
+ cursor.print = (function() { console.log( this.line, this.position ) }).bind( cursor );
236
+
237
+ Object.defineProperty( cursor, 'line', {
238
+ get: (v) => { return this._line },
239
+ set: (v) => { this._line = v; }
240
+ } );
241
+
242
+ this.cursors.appendChild( cursor );
199
243
  }
200
244
 
201
- // Add custom vertical scroll bar
245
+ // Scroll stuff
202
246
  {
203
- var scrollbar = document.createElement('div');
204
- scrollbar.className = "lexcodescrollbar";
205
- this.scrollbar = scrollbar;
206
- area.attach(this.scrollbar);
247
+ this.codeScroller = this.tabs.area.root;
248
+ this.viewportRangeStart = 0;
249
+ this.lineScrollMargin = new LX.vec2( 20, 20 ); // [ mUp, mDown ]
250
+ window.scroller = this.codeScroller;
207
251
 
208
- var scrollbarThumb = document.createElement('div');
209
- this.scrollbarThumb = scrollbarThumb;
210
- this.scrollbarThumb._top = 0;
211
- scrollbar.appendChild(scrollbarThumb);
252
+ let lastScrollTopValue = -1;
253
+ this.codeScroller.addEventListener( 'scroll', (e) => {
212
254
 
213
- this.scrollbarThumb.addEventListener("mousedown", inner_mousedown);
214
-
215
- var last_pos = 0;
216
-
217
- function inner_mousedown(e)
218
- {
219
- var doc = that.root.ownerDocument;
220
- doc.addEventListener("mousemove",inner_mousemove);
221
- doc.addEventListener("mouseup",inner_mouseup);
222
- last_pos = e.y;
223
- e.stopPropagation();
224
- e.preventDefault();
225
- }
255
+ if( this._discardScroll )
256
+ {
257
+ this._discardScroll = false;
258
+ return;
259
+ }
226
260
 
227
- function inner_mousemove(e)
228
- {
229
- var dt = (last_pos - e.y);
230
-
231
- that.applyVerticalScrollFromScrollBar( that.scrollbarThumb._top - dt )
261
+ this.setScrollBarValue( 'vertical' );
232
262
 
233
- last_pos = e.y;
234
- e.stopPropagation();
235
- e.preventDefault();
236
- }
263
+ const scrollTop = this.getScrollTop();
237
264
 
238
- function inner_mouseup(e)
239
- {
240
- var doc = that.root.ownerDocument;
241
- doc.removeEventListener("mousemove", inner_mousemove);
242
- doc.removeEventListener("mouseup", inner_mouseup);
243
- }
244
- }
265
+ // Scroll down...
266
+ if( scrollTop > lastScrollTopValue )
267
+ {
268
+ const scrollDownBoundary = (this.viewportRangeStart + this.lineScrollMargin.y) * this.lineHeight;
245
269
 
246
- // Add custom horizontal scroll bar
247
- {
248
- var hScrollbar = document.createElement('div');
249
- hScrollbar.className = "lexcodescrollbar horizontal";
250
- this.hScrollbar = hScrollbar;
251
- area.attach(this.hScrollbar);
270
+ if( scrollTop > scrollDownBoundary )
271
+ {
272
+ this.code.style.top = (scrollDownBoundary - this.lineScrollMargin.x * this.lineHeight) + "px";
273
+ this.processLines( CodeEditor.UPDATE_VISIBLE_LINES );
274
+ }
275
+ }
276
+ // Scroll up...
277
+ else
278
+ {
279
+ const scrollUpBoundary = (this.viewportRangeStart + this.lineScrollMargin.x) * this.lineHeight;
280
+ // console.log(scrollTop, scrollUpBoundary)
281
+
282
+ if( scrollTop <= scrollUpBoundary )
283
+ {
284
+ this.viewportRangeStart -= this.lineScrollMargin.x;
285
+ this.viewportRangeStart = Math.max( this.viewportRangeStart, 0 );
252
286
 
253
- var hScrollbarThumb = document.createElement('div');
254
- this.hScrollbarThumb = hScrollbarThumb;
255
- this.hScrollbarThumb._left = 0;
256
- hScrollbar.appendChild(hScrollbarThumb);
287
+ this.code.style.top = this.viewportRangeStart == 0 ? "0px" : (scrollUpBoundary - this.lineScrollMargin.x * this.lineHeight) + "px";
288
+
289
+ this.processLines( CodeEditor.KEEP_VISIBLE_LINES );
290
+ }
291
+ }
257
292
 
258
- this.hScrollbarThumb.addEventListener("mousedown", inner_mousedown);
259
-
260
- var last_pos = 0;
261
-
262
- function inner_mousedown(e)
263
- {
264
- var doc = that.root.ownerDocument;
265
- doc.addEventListener("mousemove",inner_mousemove);
266
- doc.addEventListener("mouseup",inner_mouseup);
267
- last_pos = e.x;
268
- e.stopPropagation();
269
- e.preventDefault();
270
- }
293
+ lastScrollTopValue = scrollTop;
294
+ });
271
295
 
272
- function inner_mousemove(e)
273
- {
274
- var dt = (last_pos - e.x);
275
-
276
- that.applyHorizontalScrollFromScrollBar( that.hScrollbarThumb._left - dt )
296
+ this.codeScroller.addEventListener( 'wheel', (e) => {
297
+ const dX = (e.deltaY > 0.0 ? 10.0 : -10.0) * ( e.shiftKey ? 1.0 : 0.0 );
298
+ if( dX != 0.0 ) this.setScrollBarValue( 'horizontal', dX );
299
+ });
300
+ }
277
301
 
278
- last_pos = e.x;
279
- e.stopPropagation();
280
- e.preventDefault();
281
- }
302
+ // This is only the container, line numbers are in the same line div
303
+ {
304
+ this.gutter = document.createElement( 'div' );
305
+ this.gutter.className = "lexcodegutter";
306
+ area.attach( this.gutter );
307
+ }
282
308
 
283
- function inner_mouseup(e)
284
- {
285
- var doc = that.root.ownerDocument;
286
- doc.removeEventListener("mousemove", inner_mousemove);
287
- doc.removeEventListener("mouseup", inner_mouseup);
288
- }
309
+ // Add custom vertical scroll bar
310
+ {
311
+ this.vScrollbar = new ScrollBar( this, ScrollBar.SCROLLBAR_VERTICAL );
312
+ area.attach(this.vScrollbar.root);
313
+ }
314
+
315
+ // Add custom horizontal scroll bar
316
+ {
317
+ this.hScrollbar = new ScrollBar( this, ScrollBar.SCROLLBAR_HORIZONTAL );
318
+ area.attach(this.hScrollbar.root);
289
319
  }
290
320
 
291
321
  // Add autocomplete box
292
322
  {
293
- var box = document.createElement('div');
323
+ var box = document.createElement( 'div' );
294
324
  box.className = "autocomplete";
295
325
  this.autocomplete = box;
296
326
  this.tabs.area.attach(box);
@@ -298,10 +328,21 @@ class CodeEditor {
298
328
  this.isAutoCompleteActive = false;
299
329
  }
300
330
 
331
+ // Add code-sizer
332
+ {
333
+ this.codeSizer = document.createElement( 'div' );
334
+ this.codeSizer.className = "code-sizer";
335
+
336
+ // Append all childs
337
+ while( this.codeScroller.firstChild )
338
+ this.codeSizer.appendChild( this.codeScroller.firstChild );
339
+
340
+ this.codeScroller.appendChild( this.codeSizer );
341
+ }
342
+
301
343
  // State
302
344
 
303
345
  this.state = {
304
- overwrite: false,
305
346
  focused: false,
306
347
  selectingText: false
307
348
  }
@@ -335,7 +376,7 @@ class CodeEditor {
335
376
  };
336
377
 
337
378
  // Scan tokens..
338
- setInterval( this.scanWordSuggestions.bind(this), 2000 );
379
+ // setInterval( this.scanWordSuggestions.bind( this ), 2000 );
339
380
 
340
381
  this.languages = {
341
382
  'Plain Text': { },
@@ -426,64 +467,67 @@ class CodeEditor {
426
467
 
427
468
  // Action keys
428
469
 
429
- this.action('Escape', false, ( ln, cursor, e ) => {
470
+ this.action( 'Escape', false, ( ln, cursor, e ) => {
430
471
  this.hideAutoCompleteBox();
431
472
  });
432
473
 
433
- this.action('Backspace', false, ( ln, cursor, e ) => {
474
+ this.action( 'Backspace', false, ( ln, cursor, e ) => {
434
475
 
435
- this._addUndoStep(cursor);
476
+ this._addUndoStep( cursor );
436
477
 
437
- if(this.selection) {
438
- this.deleteSelection(cursor);
478
+ if( this.selection ) {
479
+ this.deleteSelection( cursor );
439
480
  // Remove entire line when selecting with triple click
440
- if(this.code.lines[ln] && !this.code.lines[ln].length)
441
- this.actions['Backspace'].callback(ln, cursor, e);
481
+ if(this.code.lines[ ln ] != undefined && !this.code.lines[ ln ].length)
482
+ {
483
+ this.actions['Backspace'].callback( ln, cursor, e );
484
+ this.lineDown( cursor, true );
485
+ }
442
486
  }
443
487
  else {
444
488
  var letter = this.getCharAtPos( cursor, -1 );
445
- if(letter) {
446
- this.code.lines[ln] = sliceChar( this.code.lines[ln], cursor.position - 1 );
489
+ if( letter ) {
490
+ this.code.lines[ ln ] = sliceChar( this.code.lines[ ln ], cursor.position - 1 );
447
491
  this.cursorToLeft( letter );
448
- this.processLine(ln);
492
+ this.processLine( ln );
449
493
  if( this.useAutoComplete )
450
494
  this.showAutoCompleteBox( 'foo', cursor );
451
495
  }
452
- else if(this.code.lines[ln - 1] != undefined) {
496
+ else if( this.code.lines[ ln - 1 ] != undefined ) {
453
497
  this.lineUp();
454
- this.actions['End'].callback(cursor.line, cursor, e);
498
+ this.actions[ 'End' ].callback( cursor.line, cursor, e );
455
499
  // Move line on top
456
- this.code.lines[ln - 1] += this.code.lines[ln];
457
- this.code.lines.splice(ln, 1);
458
- this.processLines(ln - 1);
500
+ this.code.lines[ ln - 1 ] += this.code.lines[ ln ];
501
+ this.code.lines.splice( ln, 1 );
502
+ this.processLines();
459
503
  }
460
504
  }
461
505
  });
462
506
 
463
- this.action('Delete', false, ( ln, cursor, e ) => {
507
+ this.action( 'Delete', false, ( ln, cursor, e ) => {
464
508
 
465
509
  this._addUndoStep( cursor );
466
510
 
467
511
  if(this.selection) {
468
512
  // Use 'Backspace' as it's the same callback...
469
- this.actions['Backspace'].callback(ln, cursor, e);
513
+ this.actions['Backspace'].callback( ln, cursor, e );
470
514
  }
471
515
  else
472
516
  {
473
517
  var letter = this.getCharAtPos( cursor );
474
- if(letter) {
475
- this.code.lines[ln] = sliceChar( this.code.lines[ln], cursor.position );
476
- this.processLine(ln);
518
+ if( letter ) {
519
+ this.code.lines[ ln ] = sliceChar( this.code.lines[ ln ], cursor.position );
520
+ this.processLine( ln );
477
521
  }
478
- else if(this.code.lines[ln + 1] != undefined) {
479
- this.code.lines[ln] += this.code.lines[ln + 1];
480
- this.code.lines.splice(ln + 1, 1);
481
- this.processLines(ln);
522
+ else if(this.code.lines[ ln + 1 ] != undefined) {
523
+ this.code.lines[ ln ] += this.code.lines[ ln + 1 ];
524
+ this.code.lines.splice( ln + 1, 1 );
525
+ this.processLines();
482
526
  }
483
527
  }
484
528
  });
485
529
 
486
- this.action('Tab', true, ( ln, cursor, e ) => {
530
+ this.action( 'Tab', true, ( ln, cursor, e ) => {
487
531
 
488
532
  if( this.isAutoCompleteActive )
489
533
  {
@@ -494,53 +538,53 @@ class CodeEditor {
494
538
  }
495
539
  });
496
540
 
497
- this.action('Home', false, ( ln, cursor, e ) => {
541
+ this.action( 'Home', false, ( ln, cursor, e ) => {
498
542
 
499
- let idx = firstNonspaceIndex(this.code.lines[ln]);
543
+ let idx = firstNonspaceIndex( this.code.lines[ ln ] );
500
544
 
501
545
  // We already are in the first non space index...
502
546
  if(idx == cursor.position) idx = 0;
503
547
 
504
- const prestring = this.code.lines[ln].substring(0, idx);
505
- let last_pos = cursor.position;
548
+ const prestring = this.code.lines[ ln ].substring( 0, idx );
549
+ let lastX = cursor.position;
506
550
 
507
551
  this.resetCursorPos( CodeEditor.CURSOR_LEFT );
508
- if(idx > 0) this.cursorToString(cursor, prestring);
509
- this._refreshCodeInfo(cursor.line, cursor.position);
552
+ if(idx > 0) this.cursorToString( cursor, prestring );
553
+ this._refreshCodeInfo( cursor.line, cursor.position );
510
554
  this.setScrollLeft( 0 );
511
555
 
512
556
  if( e.shiftKey && !e.cancelShift )
513
557
  {
514
558
  // Get last selection range
515
- if(this.selection)
516
- last_pos += this.selection.chars;
559
+ if( this.selection )
560
+ lastX += this.selection.chars;
517
561
 
518
- this.startSelection(cursor);
519
- var string = this.code.lines[ln].substring(idx, last_pos);
520
- this.selection.selectInline(idx, cursor.line, this.measureString(string));
562
+ this.startSelection( cursor );
563
+ var string = this.code.lines[ ln ].substring( idx, lastX );
564
+ this.selection.selectInline( idx, cursor.line, this.measureString( string ) );
521
565
  } else if( !e.keepSelection )
522
566
  this.endSelection();
523
567
  });
524
568
 
525
- this.action('End', false, ( ln, cursor, e ) => {
569
+ this.action( 'End', false, ( ln, cursor, e ) => {
526
570
 
527
571
  if( e.shiftKey || e._shiftKey ) {
528
572
 
529
- var string = this.code.lines[ln].substring(cursor.position);
530
- if(!this.selection)
531
- this.startSelection(cursor);
573
+ var string = this.code.lines[ ln ].substring(cursor.position);
574
+ if( !this.selection )
575
+ this.startSelection( cursor );
532
576
  this.selection.selectInline(cursor.position, cursor.line, this.measureString(string));
533
577
  } else
534
578
  this.endSelection();
535
579
 
536
580
  this.resetCursorPos( CodeEditor.CURSOR_LEFT );
537
- this.cursorToString( cursor, this.code.lines[ln] );
581
+ this.cursorToString( cursor, this.code.lines[ ln ] );
538
582
 
539
583
  const last_char = (this.code.clientWidth / this.charWidth)|0;
540
584
  this.setScrollLeft( cursor.position >= last_char ? (cursor.position - last_char) * this.charWidth : 0 );
541
585
  });
542
586
 
543
- this.action('Enter', true, ( ln, cursor, e ) => {
587
+ this.action( 'Enter', true, ( ln, cursor, e ) => {
544
588
 
545
589
  // Add word
546
590
  if( this.isAutoCompleteActive )
@@ -549,63 +593,63 @@ class CodeEditor {
549
593
  return;
550
594
  }
551
595
 
552
- if(e.ctrlKey)
596
+ if( e.ctrlKey )
553
597
  {
554
598
  this.onrun( this.getText() );
555
599
  return;
556
600
  }
557
601
 
558
- this._addUndoStep(cursor);
602
+ this._addUndoStep( cursor );
559
603
 
560
604
  var _c0 = this.getCharAtPos( cursor, -1 );
561
605
  var _c1 = this.getCharAtPos( cursor );
562
606
 
563
- this.code.lines.splice(cursor.line + 1, 0, "");
564
- this.code.lines[cursor.line + 1] = this.code.lines[ln].substr( cursor.position ); // new line (below)
565
- this.code.lines[ln] = this.code.lines[ln].substr( 0, cursor.position ); // line above
566
- this.lineDown(cursor, true);
607
+ this.code.lines.splice( cursor.line + 1, 0, "" );
608
+ this.code.lines[cursor.line + 1] = this.code.lines[ ln ].substr( cursor.position ); // new line (below)
609
+ this.code.lines[ ln ] = this.code.lines[ ln ].substr( 0, cursor.position ); // line above
610
+ this.lineDown( cursor, true );
567
611
 
568
612
  // Check indentation
569
- var spaces = firstNonspaceIndex(this.code.lines[ln]);
613
+ var spaces = firstNonspaceIndex( this.code.lines[ ln ]);
570
614
  var tabs = Math.floor( spaces / this.tabSpaces );
571
615
 
572
616
  if( _c0 == '{' && _c1 == '}' ) {
573
- this.code.lines.splice(cursor.line, 0, "");
574
- this.addSpaceTabs(tabs + 1);
575
- this.code.lines[cursor.line + 1] = " ".repeat(spaces) + this.code.lines[cursor.line + 1];
617
+ this.code.lines.splice( cursor.line, 0, "" );
618
+ this.addSpaceTabs( tabs + 1 );
619
+ this.code.lines[ cursor.line + 1 ] = " ".repeat(spaces) + this.code.lines[ cursor.line + 1 ];
576
620
  } else {
577
- this.addSpaceTabs(tabs);
621
+ this.addSpaceTabs( tabs );
578
622
  }
579
623
 
580
- this.processLines( ln );
624
+ this.processLines();
581
625
  });
582
626
 
583
- this.action('ArrowUp', false, ( ln, cursor, e ) => {
627
+ this.action( 'ArrowUp', false, ( ln, cursor, e ) => {
584
628
 
585
629
  // Move cursor..
586
630
  if( !this.isAutoCompleteActive )
587
631
  {
588
632
  if( e.shiftKey ) {
589
- if(!this.selection)
590
- this.startSelection(cursor);
633
+ if( !this.selection )
634
+ this.startSelection( cursor );
591
635
 
592
- this.selection.toY = (this.selection.toY > 0) ? (this.selection.toY - 1) : 0;
593
- this.cursorToLine(cursor, this.selection.toY);
636
+ this.selection.toY = ( this.selection.toY > 0 ) ? ( this.selection.toY - 1 ) : 0;
637
+ this.cursorToLine( cursor, this.selection.toY );
594
638
 
595
639
  var letter = this.getCharAtPos( cursor );
596
- if(!letter) {
597
- this.selection.toX = this.code.lines[cursor.line].length;
598
- this.cursorToPosition(cursor, this.selection.toX);
640
+ if( !letter ) {
641
+ this.selection.toX = this.code.lines[ cursor.line ].length;
642
+ this.cursorToPosition( cursor, this.selection.toX );
599
643
  }
600
644
 
601
- this.processSelection(null, true);
645
+ this.processSelection( null, true );
602
646
 
603
647
  } else {
604
648
  this.endSelection();
605
649
  this.lineUp();
606
650
  // Go to end of line if out of line
607
651
  var letter = this.getCharAtPos( cursor );
608
- if(!letter) this.actions['End'].callback(cursor.line, cursor, e);
652
+ if( !letter ) this.actions['End'].callback( cursor.line, cursor, e );
609
653
  }
610
654
  }
611
655
  // Move up autocomplete selection
@@ -615,34 +659,34 @@ class CodeEditor {
615
659
  }
616
660
  });
617
661
 
618
- this.action('ArrowDown', false, ( ln, cursor, e ) => {
662
+ this.action( 'ArrowDown', false, ( ln, cursor, e ) => {
619
663
 
620
664
  // Move cursor..
621
665
  if( !this.isAutoCompleteActive )
622
666
  {
623
667
  if( e.shiftKey ) {
624
- if(!this.selection)
625
- this.startSelection(cursor);
668
+ if( !this.selection )
669
+ this.startSelection( cursor );
626
670
 
627
671
  this.selection.toY = this.selection.toY < this.code.lines.length - 1 ? this.selection.toY + 1 : this.code.lines.length - 1;
628
- this.cursorToLine(cursor, this.selection.toY);
672
+ this.cursorToLine( cursor, this.selection.toY );
629
673
 
630
674
  var letter = this.getCharAtPos( cursor );
631
- if(!letter) {
632
- this.selection.toX = Math.max(this.code.lines[cursor.line].length - 1, 0);
675
+ if( !letter ) {
676
+ this.selection.toX = Math.max(this.code.lines[ cursor.line ].length - 1, 0);
633
677
  this.cursorToPosition(cursor, this.selection.toX);
634
678
  }
635
679
 
636
- this.processSelection(null, true);
680
+ this.processSelection( null, true );
637
681
  } else {
638
682
 
639
683
  if( this.code.lines[ ln + 1 ] == undefined )
640
684
  return;
641
685
  this.endSelection();
642
- this.lineDown();
686
+ this.lineDown( cursor );
643
687
  // Go to end of line if out of line
644
688
  var letter = this.getCharAtPos( cursor );
645
- if(!letter) this.actions['End'].callback(cursor.line, cursor, e);
689
+ if( !letter ) this.actions['End'].callback(cursor.line, cursor, e);
646
690
  }
647
691
  }
648
692
  // Move down autocomplete selection
@@ -652,40 +696,40 @@ class CodeEditor {
652
696
  }
653
697
  });
654
698
 
655
- this.action('ArrowLeft', false, ( ln, cursor, e ) => {
699
+ this.action( 'ArrowLeft', false, ( ln, cursor, e ) => {
656
700
 
657
- if(e.metaKey) { // Apple devices (Command)
701
+ if( e.metaKey ) { // Apple devices (Command)
658
702
  e.preventDefault();
659
703
  this.actions[ 'Home' ].callback( ln, cursor, e );
660
704
  }
661
- else if(e.ctrlKey) {
705
+ else if( e.ctrlKey ) {
662
706
  // Get next word
663
707
  const [word, from, to] = this.getWordAtPos( cursor, -1 );
664
708
  var diff = Math.max(cursor.position - from, 1);
665
709
  var substr = word.substr(0, diff);
666
710
  // Selections...
667
- if( e.shiftKey ) if(!this.selection) this.startSelection(cursor);
711
+ if( e.shiftKey ) if( !this.selection ) this.startSelection( cursor );
668
712
  this.cursorToString(cursor, substr, true);
669
713
  if( e.shiftKey ) this.processSelection();
670
714
  }
671
715
  else {
672
716
  var letter = this.getCharAtPos( cursor, -1 );
673
- if(letter) {
717
+ if( letter ) {
674
718
  if( e.shiftKey ) {
675
- if(!this.selection) this.startSelection(cursor);
676
- if( ((cursor.position - 1) < this.selection.fromX) && this.selection.sameLine() )
719
+ if( !this.selection ) this.startSelection( cursor );
720
+ if( ( ( cursor.position - 1 ) < this.selection.fromX ) && this.selection.sameLine() )
677
721
  this.selection.fromX--;
678
- else if( (cursor.position - 1) == this.selection.fromX && this.selection.sameLine() ) {
722
+ else if( ( cursor.position - 1 ) == this.selection.fromX && this.selection.sameLine() ) {
679
723
  this.cursorToLeft( letter, cursor );
680
724
  this.endSelection();
681
725
  return;
682
726
  }
683
727
  else this.selection.toX--;
684
728
  this.cursorToLeft( letter, cursor );
685
- this.processSelection(null, true);
729
+ this.processSelection( null, true );
686
730
  }
687
731
  else {
688
- if(!this.selection) {
732
+ if( !this.selection ) {
689
733
  this.cursorToLeft( letter, cursor );
690
734
  if( this.useAutoComplete && this.isAutoCompleteActive )
691
735
  this.showAutoCompleteBox( 'foo', cursor );
@@ -693,8 +737,8 @@ class CodeEditor {
693
737
  else {
694
738
  this.selection.invertIfNecessary();
695
739
  this.resetCursorPos( CodeEditor.CURSOR_LEFT | CodeEditor.CURSOR_TOP );
696
- this.cursorToLine(cursor, this.selection.fromY, true);
697
- this.cursorToPosition(cursor, this.selection.fromX);
740
+ this.cursorToLine( cursor, this.selection.fromY, true );
741
+ this.cursorToPosition( cursor, this.selection.fromX );
698
742
  this.endSelection();
699
743
  }
700
744
  }
@@ -702,7 +746,7 @@ class CodeEditor {
702
746
  else if( cursor.line > 0 ) {
703
747
 
704
748
  if( e.shiftKey ) {
705
- if(!this.selection) this.startSelection(cursor);
749
+ if( !this.selection ) this.startSelection( cursor );
706
750
  }
707
751
 
708
752
  this.lineUp( cursor );
@@ -711,34 +755,34 @@ class CodeEditor {
711
755
  if( e.shiftKey ) {
712
756
  this.selection.toX = cursor.position;
713
757
  this.selection.toY--;
714
- this.processSelection(null, true);
758
+ this.processSelection( null, true );
715
759
  }
716
760
  }
717
761
  }
718
762
  });
719
763
 
720
- this.action('ArrowRight', false, ( ln, cursor, e ) => {
764
+ this.action( 'ArrowRight', false, ( ln, cursor, e ) => {
721
765
 
722
- if(e.metaKey) { // Apple devices (Command)
766
+ if( e.metaKey ) { // Apple devices (Command)
723
767
  e.preventDefault();
724
768
  this.actions[ 'End' ].callback( ln, cursor );
725
- } else if(e.ctrlKey) {
769
+ } else if( e.ctrlKey ) {
726
770
  // Get next word
727
- const [word, from, to] = this.getWordAtPos( cursor );
771
+ const [ word, from, to ] = this.getWordAtPos( cursor );
728
772
  var diff = cursor.position - from;
729
- var substr = word.substr(diff);
773
+ var substr = word.substr( diff );
730
774
  // Selections...
731
- if( e.shiftKey ) if(!this.selection) this.startSelection(cursor);
732
- this.cursorToString(cursor, substr);
775
+ if( e.shiftKey ) if( !this.selection ) this.startSelection( cursor );
776
+ this.cursorToString( cursor, substr);
733
777
  if( e.shiftKey ) this.processSelection();
734
778
  } else {
735
779
  var letter = this.getCharAtPos( cursor );
736
- if(letter) {
780
+ if( letter ) {
737
781
  if( e.shiftKey ) {
738
- if(!this.selection) this.startSelection(cursor);
782
+ if( !this.selection ) this.startSelection( cursor );
739
783
  var keep_range = false;
740
784
  if( cursor.position == this.selection.fromX ) {
741
- if( (cursor.position + 1) == this.selection.toX && this.selection.sameLine() ) {
785
+ if( ( cursor.position + 1 ) == this.selection.toX && this.selection.sameLine() ) {
742
786
  this.cursorToRight( letter, cursor );
743
787
  this.endSelection();
744
788
  return;
@@ -748,9 +792,9 @@ class CodeEditor {
748
792
  } else this.selection.toX++;
749
793
  }
750
794
  this.cursorToRight( letter, cursor );
751
- this.processSelection(null, keep_range);
795
+ this.processSelection( null, keep_range );
752
796
  }else{
753
- if(!this.selection) {
797
+ if( !this.selection ) {
754
798
  this.cursorToRight( letter, cursor );
755
799
  if( this.useAutoComplete && this.isAutoCompleteActive )
756
800
  this.showAutoCompleteBox( 'foo', cursor );
@@ -759,8 +803,8 @@ class CodeEditor {
759
803
  {
760
804
  this.selection.invertIfNecessary();
761
805
  this.resetCursorPos( CodeEditor.CURSOR_LEFT | CodeEditor.CURSOR_TOP );
762
- this.cursorToLine(cursor, this.selection.toY);
763
- this.cursorToPosition(cursor, this.selection.toX);
806
+ this.cursorToLine( cursor, this.selection.toY );
807
+ this.cursorToPosition( cursor, this.selection.toX );
764
808
  this.endSelection();
765
809
  }
766
810
  }
@@ -768,18 +812,18 @@ class CodeEditor {
768
812
  else if( this.code.lines[ cursor.line + 1 ] !== undefined ) {
769
813
 
770
814
  if( e.shiftKey ) {
771
- if(!this.selection) this.startSelection(cursor);
815
+ if( !this.selection ) this.startSelection( cursor );
772
816
  e.cancelShift = true;
773
817
  e.keepSelection = true;
774
818
  }
775
819
 
776
820
  this.lineDown( cursor );
777
- this.actions['Home'].callback(cursor.line, cursor, e);
821
+ this.actions['Home'].callback( cursor.line, cursor, e );
778
822
 
779
823
  if( e.shiftKey ) {
780
824
  this.selection.toX = cursor.position;
781
825
  this.selection.toY++;
782
- this.processSelection(null, true);
826
+ this.processSelection( null, true );
783
827
  }
784
828
 
785
829
  this.hideAutoCompleteBox();
@@ -813,14 +857,14 @@ class CodeEditor {
813
857
  // This can be used to empty all text...
814
858
  setText( text = "", lang ) {
815
859
 
816
- let new_lines = text.split('\n');
817
- this.code.lines = [].concat(new_lines);
860
+ let new_lines = text.split( '\n' );
861
+ this.code.lines = [].concat( new_lines );
818
862
 
819
- let cursor = this.cursors.children[0];
863
+ let cursor = this.cursors.children[ 0 ];
820
864
  let lastLine = new_lines.pop();
821
865
 
822
- this.cursorToLine(cursor, new_lines.length); // Already substracted 1
823
- this.cursorToPosition(cursor, lastLine.length);
866
+ this.cursorToLine( cursor, new_lines.length ); // Already substracted 1
867
+ this.cursorToPosition( cursor, lastLine.length );
824
868
  this.processLines();
825
869
 
826
870
  if( lang )
@@ -831,64 +875,64 @@ class CodeEditor {
831
875
 
832
876
  appendText( text ) {
833
877
 
834
- let cursor = this.cursors.children[0];
878
+ let cursor = this.cursors.children[ 0 ];
835
879
  let lidx = cursor.line;
836
880
 
837
881
  if( this.selection ) {
838
- this.deleteSelection(cursor);
882
+ this.deleteSelection( cursor );
839
883
  lidx = cursor.line;
840
884
  }
841
885
 
842
886
  this.endSelection();
843
887
 
844
- const new_lines = text.split('\n');
888
+ const new_lines = text.split( '\n' );
845
889
 
846
890
  // Pasting Multiline...
847
- if(new_lines.length != 1)
891
+ if( new_lines.length != 1 )
848
892
  {
849
893
  let num_lines = new_lines.length;
850
- console.assert(num_lines > 0);
894
+ console.assert( num_lines > 0 );
851
895
  const first_line = new_lines.shift();
852
896
  num_lines--;
853
897
 
854
- const remaining = this.code.lines[lidx].slice(cursor.position);
898
+ const remaining = this.code.lines[ lidx ].slice( cursor.position );
855
899
 
856
900
  // Add first line
857
- this.code.lines[lidx] = [
858
- this.code.lines[lidx].slice(0, cursor.position),
901
+ this.code.lines[ lidx ] = [
902
+ this.code.lines[ lidx ].slice( 0, cursor.position ),
859
903
  first_line
860
904
  ].join('');
861
905
 
862
- this.cursorToPosition(cursor, (cursor.position + first_line.length));
906
+ this.cursorToPosition( cursor, ( cursor.position + first_line.length ) );
863
907
 
864
908
  // Enter next lines...
865
909
 
866
910
  let _text = null;
867
911
 
868
912
  for( var i = 0; i < new_lines.length; ++i ) {
869
- _text = new_lines[i];
870
- this.cursorToLine(cursor, cursor.line++, true);
913
+ _text = new_lines[ i ];
914
+ this.cursorToLine( cursor, cursor.line++, true );
871
915
  // Add remaining...
872
916
  if( i == (new_lines.length - 1) )
873
917
  _text += remaining;
874
- this.code.lines.splice( 1 + lidx + i, 0, _text);
918
+ this.code.lines.splice( 1 + lidx + i, 0, _text );
875
919
  }
876
920
 
877
- if(_text) this.cursorToPosition(cursor, _text.length);
878
- this.cursorToLine(cursor, cursor.line + num_lines);
879
- this.processLines(lidx);
921
+ if( _text ) this.cursorToPosition( cursor, _text.length );
922
+ this.cursorToLine( cursor, cursor.line + num_lines );
923
+ this.processLines();
880
924
  }
881
925
  // Pasting one line...
882
926
  else
883
927
  {
884
- this.code.lines[lidx] = [
885
- this.code.lines[lidx].slice(0, cursor.position),
886
- new_lines[0],
887
- this.code.lines[lidx].slice(cursor.position)
928
+ this.code.lines[ lidx ] = [
929
+ this.code.lines[ lidx ].slice( 0, cursor.position ),
930
+ new_lines[ 0 ],
931
+ this.code.lines[ lidx ].slice( cursor.position )
888
932
  ].join('');
889
933
 
890
- this.cursorToPosition(cursor, (cursor.position + new_lines[0].length));
891
- this.processLine(lidx);
934
+ this.cursorToPosition( cursor, ( cursor.position + new_lines[ 0 ].length ) );
935
+ this.processLine( lidx );
892
936
  }
893
937
  }
894
938
 
@@ -898,18 +942,18 @@ class CodeEditor {
898
942
  const existing = this.addTab(name, true, title);
899
943
  if( !existing )
900
944
  {
901
- text = text.replaceAll('\r', '');
902
- this.code.lines = text.split('\n');
903
- this._changeLanguageFromExtension( LX.getExtension(name) );
945
+ text = text.replaceAll( '\r', '' );
946
+ this.code.lines = text.split( '\n' );
947
+ this._changeLanguageFromExtension( LX.getExtension( name ) );
904
948
  }
905
949
  };
906
950
 
907
- if(file.constructor == String)
951
+ if( file.constructor == String )
908
952
  {
909
953
  let filename = file;
910
954
  LX.request({ url: filename, success: text => {
911
955
 
912
- const name = filename.substring(filename.lastIndexOf('/') + 1);
956
+ const name = filename.substring(filename.lastIndexOf( '/' ) + 1);
913
957
  inner_add_tab( text, name, filename );
914
958
  } });
915
959
  }
@@ -926,18 +970,18 @@ class CodeEditor {
926
970
 
927
971
  _addUndoStep( cursor ) {
928
972
 
929
- var cursor = cursor ?? this.cursors.children[0];
973
+ var cursor = cursor ?? this.cursors.children[ 0 ];
930
974
 
931
975
  this.code.undoSteps.push( {
932
- lines: LX.deepCopy(this.code.lines),
933
- cursor: this.saveCursor(cursor),
976
+ lines: LX.deepCopy( this.code.lines ),
977
+ cursor: this.saveCursor( cursor ),
934
978
  line: cursor.line
935
979
  } );
936
980
  }
937
981
 
938
982
  _changeLanguage( lang ) {
939
983
 
940
- this.code.lang = lang;
984
+ this.code.language = lang;
941
985
  this.highlight = lang;
942
986
  this._refreshCodeInfo();
943
987
  this.processLines();
@@ -946,23 +990,23 @@ class CodeEditor {
946
990
  _changeLanguageFromExtension( ext ) {
947
991
 
948
992
  if( !ext )
949
- return this._changeLanguage( this.code.lang );
950
-
951
- switch(ext.toLowerCase())
952
- {
953
- case 'js': return this._changeLanguage('JavaScript');
954
- case 'cpp': return this._changeLanguage('C++');
955
- case 'h': return this._changeLanguage('C++');
956
- case 'glsl': return this._changeLanguage('GLSL');
957
- case 'css': return this._changeLanguage('CSS');
958
- case 'json': return this._changeLanguage('JSON');
959
- case 'xml': return this._changeLanguage('XML');
960
- case 'wgsl': return this._changeLanguage('WGSL');
961
- case 'py': return this._changeLanguage('Python');
962
- case 'bat': return this._changeLanguage('Batch');
993
+ return this._changeLanguage( this.code.language );
994
+
995
+ switch( ext.toLowerCase() )
996
+ {
997
+ case 'js': return this._changeLanguage( 'JavaScript' );
998
+ case 'cpp': return this._changeLanguage( 'C++' );
999
+ case 'h': return this._changeLanguage( 'C++' );
1000
+ case 'glsl': return this._changeLanguage( 'GLSL' );
1001
+ case 'css': return this._changeLanguage( 'CSS' );
1002
+ case 'json': return this._changeLanguage( 'JSON' );
1003
+ case 'xml': return this._changeLanguage( 'XML' );
1004
+ case 'wgsl': return this._changeLanguage( 'WGSL' );
1005
+ case 'py': return this._changeLanguage( 'Python' );
1006
+ case 'bat': return this._changeLanguage( 'Batch' );
963
1007
  case 'txt':
964
1008
  default:
965
- this._changeLanguage('Plain Text');
1009
+ this._changeLanguage( 'Plain Text' );
966
1010
  }
967
1011
  }
968
1012
 
@@ -974,15 +1018,15 @@ class CodeEditor {
974
1018
  panel.ln = 0;
975
1019
  panel.col = 0;
976
1020
 
977
- this._refreshCodeInfo = (ln = panel.ln, col = panel.col) => {
1021
+ this._refreshCodeInfo = ( ln = panel.ln, col = panel.col ) => {
978
1022
  panel.ln = ln + 1;
979
1023
  panel.col = col + 1;
980
1024
  panel.clear();
981
1025
  panel.sameLine();
982
- panel.addLabel(this.code.title, { float: 'right' });
983
- panel.addLabel("Ln " + panel.ln, { width: "64px" });
984
- panel.addLabel("Col " + panel.col, { width: "64px" });
985
- panel.addButton("<b>{ }</b>", this.highlight, (value, event) => {
1026
+ panel.addLabel( this.code.title, { float: 'right' });
1027
+ panel.addLabel( "Ln " + panel.ln, { width: "64px" });
1028
+ panel.addLabel( "Col " + panel.col, { width: "64px" });
1029
+ panel.addButton( "<b>{ }</b>", this.highlight, ( value, event ) => {
986
1030
  LX.addContextMenu( "Language", event, m => {
987
1031
  for( const lang of Object.keys(this.languages) )
988
1032
  m.add( lang, this._changeLanguage.bind(this) );
@@ -1003,8 +1047,8 @@ class CodeEditor {
1003
1047
 
1004
1048
  // Change css a little bit...
1005
1049
  this.gutter.style.height = "calc(100% - 38px)";
1006
- this.root.querySelectorAll('.code').forEach( e => e.style.height = "calc(100% - 6px)" );
1007
- this.root.querySelector('.lexareatabscontent').style.height = "calc(100% - 23px)";
1050
+ this.root.querySelectorAll( '.code' ).forEach( e => e.style.height = "calc(100% - 6px)" );
1051
+ this.root.querySelector( '.lexareatabscontent' ).style.height = "calc(100% - 23px)";
1008
1052
 
1009
1053
  }, 100);
1010
1054
  }
@@ -1015,61 +1059,46 @@ class CodeEditor {
1015
1059
  this.processFocus(false);
1016
1060
 
1017
1061
  LX.addContextMenu( null, e, m => {
1018
- m.add( "Create", this.addTab.bind(this, "unnamed.js", true) );
1019
- m.add( "Load", this.loadTab.bind(this, "unnamed.js", true) );
1062
+ m.add( "Create", this.addTab.bind( this, "unnamed.js", true ) );
1063
+ m.add( "Load", this.loadTab.bind( this, "unnamed.js", true ) );
1020
1064
  });
1021
1065
  }
1022
1066
 
1023
1067
  addTab(name, selected, title) {
1024
1068
 
1025
- if(this.openedTabs[name])
1069
+ if(this.openedTabs[ name ])
1026
1070
  {
1027
1071
  this.tabs.select( this.code.tabName );
1028
1072
  return true;
1029
1073
  }
1030
1074
 
1031
1075
  // Create code content
1032
- let code = document.createElement('div');
1076
+ let code = document.createElement( 'div' );
1033
1077
  code.className = 'code';
1034
- code.lines = [""];
1035
- code.lang = "Plain Text";
1078
+ code.lines = [ "" ];
1079
+ code.language = "Plain Text";
1036
1080
  code.cursorState = {};
1037
1081
  code.undoSteps = [];
1038
1082
  code.tabName = name;
1039
1083
  code.title = title ?? name;
1040
1084
  code.tokens = {};
1041
- code.customScroll = new LX.vec2;
1042
1085
 
1043
- code.addEventListener('dragenter', function(e) {
1086
+ code.addEventListener( 'dragenter', function(e) {
1044
1087
  e.preventDefault();
1045
- this.parentElement.classList.add('dragging');
1088
+ this.parentElement.classList.add( 'dragging' );
1046
1089
  });
1047
- code.addEventListener('dragleave', function(e) {
1090
+ code.addEventListener( 'dragleave', function(e) {
1048
1091
  e.preventDefault();
1049
- this.parentElement.remove('dragging');
1092
+ this.parentElement.remove( 'dragging' );
1050
1093
  });
1051
- code.addEventListener('drop', (e) => {
1094
+ code.addEventListener( 'drop', e => {
1052
1095
  e.preventDefault();
1053
- code.parentElement.classList.remove('dragging');
1096
+ code.parentElement.classList.remove( 'dragging' );
1054
1097
  for( let i = 0; i < e.dataTransfer.files.length; ++i )
1055
- this.loadFile( e.dataTransfer.files[i] );
1056
- });
1057
- code.addEventListener('wheel', (e) => {
1058
-
1059
- // Get scroll data
1060
-
1061
- const dX = (e.deltaY > 0.0 ? 1.0 : -1.0) * 20.0 * ( e.shiftKey ? 1.0 : 0.0 );
1062
- const dY = (e.deltaY > 0.0 ? 1.0 : -1.0) * 40.0 * ( e.shiftKey ? 0.0 : 1.0 );
1063
-
1064
- var new_scroll = code.customScroll.add( new LX.vec2( dX, dY ), new LX.vec2() );
1065
-
1066
- // Update state
1067
-
1068
- if( new_scroll.x != this.getScrollLeft()) this.setScrollLeft( new_scroll.x );
1069
- if( new_scroll.y != this.getScrollTop()) this.setScrollTop( new_scroll.y );
1098
+ this.loadFile( e.dataTransfer.files[ i ] );
1070
1099
  });
1071
1100
 
1072
- this.openedTabs[name] = code;
1101
+ this.openedTabs[ name ] = code;
1073
1102
 
1074
1103
  this.tabs.add(name, code, { 'selected': selected, 'fixed': (name === '+') , 'title': code.title, 'onSelect': (e, tabname) => {
1075
1104
 
@@ -1079,38 +1108,37 @@ class CodeEditor {
1079
1108
  return;
1080
1109
  }
1081
1110
 
1082
- var cursor = cursor ?? this.cursors.children[0];
1083
- this.saveCursor(cursor, this.code.cursorState);
1084
- this.code = this.openedTabs[tabname];
1085
- this.restoreCursor(cursor, this.code.cursorState);
1111
+ var cursor = cursor ?? this.cursors.children[ 0 ];
1112
+ this.saveCursor( cursor, this.code.cursorState );
1113
+ this.code = this.openedTabs[ tabname ];
1114
+ this.restoreCursor( cursor, this.code.cursorState );
1086
1115
  this.endSelection();
1087
- this._changeLanguageFromExtension( LX.getExtension(tabname) );
1088
- this._refreshCodeInfo(cursor.line, cursor.position);
1089
-
1090
- // Restore scroll
1091
- this.gutter.scrollLeft = this.getScrollLeft();
1092
- this.gutter.scrollTop = this.getScrollTop();
1116
+ this._changeLanguageFromExtension( LX.getExtension( tabname ) );
1117
+ this._refreshCodeInfo( cursor.line, cursor.position );
1093
1118
  }});
1119
+
1120
+ // Move into the sizer..
1121
+ this.codeSizer.appendChild( code );
1094
1122
 
1095
1123
  this.endSelection();
1096
1124
 
1097
1125
  if( selected )
1098
1126
  {
1099
1127
  this.code = code;
1100
- this.resetCursorPos(CodeEditor.CURSOR_LEFT | CodeEditor.CURSOR_TOP);
1128
+ this.resetCursorPos( CodeEditor.CURSOR_LEFT | CodeEditor.CURSOR_TOP );
1101
1129
  this.processLines();
1102
- doAsync( () => this._refreshCodeInfo(0, 0), 50 );
1130
+ doAsync( () => this._refreshCodeInfo( 0, 0 ), 50 );
1103
1131
  }
1104
1132
  }
1105
1133
 
1106
1134
  loadTab() {
1107
- const input = document.createElement('input');
1135
+ const input = document.createElement( 'input' );
1108
1136
  input.type = 'file';
1109
- document.body.appendChild(input);
1137
+ document.body.appendChild( input );
1110
1138
  input.click();
1111
- input.addEventListener('change', (e) => {
1112
- if (e.target.files[0]) {
1113
- this.loadFile( e.target.files[0] );
1139
+ input.addEventListener('change', e => {
1140
+ if (e.target.files[ 0 ]) {
1141
+ this.loadFile( e.target.files[ 0 ] );
1114
1142
  }
1115
1143
  input.remove();
1116
1144
  });
@@ -1131,7 +1159,7 @@ class CodeEditor {
1131
1159
  if( !e.target.classList.contains('code') ) return;
1132
1160
  if( !this.code ) return;
1133
1161
 
1134
- var cursor = this.cursors.children[0];
1162
+ var cursor = this.cursors.children[ 0 ];
1135
1163
  var code_rect = this.code.getBoundingClientRect();
1136
1164
  var mouse_pos = [(e.clientX - code_rect.x), (e.clientY - code_rect.y)];
1137
1165
 
@@ -1139,7 +1167,7 @@ class CodeEditor {
1139
1167
  if( e.type != 'contextmenu' )
1140
1168
  {
1141
1169
  var ln = (mouse_pos[1] / this.lineHeight)|0;
1142
- if(this.code.lines[ln] == undefined) return;
1170
+ if(this.code.lines[ ln ] == undefined) return;
1143
1171
  }
1144
1172
 
1145
1173
  if( e.type == 'mousedown' )
@@ -1231,36 +1259,35 @@ class CodeEditor {
1231
1259
  }
1232
1260
  }
1233
1261
 
1234
- processClick(e, skip_refresh = false) {
1262
+ processClick( e, skip_refresh = false ) {
1235
1263
 
1236
- var code_rect = this.code.getBoundingClientRect();
1237
- var position = [(e.clientX - code_rect.x), (e.clientY - code_rect.y)];
1238
- var ln = (position[1] / this.lineHeight)|0;
1264
+ var cursor = this.cursors.children[ 0 ];
1265
+ var code_rect = this.codeScroller.getBoundingClientRect();
1266
+ var position = [( e.clientX - code_rect.x ) + this.getScrollLeft(), (e.clientY - code_rect.y) + this.getScrollTop()];
1267
+ var ln = (position[ 1 ] / this.lineHeight)|0;
1239
1268
 
1240
- if(this.code.lines[ln] == undefined) return;
1269
+ if( this.code.lines[ ln ] == undefined )
1270
+ return;
1241
1271
 
1242
- var cursor = this.cursors.children[0];
1243
- cursor.line = ln;
1244
-
1245
- this.cursorToLine(cursor, ln, true);
1272
+ this.cursorToLine( cursor, ln, true );
1246
1273
 
1247
- var ch = (position[0] / this.charWidth)|0;
1248
- var string = this.code.lines[ln].slice(0, ch);
1249
- this.cursorToPosition(cursor, string.length);
1274
+ var ch = ( ( position[ 0 ] - parseInt( this.xPadding ) + 3) / this.charWidth )|0;
1275
+ var string = this.code.lines[ ln ].slice( 0, ch );
1276
+ this.cursorToPosition( cursor, string.length );
1250
1277
 
1251
1278
  this.hideAutoCompleteBox();
1252
1279
 
1253
- if(!skip_refresh)
1280
+ if( !skip_refresh )
1254
1281
  this._refreshCodeInfo( ln, cursor.position );
1255
1282
  }
1256
1283
 
1257
1284
  processSelection( e, keep_range ) {
1258
1285
 
1259
- var cursor = this.cursors.children[0];
1286
+ var cursor = this.cursors.children[ 0 ];
1260
1287
 
1261
- if(e) this.processClick(e, true);
1288
+ if(e) this.processClick( e, true );
1262
1289
  if( !this.selection )
1263
- this.startSelection(cursor);
1290
+ this.startSelection( cursor );
1264
1291
 
1265
1292
  // Update selection
1266
1293
  if(!keep_range)
@@ -1291,7 +1318,7 @@ class CodeEditor {
1291
1318
  let domEl = this.selections.childNodes[sId];
1292
1319
  if(!domEl)
1293
1320
  {
1294
- domEl = document.createElement('div');
1321
+ domEl = document.createElement( 'div' );
1295
1322
  domEl.className = "lexcodeselection";
1296
1323
  this.selections.appendChild( domEl );
1297
1324
  }
@@ -1302,22 +1329,21 @@ class CodeEditor {
1302
1329
  if(sId == 0) // First line 2 cases (single line, multiline)
1303
1330
  {
1304
1331
  const reverse = fromX > toX;
1305
- if(deltaY == 0) string = !reverse ? this.code.lines[i].substring(fromX, toX) : this.code.lines[i].substring(toX, fromX);
1306
- else string = this.code.lines[i].substr(fromX);
1307
- const pixels = ((reverse && deltaY == 0 ? toX : fromX) * this.charWidth) - this.getScrollLeft();
1332
+ if(deltaY == 0) string = !reverse ? this.code.lines[ i ].substring(fromX, toX) : this.code.lines[ i ].substring(toX, fromX);
1333
+ else string = this.code.lines[ i ].substr(fromX);
1334
+ const pixels = (reverse && deltaY == 0 ? toX : fromX) * this.charWidth;
1308
1335
  domEl.style.left = "calc(" + pixels + "px + " + this.xPadding + ")";
1309
1336
  }
1310
1337
  else
1311
1338
  {
1312
- string = (i == toY) ? this.code.lines[i].substring(0, toX) : this.code.lines[i]; // Last line, any multiple line...
1313
- const pixels = -this.getScrollLeft();
1314
- domEl.style.left = "calc(" + pixels + "px + " + this.xPadding + ")";
1339
+ string = (i == toY) ? this.code.lines[ i ].substring(0, toX) : this.code.lines[ i ]; // Last line, any multiple line...
1340
+ domEl.style.left = this.xPadding;
1315
1341
  }
1316
1342
 
1317
1343
  const stringWidth = this.measureString(string);
1318
1344
  domEl.style.width = (stringWidth || 8) + "px";
1319
- domEl._top = 4 + i * this.lineHeight;
1320
- domEl.style.top = (domEl._top - this.getScrollTop()) + "px";
1345
+ domEl._top = i * this.lineHeight;
1346
+ domEl.style.top = domEl._top + "px";
1321
1347
  this.selection.chars += stringWidth / this.charWidth;
1322
1348
  }
1323
1349
  }
@@ -1334,7 +1360,7 @@ class CodeEditor {
1334
1360
  let domEl = this.selections.childNodes[sId];
1335
1361
  if(!domEl)
1336
1362
  {
1337
- domEl = document.createElement('div');
1363
+ domEl = document.createElement( 'div' );
1338
1364
  domEl.className = "lexcodeselection";
1339
1365
  this.selections.appendChild( domEl );
1340
1366
  }
@@ -1344,20 +1370,20 @@ class CodeEditor {
1344
1370
 
1345
1371
  if(sId == 0)
1346
1372
  {
1347
- string = this.code.lines[i].substr(toX);
1348
- const pixels = (toX * this.charWidth) - this.getScrollLeft();
1373
+ string = this.code.lines[ i ].substr(toX);
1374
+ const pixels = toX * this.charWidth;
1349
1375
  domEl.style.left = "calc(" + pixels + "px + " + this.xPadding + ")";
1350
1376
  }
1351
1377
  else
1352
1378
  {
1353
- string = (i == fromY) ? this.code.lines[i].substring(0, fromX) : this.code.lines[i]; // Last line, any multiple line...
1354
- domEl.style.left = "calc(" + (-this.getScrollLeft()) + "px + " + this.xPadding + ")";
1379
+ string = (i == fromY) ? this.code.lines[ i ].substring(0, fromX) : this.code.lines[ i ]; // Last line, any multiple line...
1380
+ domEl.style.left = this.xPadding;
1355
1381
  }
1356
1382
 
1357
1383
  const stringWidth = this.measureString(string);
1358
1384
  domEl.style.width = (stringWidth || 8) + "px";
1359
- domEl._top = 4 + i * this.lineHeight;
1360
- domEl.style.top = (domEl._top - this.getScrollTop()) + "px";
1385
+ domEl._top = i * this.lineHeight;
1386
+ domEl.style.top = domEl._top + "px";
1361
1387
  this.selection.chars += stringWidth / this.charWidth;
1362
1388
  }
1363
1389
  }
@@ -1376,9 +1402,9 @@ class CodeEditor {
1376
1402
  if( key.length > 1 && this.specialKeys.indexOf(key) == -1 )
1377
1403
  return;
1378
1404
 
1379
- let cursor = this.cursors.children[0];
1405
+ let cursor = this.cursors.children[ 0 ];
1380
1406
  let lidx = cursor.line;
1381
- this.code.lines[lidx] = this.code.lines[lidx] ?? "";
1407
+ this.code.lines[ lidx ] = this.code.lines[ lidx ] ?? "";
1382
1408
 
1383
1409
  // Check combinations
1384
1410
 
@@ -1388,22 +1414,22 @@ class CodeEditor {
1388
1414
  case 'a': // select all
1389
1415
  e.preventDefault();
1390
1416
  this.resetCursorPos( CodeEditor.CURSOR_LEFT | CodeEditor.CURSOR_TOP );
1391
- this.startSelection(cursor);
1417
+ this.startSelection( cursor );
1392
1418
  const nlines = this.code.lines.length - 1;
1393
- this.selection.toX = this.code.lines[nlines].length;
1419
+ this.selection.toX = this.code.lines[ nlines ].length;
1394
1420
  this.selection.toY = nlines;
1395
- this.processSelection(null, true);
1396
- this.cursorToPosition(cursor, this.selection.toX);
1397
- this.cursorToLine(cursor, this.selection.toY);
1421
+ this.processSelection( null, true );
1422
+ this.cursorToPosition( cursor, this.selection.toX );
1423
+ this.cursorToLine( cursor, this.selection.toY );
1398
1424
  break;
1399
1425
  case 'c': // copy
1400
1426
  this._copyContent();
1401
1427
  return;
1402
1428
  case 'd': // duplicate line
1403
1429
  e.preventDefault();
1404
- this.code.lines.splice(lidx, 0, this.code.lines[lidx]);
1430
+ this.code.lines.splice( lidx, 0, this.code.lines[ lidx ] );
1405
1431
  this.lineDown( cursor );
1406
- this.processLines(lidx);
1432
+ this.processLines();
1407
1433
  return;
1408
1434
  case 's': // save
1409
1435
  e.preventDefault();
@@ -1424,8 +1450,6 @@ class CodeEditor {
1424
1450
  this.restoreCursor( cursor, step.cursor );
1425
1451
  this.processLines();
1426
1452
  return;
1427
-
1428
-
1429
1453
  }
1430
1454
  }
1431
1455
 
@@ -1509,14 +1533,14 @@ class CodeEditor {
1509
1533
  // Append key
1510
1534
 
1511
1535
  const isPairKey = (Object.values( this.pairKeys ).indexOf( key ) > -1) && !this.wasKeyPaired;
1512
- const sameKeyNext = isPairKey && (this.code.lines[lidx][cursor.position] === key);
1536
+ const sameKeyNext = isPairKey && (this.code.lines[ lidx ][cursor.position] === key);
1513
1537
 
1514
1538
  if( !sameKeyNext )
1515
1539
  {
1516
- this.code.lines[lidx] = [
1517
- this.code.lines[lidx].slice(0, cursor.position),
1540
+ this.code.lines[ lidx ] = [
1541
+ this.code.lines[ lidx ].slice(0, cursor.position),
1518
1542
  key,
1519
- this.code.lines[lidx].slice(cursor.position)
1543
+ this.code.lines[ lidx ].slice(cursor.position)
1520
1544
  ].join('');
1521
1545
  }
1522
1546
 
@@ -1556,11 +1580,11 @@ class CodeEditor {
1556
1580
 
1557
1581
  async _copyContent() {
1558
1582
 
1559
- let cursor = this.cursors.children[0];
1583
+ let cursor = this.cursors.children[ 0 ];
1560
1584
  let text_to_copy = "";
1561
1585
 
1562
1586
  if( !this.selection ) {
1563
- text_to_copy = "\n" + this.code.lines[cursor.line];
1587
+ text_to_copy = "\n" + this.code.lines[ cursor.line ];
1564
1588
  }
1565
1589
  else {
1566
1590
  const separator = "_NEWLINE_";
@@ -1570,7 +1594,7 @@ class CodeEditor {
1570
1594
  let index = 0;
1571
1595
 
1572
1596
  for(let i = 0; i <= this.selection.fromY; i++)
1573
- index += (i == this.selection.fromY ? this.selection.fromX : this.code.lines[i].length);
1597
+ index += (i == this.selection.fromY ? this.selection.fromX : this.code.lines[ i ].length);
1574
1598
 
1575
1599
  index += this.selection.fromY * separator.length;
1576
1600
  const num_chars = this.selection.chars + (this.selection.toY - this.selection.fromY) * separator.length;
@@ -1584,14 +1608,14 @@ class CodeEditor {
1584
1608
 
1585
1609
  async _cutContent() {
1586
1610
 
1587
- let cursor = this.cursors.children[0];
1611
+ let cursor = this.cursors.children[ 0 ];
1588
1612
  let lidx = cursor.line;
1589
1613
  let text_to_cut = "";
1590
1614
 
1591
1615
  if( !this.selection ) {
1592
- text_to_cut = "\n" + this.code.lines[cursor.line];
1593
- this.code.lines.splice(lidx, 1);
1594
- this.processLines(lidx);
1616
+ text_to_cut = "\n" + this.code.lines[ cursor.line ];
1617
+ this.code.lines.splice( lidx, 1 );
1618
+ this.processLines();
1595
1619
  this.resetCursorPos( CodeEditor.CURSOR_LEFT );
1596
1620
  }
1597
1621
  else {
@@ -1602,7 +1626,7 @@ class CodeEditor {
1602
1626
  let index = 0;
1603
1627
 
1604
1628
  for(let i = 0; i <= this.selection.fromY; i++)
1605
- index += (i == this.selection.fromY ? this.selection.fromX : this.code.lines[i].length);
1629
+ index += (i == this.selection.fromY ? this.selection.fromX : this.code.lines[ i ].length);
1606
1630
 
1607
1631
  index += this.selection.fromY * separator.length;
1608
1632
  const num_chars = this.selection.chars + (this.selection.toY - this.selection.fromY) * separator.length;
@@ -1631,70 +1655,117 @@ class CodeEditor {
1631
1655
  for( let i = 0; i < this.code.lines.length; ++i )
1632
1656
  {
1633
1657
  const linestring = this.code.lines[ i ];
1634
- const tokens = this._getTokensFromString( linestring, true );
1658
+ const tokens = this._getTokensFromLine( linestring, true );
1635
1659
  tokens.forEach( t => this.code.tokens[ t ] = 1 );
1636
1660
  }
1637
1661
  }
1638
1662
 
1639
- processLines( from__legacy ) {
1663
+ toLocalLine( line ) {
1640
1664
 
1641
- const start = performance.now();
1665
+ const d = Math.max( this.viewportRangeStart - this.lineScrollMargin.x, 0 );
1666
+ return Math.min( Math.max( line - d, 0 ), this.code.lines.length - 1 );
1667
+ }
1668
+
1669
+ getMaxLineLength() {
1670
+
1671
+ return Math.max(...this.code.lines.map( v => v.length ));
1672
+ }
1673
+
1674
+ processLines( mode ) {
1675
+
1676
+ mode = mode ?? CodeEditor.KEEP_VISIBLE_LINES;
1642
1677
 
1643
- var gutter_html = "";
1644
- var code_html = "";
1678
+ // console.clear();
1679
+ console.log("--------------------------------------------");
1645
1680
 
1646
- this.resizeScrollBars();
1681
+ const lastScrollTop = this.getScrollTop();
1682
+ const start = performance.now();
1683
+
1684
+ var gutter_html = "", code_html = "";
1647
1685
 
1648
1686
  this.code.innerHTML = "";
1649
- this.gutter.innerHTML = "";
1650
1687
 
1651
1688
  // Get info about lines in viewport
1652
- const margin = 20;
1653
- const firstLineInViewport = (this.getScrollTop() / this.lineHeight)|0;
1654
- const totalLinesInViewport = ((this.code.parentElement.offsetHeight - 36) / this.lineHeight)|0;
1689
+ const firstLineInViewport = mode & CodeEditor.UPDATE_VISIBLE_LINES ?
1690
+ ( (lastScrollTop / this.lineHeight)|0 ) : this.viewportRangeStart;
1691
+ const totalLinesInViewport = ((this.codeScroller.offsetHeight - 36) / this.lineHeight)|0;
1692
+ this.viewportRangeStart = firstLineInViewport;
1693
+
1655
1694
  const viewportRange = new LX.vec2(
1656
- Math.max( firstLineInViewport - margin, 0 ),
1657
- Math.min( firstLineInViewport + totalLinesInViewport + margin, this.code.lines.length )
1695
+ Math.max( firstLineInViewport - this.lineScrollMargin.x, 0 ),
1696
+ Math.min( firstLineInViewport + totalLinesInViewport + this.lineScrollMargin.y, this.code.lines.length )
1658
1697
  );
1659
1698
 
1699
+ // Add remaining lines if we are near the end of the scroll
1700
+ {
1701
+ const diff = Math.max( this.code.lines.length - viewportRange.y, 0 );
1702
+ if( diff < ( totalLinesInViewport + this.lineScrollMargin.y ) )
1703
+ viewportRange.y += diff;
1704
+ }
1705
+
1660
1706
  for( let i = viewportRange.x; i < viewportRange.y; ++i )
1661
1707
  {
1662
1708
  gutter_html += "<span>" + (i + 1) + "</span>";
1663
1709
  code_html += this.processLine( i, true );
1664
1710
  }
1665
-
1666
- this.code.innerHTML = code_html;
1667
- this.gutter.innerHTML = gutter_html;
1668
1711
 
1669
- console.log( "Num lines processed: " + (viewportRange.y - viewportRange.x), performance.now() - start );
1712
+ this.code.innerHTML = code_html;
1713
+
1714
+ console.log("RANGE:", viewportRange);
1715
+ console.log( "Num lines processed:", (viewportRange.y - viewportRange.x), performance.now() - start );
1716
+ console.log("--------------------------------------------");
1717
+
1718
+ this.codeScroller.scrollTop = lastScrollTop;
1719
+
1720
+ setTimeout( () => {
1721
+
1722
+ // Update max viewport
1723
+ const scrollWidth = this.getMaxLineLength() * this.charWidth;
1724
+ const scrollHeight = this.code.lines.length * this.lineHeight + 10; // scrollbar offset
1725
+
1726
+ this.codeSizer.style.minWidth = scrollWidth + "px";
1727
+ this.codeSizer.style.minHeight = scrollHeight + "px";
1728
+
1729
+ this.resizeScrollBars( totalLinesInViewport );
1730
+
1731
+ }, 10 );
1670
1732
  }
1671
1733
 
1672
1734
  processLine( linenum, force ) {
1673
1735
 
1674
- delete this._buildingString; // multi-line strings not supported by now
1736
+ const local_line_num = this.toLocalLine( linenum );
1737
+ const gutter_line = "<span class='line-gutter'>" + (linenum + 1) + "</span>";
1738
+
1739
+ const UPDATE_LINE = ( html ) => {
1740
+ if( !force ) // Single line update
1741
+ this.code.childNodes[ local_line_num ].innerHTML = gutter_line + html;
1742
+ else // Update all lines at once
1743
+ return "<pre>" + ( gutter_line + html ) + "</pre>";
1744
+ }
1745
+
1746
+ // multi-line strings not supported by now
1747
+ delete this._buildingString;
1748
+ delete this._pendingString;
1675
1749
 
1676
- // It's allowed to process only 1 line to optimize
1677
1750
  let linestring = this.code.lines[ linenum ];
1678
1751
 
1752
+ // Single line
1679
1753
  if( !force )
1680
1754
  {
1681
- var pre = document.createElement('pre');
1755
+ deleteElement( this.code.childNodes[ local_line_num ] );
1756
+ this.code.insertChildAtIndex( document.createElement( 'pre' ), local_line_num );
1757
+ }
1682
1758
 
1683
- // Single code line
1684
- deleteElement( this.code.childNodes[ linenum ] );
1685
- this.code.insertChildAtIndex( pre, linenum );
1686
-
1687
- // Gutter
1688
- deleteElement( this.gutter.childNodes[ linenum ] );
1689
- var linenumspan = document.createElement('span');
1690
- linenumspan.innerHTML = (linenum + 1);
1691
- this.gutter.insertChildAtIndex( linenumspan, linenum );
1759
+ // Early out check for no highlighting languages
1760
+ if( this.highlight == 'Plain Text' )
1761
+ {
1762
+ return UPDATE_LINE( linestring );
1692
1763
  }
1693
1764
 
1694
- const tokensToEvaluate = this._getTokensFromString( linestring );
1765
+ const tokensToEvaluate = this._getTokensFromLine( linestring );
1695
1766
 
1696
1767
  if( !tokensToEvaluate.length )
1697
- return "<pre></pre>";
1768
+ return "<pre><span class='line-gutter'>" + linenum + "</span></pre>";
1698
1769
 
1699
1770
  var line_inner_html = "";
1700
1771
 
@@ -1702,58 +1773,49 @@ class CodeEditor {
1702
1773
  for( var i = 0; i < tokensToEvaluate.length; ++i )
1703
1774
  {
1704
1775
  let it = i - 1;
1705
- let prev = tokensToEvaluate[it];
1776
+ let prev = tokensToEvaluate[ it ];
1706
1777
  while( prev == ' ' ) {
1707
1778
  it--;
1708
- prev = tokensToEvaluate[it];
1779
+ prev = tokensToEvaluate[ it ];
1709
1780
  }
1710
1781
 
1711
1782
  it = i + 1;
1712
- let next = tokensToEvaluate[it];
1783
+ let next = tokensToEvaluate[ it ];
1713
1784
  while( next == ' ' || next == '"' ) {
1714
1785
  it++;
1715
- next = tokensToEvaluate[it];
1786
+ next = tokensToEvaluate[ it ];
1716
1787
  }
1717
1788
 
1718
- const token = tokensToEvaluate[i];
1789
+ const token = tokensToEvaluate[ i ];
1719
1790
 
1720
1791
  if( this.languages[ this.highlight ].blockComments ?? true )
1721
1792
  {
1722
- if( token.substr(0, 2) == '/*' )
1793
+ if( token.substr( 0, 2 ) == '/*' )
1723
1794
  this._buildingBlockComment = true;
1724
- if( token.substr(token.length - 2) == '*/' )
1795
+ if( token.substr( token.length - 2 ) == '*/' )
1725
1796
  delete this._buildingBlockComment;
1726
1797
  }
1727
1798
 
1728
- line_inner_html += this.evaluateToken(token, prev, next);
1799
+ line_inner_html += this.evaluateToken( token, prev, next, (i == tokensToEvaluate.length - 1) );
1729
1800
  }
1730
1801
 
1731
- // Single line update
1732
- if( !force )
1733
- {
1734
- this.code.childNodes[ linenum ].innerHTML = line_inner_html;
1735
- }
1736
- // Update all lines at once
1737
- else
1738
- {
1739
- return "<pre>" + line_inner_html + "</pre>";
1740
- }
1802
+ return UPDATE_LINE( line_inner_html );
1741
1803
  }
1742
1804
 
1743
1805
  _processTokens( tokens, offset = 0 ) {
1744
1806
 
1745
1807
  if( this.highlight == 'C++' )
1746
1808
  {
1747
- var idx = tokens.slice(offset).findIndex( (value, index) => this.isNumber(value) );
1809
+ var idx = tokens.slice( offset ).findIndex( ( value, index ) => this.isNumber( value ) );
1748
1810
  if( idx > -1 )
1749
1811
  {
1750
1812
  idx += offset; // Add offset to compute within the whole array of tokens
1751
- let data = tokens[idx] + tokens[++idx];
1813
+ let data = tokens[ idx ] + tokens[ ++idx ];
1752
1814
  while( this.isNumber( data ) )
1753
1815
  {
1754
1816
  tokens[ idx - 1 ] += tokens[ idx ];
1755
1817
  tokens.splice( idx, 1 );
1756
- data += tokens[idx];
1818
+ data += tokens[ idx ];
1757
1819
  }
1758
1820
  // Scan for numbers again
1759
1821
  return this._processTokens( tokens, idx );
@@ -1763,16 +1825,43 @@ class CodeEditor {
1763
1825
  return tokens;
1764
1826
  }
1765
1827
 
1766
- _getTokensFromString( linestring, skipNonWords ) {
1828
+ _lineHasComment( linestring ) {
1767
1829
 
1768
- // Check if line comment
1769
1830
  const singleLineCommentToken = this.languages[ this.highlight ].singleLineCommentToken ?? this.defaultSingleLineCommentToken;
1770
- const usesBlockComments = this.languages[ this.highlight ].blockComments ?? true;
1771
- const has_comment = linestring.split(singleLineCommentToken);
1772
- linestring = ( has_comment.length > 1 ) ? has_comment[0] : linestring;
1773
-
1774
- // const tokens = linestring.split(' ').join('¬ ¬').split('¬'); // trick to split without losing spaces
1831
+ const idx = linestring.indexOf( singleLineCommentToken );
1775
1832
 
1833
+ if( idx > -1 )
1834
+ {
1835
+ const stringKeys = Object.values( this.stringKeys );
1836
+ // Count times we started a string BEFORE the comment
1837
+ var err = false;
1838
+ err |= stringKeys.some( function(v) {
1839
+ var re = new RegExp( v, "g" );
1840
+ var matches = (linestring.substring( 0, idx ).match( re ) || []);
1841
+ return (matches.length % 2) !== 0;
1842
+ } );
1843
+ err |= stringKeys.some( function(v) {
1844
+ var re = new RegExp( v, "g" );
1845
+ var matches = (linestring.substring( idx ).match( re ) || []);
1846
+ return (matches.length % 2) !== 0;
1847
+ } );
1848
+ return err ? undefined : idx;
1849
+ }
1850
+ }
1851
+
1852
+ _getTokensFromLine( linestring, skipNonWords ) {
1853
+
1854
+ // Check if line comment
1855
+ const ogLine = linestring;
1856
+ const hasCommentIdx = this._lineHasComment( linestring );
1857
+
1858
+ if( hasCommentIdx != undefined )
1859
+ {
1860
+ linestring = ogLine.substring( 0, hasCommentIdx );
1861
+ }
1862
+
1863
+ // const usesBlockComments = this.languages[ this.highlight ].blockComments ?? true;
1864
+
1776
1865
  let tokensToEvaluate = []; // store in a temp array so we know prev and next tokens...
1777
1866
 
1778
1867
  const pushToken = function( t ) {
@@ -1790,8 +1879,8 @@ class CodeEditor {
1790
1879
  {
1791
1880
  const _pt = linestring.substring(idx, subtokens.value.index);
1792
1881
  if( _pt.length ) pushToken( _pt );
1793
- pushToken( subtokens.value[0] );
1794
- idx = subtokens.value.index + subtokens.value[0].length;
1882
+ pushToken( subtokens.value[ 0 ] );
1883
+ idx = subtokens.value.index + subtokens.value[ 0 ].length;
1795
1884
  subtokens = iter.next();
1796
1885
  if(!subtokens.value) {
1797
1886
  const _at = linestring.substring(idx);
@@ -1821,10 +1910,10 @@ class CodeEditor {
1821
1910
  // if( block ) continue;
1822
1911
  // }
1823
1912
 
1824
- if( has_comment.length > 1 && !skipNonWords )
1825
- pushToken( singleLineCommentToken + has_comment[1] );
1826
-
1827
- // console.log( tokensToEvaluate );
1913
+ if( hasCommentIdx != undefined )
1914
+ {
1915
+ pushToken( ogLine.substring( hasCommentIdx ) );
1916
+ }
1828
1917
 
1829
1918
  return this._processTokens( tokensToEvaluate );
1830
1919
  }
@@ -1834,45 +1923,51 @@ class CodeEditor {
1834
1923
  return kindArray[this.highlight] && kindArray[this.highlight][token] != undefined;
1835
1924
  }
1836
1925
 
1837
- evaluateToken( token, prev, next ) {
1926
+ evaluateToken( token, prev, next, isLastToken ) {
1927
+
1928
+ const highlight = this.highlight.replace(/\s/g, '').replaceAll("+", "p").toLowerCase();
1929
+ const customStringKeys = Object.assign( {}, this.stringKeys );
1930
+
1931
+ if( highlight == 'cpp' && prev && prev.includes('#') ) // preprocessor code..
1932
+ {
1933
+ customStringKeys['@<'] = '>';
1934
+ }
1935
+
1936
+ // Manage strings
1937
+ this._stringEnded = false;
1938
+ if( this._buildingString != undefined )
1939
+ {
1940
+ const idx = Object.values(customStringKeys).indexOf( token );
1941
+ this._stringEnded = (idx > -1) && (idx == Object.values(customStringKeys).indexOf( customStringKeys[ '@' + this._buildingString ] ));
1942
+ }
1943
+ else if( customStringKeys[ '@' + token ] )
1944
+ {
1945
+ // Start new string
1946
+ this._buildingString = token;
1947
+ }
1838
1948
 
1839
1949
  if(token == ' ')
1840
1950
  {
1951
+ if( this._buildingString != undefined )
1952
+ {
1953
+ this.appendStringToken( token );
1954
+ return "";
1955
+ }
1841
1956
  return token;
1842
1957
  }
1843
1958
  else
1844
1959
  {
1845
- let stringEnded = false;
1846
- let highlight = this.highlight.replace(/\s/g, '').replaceAll("+", "p").toLowerCase();
1847
-
1848
1960
  const singleLineCommentToken = this.languages[ this.highlight ].singleLineCommentToken ?? this.defaultSingleLineCommentToken;
1849
1961
  const usesBlockComments = this.languages[ this.highlight ].blockComments ?? true;
1850
- const customStringKeys = Object.assign( {}, this.stringKeys );
1851
-
1852
- if( highlight == 'cpp' && prev && prev.includes('#') ) // preprocessor code..
1853
- {
1854
- customStringKeys['@<'] = '>';
1855
- }
1856
-
1857
- // Manage strings
1858
- if( this._buildingString != undefined )
1859
- {
1860
- const idx = Object.values(customStringKeys).indexOf( token );
1861
- stringEnded = (idx > -1) && (idx == Object.values(customStringKeys).indexOf( customStringKeys[ '@' + this._buildingString ] ));
1862
- }
1863
- else if( customStringKeys[ '@' + token ] )
1864
- {
1865
- // Start new string
1866
- this._buildingString = token;
1867
- }
1868
-
1962
+
1869
1963
  let token_classname = "";
1964
+ let discardToken = false;
1870
1965
 
1871
1966
  if( this._buildingBlockComment != undefined )
1872
1967
  token_classname = "cm-com";
1873
1968
 
1874
1969
  else if( this._buildingString != undefined )
1875
- token_classname = "cm-str";
1970
+ discardToken = this.appendStringToken( token );
1876
1971
 
1877
1972
  else if( this._mustHightlightWord( token, this.keywords ) )
1878
1973
  token_classname = "cm-kwd";
@@ -1922,15 +2017,49 @@ class CodeEditor {
1922
2017
  else if ( highlight == 'css' && prev == undefined && next == ':' ) // CSS attribute
1923
2018
  token_classname = "cm-typ";
1924
2019
 
1925
- else if ( token[0] != '@' && next == '(' )
2020
+ else if ( token[ 0 ] != '@' && token[ 0 ] != ',' && next == '(' )
1926
2021
  token_classname = "cm-mtd";
1927
2022
 
1928
- this._buildingString = stringEnded ? undefined : this._buildingString;
2023
+
2024
+ // We finished constructing a string
2025
+ if( this._buildingString && ( this._stringEnded || isLastToken ) )
2026
+ {
2027
+ token = this.getCurrentString();
2028
+ token_classname = "cm-str";
2029
+ discardToken = false;
2030
+ }
2031
+
2032
+ // Update state
2033
+ this._buildingString = this._stringEnded ? undefined : this._buildingString;
2034
+
2035
+ if( discardToken )
2036
+ return "";
2037
+
2038
+ // No highlighting, no need to put it inside another span..
2039
+ if( !token_classname.length )
2040
+ return token;
1929
2041
 
1930
2042
  return "<span class='" + highlight + " " + token_classname + "'>" + token + "</span>";
1931
2043
  }
1932
2044
  }
1933
2045
 
2046
+ appendStringToken( token ) {
2047
+
2048
+ if( !this._pendingString )
2049
+ this._pendingString = "";
2050
+
2051
+ this._pendingString += token;
2052
+
2053
+ return true;
2054
+ }
2055
+
2056
+ getCurrentString() {
2057
+
2058
+ const chars = this._pendingString;
2059
+ delete this._pendingString;
2060
+ return chars;
2061
+ }
2062
+
1934
2063
  isCSSClass( token, prev, next ) {
1935
2064
  return this.highlight == 'CSS' && prev == '.';
1936
2065
  }
@@ -1980,10 +2109,10 @@ class CodeEditor {
1980
2109
  this.selection.invertIfNecessary();
1981
2110
 
1982
2111
  // Insert first..
1983
- this.code.lines[lidx] = [
1984
- this.code.lines[lidx].slice(0, this.selection.fromX),
2112
+ this.code.lines[ lidx ] = [
2113
+ this.code.lines[ lidx ].slice(0, this.selection.fromX),
1985
2114
  key,
1986
- this.code.lines[lidx].slice(this.selection.fromX)
2115
+ this.code.lines[ lidx ].slice(this.selection.fromX)
1987
2116
  ].join('');
1988
2117
 
1989
2118
  // Go to the end of the word
@@ -2000,10 +2129,10 @@ class CodeEditor {
2000
2129
  }
2001
2130
 
2002
2131
  // Insert the other
2003
- this.code.lines[lidx] = [
2004
- this.code.lines[lidx].slice(0, cursor.position),
2132
+ this.code.lines[ lidx ] = [
2133
+ this.code.lines[ lidx ].slice(0, cursor.position),
2005
2134
  key,
2006
- this.code.lines[lidx].slice(cursor.position)
2135
+ this.code.lines[ lidx ].slice(cursor.position)
2007
2136
  ].join('');
2008
2137
 
2009
2138
  // Recompute and reposition current selection
@@ -2018,19 +2147,19 @@ class CodeEditor {
2018
2147
  return true;
2019
2148
  }
2020
2149
 
2021
- lineUp(cursor, resetLeft) {
2150
+ lineUp( cursor, resetLeft ) {
2022
2151
 
2023
- cursor = cursor ?? this.cursors.children[0];
2152
+ cursor = cursor ?? this.cursors.children[ 0 ];
2024
2153
  cursor.line--;
2025
- cursor.line = Math.max(0, cursor.line);
2026
- this.cursorToTop(cursor, resetLeft);
2154
+ cursor.line = Math.max( 0, cursor.line );
2155
+ this.cursorToTop( cursor, resetLeft );
2027
2156
  }
2028
2157
 
2029
- lineDown(cursor, resetLeft) {
2158
+ lineDown( cursor, resetLeft ) {
2030
2159
 
2031
- cursor = cursor ?? this.cursors.children[0];
2160
+ cursor = cursor ?? this.cursors.children[ 0 ];
2032
2161
  cursor.line++;
2033
- this.cursorToBottom(cursor, resetLeft);
2162
+ this.cursorToBottom( cursor, resetLeft );
2034
2163
  }
2035
2164
 
2036
2165
  restartBlink() {
@@ -2057,40 +2186,40 @@ class CodeEditor {
2057
2186
  this.selections.classList.add('show');
2058
2187
 
2059
2188
  // Create new selection instance
2060
- this.selection = new ISelection(this, cursor.position, cursor.line);
2189
+ this.selection = new CodeSelection(this, cursor.position, cursor.line);
2061
2190
  }
2062
2191
 
2063
2192
  deleteSelection( cursor ) {
2064
2193
 
2065
2194
  // I think it's not necessary but...
2066
- if(this.disableEdition)
2195
+ if( this.disableEdition )
2067
2196
  return;
2068
2197
 
2069
2198
  // Some selections don't depend on mouse up..
2070
- if(this.selection) this.selection.invertIfNecessary();
2199
+ if( this.selection ) this.selection.invertIfNecessary();
2071
2200
 
2072
2201
  const separator = "_NEWLINE_";
2073
- let code = this.code.lines.join(separator);
2202
+ let code = this.code.lines.join( separator );
2074
2203
 
2075
2204
  // Get linear start index
2076
2205
  let index = 0;
2077
2206
  for(let i = 0; i <= this.selection.fromY; i++)
2078
- index += (i == this.selection.fromY ? this.selection.fromX : this.code.lines[i].length);
2207
+ index += (i == this.selection.fromY ? this.selection.fromX : this.code.lines[ i ].length);
2079
2208
 
2080
2209
  index += this.selection.fromY * separator.length;
2081
2210
 
2082
2211
  const num_chars = this.selection.chars + (this.selection.toY - this.selection.fromY) * separator.length;
2083
- const pre = code.slice(0, index);
2084
- const post = code.slice(index + num_chars);
2212
+ const pre = code.slice( 0, index );
2213
+ const post = code.slice( index + num_chars );
2085
2214
 
2086
- this.code.lines = (pre + post).split(separator);
2087
- this.processLines(this.selection.fromY);
2215
+ this.code.lines = ( pre + post ).split( separator );
2216
+ this.processLines();
2088
2217
 
2089
- this.cursorToLine(cursor, this.selection.fromY, true);
2090
- this.cursorToPosition(cursor, this.selection.fromX);
2218
+ this.cursorToLine( cursor, this.selection.fromY, true );
2219
+ this.cursorToPosition( cursor, this.selection.fromX );
2220
+
2091
2221
  this.endSelection();
2092
-
2093
- this._refreshCodeInfo(cursor.line, cursor.position);
2222
+ this._refreshCodeInfo( cursor.line, cursor.position );
2094
2223
  }
2095
2224
 
2096
2225
  endSelection() {
@@ -2102,10 +2231,10 @@ class CodeEditor {
2102
2231
 
2103
2232
  cursorToRight( key, cursor ) {
2104
2233
 
2105
- if(!key) return;
2106
- cursor = cursor ?? this.cursors.children[0];
2234
+ if( !key ) return;
2235
+ cursor = cursor ?? this.cursors.children[ 0 ];
2107
2236
  cursor._left += this.charWidth;
2108
- cursor.style.left = "calc(" + (cursor._left - this.getScrollLeft()) + "px + " + this.xPadding + ")";
2237
+ cursor.style.left = "calc( " + cursor._left + "px + " + this.xPadding + " )";
2109
2238
  cursor.position++;
2110
2239
  this.restartBlink();
2111
2240
  this._refreshCodeInfo( cursor.line, cursor.position );
@@ -2113,7 +2242,7 @@ class CodeEditor {
2113
2242
  // Add horizontal scroll
2114
2243
 
2115
2244
  doAsync(() => {
2116
- var last_char = ((this.code.clientWidth) / this.charWidth)|0;
2245
+ var last_char = ((this.codeScroller.clientWidth + this.getScrollLeft()) / this.charWidth)|0;
2117
2246
  if( cursor.position >= last_char )
2118
2247
  this.setScrollLeft( this.getScrollLeft() + this.charWidth );
2119
2248
  });
@@ -2122,10 +2251,10 @@ class CodeEditor {
2122
2251
  cursorToLeft( key, cursor ) {
2123
2252
 
2124
2253
  if(!key) return;
2125
- cursor = cursor ?? this.cursors.children[0];
2254
+ cursor = cursor ?? this.cursors.children[ 0 ];
2126
2255
  cursor._left -= this.charWidth;
2127
2256
  cursor._left = Math.max(cursor._left, 0);
2128
- cursor.style.left = "calc(" + (cursor._left - this.getScrollLeft()) + "px + " + this.xPadding + ")";
2257
+ cursor.style.left = "calc( " + cursor._left + "px + " + this.xPadding + " )";
2129
2258
  cursor.position--;
2130
2259
  cursor.position = Math.max(cursor.position, 0);
2131
2260
  this.restartBlink();
@@ -2142,10 +2271,10 @@ class CodeEditor {
2142
2271
 
2143
2272
  cursorToTop( cursor, resetLeft = false ) {
2144
2273
 
2145
- cursor = cursor ?? this.cursors.children[0];
2274
+ cursor = cursor ?? this.cursors.children[ 0 ];
2146
2275
  cursor._top -= this.lineHeight;
2147
- cursor._top = Math.max(cursor._top, 4);
2148
- cursor.style.top = "calc(" + (cursor._top - this.getScrollTop()) + "px)";
2276
+ cursor._top = Math.max(cursor._top, 0);
2277
+ cursor.style.top = "calc(" + cursor._top + "px)";
2149
2278
  this.restartBlink();
2150
2279
 
2151
2280
  if(resetLeft)
@@ -2162,9 +2291,9 @@ class CodeEditor {
2162
2291
 
2163
2292
  cursorToBottom( cursor, resetLeft = false ) {
2164
2293
 
2165
- cursor = cursor ?? this.cursors.children[0];
2294
+ cursor = cursor ?? this.cursors.children[ 0 ];
2166
2295
  cursor._top += this.lineHeight;
2167
- cursor.style.top = "calc(" + (cursor._top - this.getScrollTop()) + "px)";
2296
+ cursor.style.top = "calc(" + cursor._top + "px)";
2168
2297
  this.restartBlink();
2169
2298
 
2170
2299
  if(resetLeft)
@@ -2173,7 +2302,7 @@ class CodeEditor {
2173
2302
  this._refreshCodeInfo( cursor.line, cursor.position );
2174
2303
 
2175
2304
  doAsync(() => {
2176
- var last_line = ((this.code.parentElement.offsetHeight - 32) / this.lineHeight)|0;
2305
+ var last_line = ((this.codeScroller.offsetHeight + this.getScrollTop()) / this.lineHeight)|0;
2177
2306
  if( cursor.line >= last_line )
2178
2307
  this.setScrollTop( this.getScrollTop() + this.lineHeight );
2179
2308
  });
@@ -2181,7 +2310,7 @@ class CodeEditor {
2181
2310
 
2182
2311
  cursorToString( cursor, text, reverse ) {
2183
2312
 
2184
- cursor = cursor ?? this.cursors.children[0];
2313
+ cursor = cursor ?? this.cursors.children[ 0 ];
2185
2314
  for( let char of text )
2186
2315
  reverse ? this.cursorToLeft(char) : this.cursorToRight(char);
2187
2316
  }
@@ -2190,20 +2319,20 @@ class CodeEditor {
2190
2319
 
2191
2320
  cursor.position = position;
2192
2321
  cursor._left = position * this.charWidth;
2193
- cursor.style.left = "calc(" + (cursor._left - this.getScrollLeft()) + "px + " + this.xPadding + ")";
2322
+ cursor.style.left = "calc(" + cursor._left + "px + " + this.xPadding + ")";
2194
2323
  }
2195
2324
 
2196
2325
  cursorToLine( cursor, line, resetLeft = false ) {
2197
2326
 
2198
2327
  cursor.line = line;
2199
- cursor._top = 4 + this.lineHeight * line;
2200
- cursor.style.top = (cursor._top - this.getScrollTop()) + "px";
2328
+ cursor._top = this.lineHeight * line;
2329
+ cursor.style.top = cursor._top + "px";
2201
2330
  if(resetLeft) this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
2202
2331
  }
2203
2332
 
2204
2333
  saveCursor( cursor, state = {} ) {
2205
2334
 
2206
- var cursor = cursor ?? this.cursors.children[0];
2335
+ var cursor = cursor ?? this.cursors.children[ 0 ];
2207
2336
  state.top = cursor._top;
2208
2337
  state.left = cursor._left;
2209
2338
  state.line = cursor.line;
@@ -2213,19 +2342,19 @@ class CodeEditor {
2213
2342
 
2214
2343
  restoreCursor( cursor, state ) {
2215
2344
 
2216
- cursor = cursor ?? this.cursors.children[0];
2345
+ cursor = cursor ?? this.cursors.children[ 0 ];
2217
2346
  cursor.line = state.line ?? 0;
2218
2347
  cursor.position = state.charPos ?? 0;
2219
2348
 
2220
2349
  cursor._left = state.left ?? 0;
2221
2350
  cursor.style.left = "calc(" + (cursor._left - this.getScrollLeft()) + "px + " + this.xPadding + ")";
2222
- cursor._top = state.top ?? 4;
2351
+ cursor._top = state.top ?? 0;
2223
2352
  cursor.style.top = "calc(" + (cursor._top - this.getScrollTop()) + "px)";
2224
2353
  }
2225
2354
 
2226
2355
  resetCursorPos( flag, cursor ) {
2227
2356
 
2228
- cursor = cursor ?? this.cursors.children[0];
2357
+ cursor = cursor ?? this.cursors.children[ 0 ];
2229
2358
 
2230
2359
  if( flag & CodeEditor.CURSOR_LEFT )
2231
2360
  {
@@ -2236,7 +2365,7 @@ class CodeEditor {
2236
2365
 
2237
2366
  if( flag & CodeEditor.CURSOR_TOP )
2238
2367
  {
2239
- cursor._top = 4;
2368
+ cursor._top = 0;
2240
2369
  cursor.style.top = (cursor._top - this.getScrollTop()) + "px";
2241
2370
  cursor.line = 0;
2242
2371
  }
@@ -2261,160 +2390,141 @@ class CodeEditor {
2261
2390
 
2262
2391
  getScrollLeft() {
2263
2392
 
2264
- if(!this.code) return 0;
2265
- return this.code.customScroll.x;
2393
+ if(!this.codeScroller) return 0;
2394
+ return this.codeScroller.scrollLeft;
2266
2395
  }
2267
2396
 
2268
2397
  getScrollTop() {
2269
2398
 
2270
- if(!this.code) return 0;
2271
- return this.code.customScroll.y;
2399
+ if(!this.codeScroller) return 0;
2400
+ return this.codeScroller.scrollTop;
2272
2401
  }
2273
2402
 
2274
- setScrollLeft( value, keepScrollBar ) {
2275
-
2276
- if(!this.code) return;
2403
+ setScrollLeft( value ) {
2277
2404
 
2278
- const realClientWidth = (this.code.clientWidth - this.code.customScroll.x);
2279
- const maxWidth = Math.max( this.code.scrollWidth - realClientWidth, 0 );
2280
-
2281
- value = LX.UTILS.clamp( value, 0, maxWidth );
2282
-
2283
- this.code.style.marginLeft = (-value) + "px";
2284
-
2285
- if( !keepScrollBar )
2286
- {
2287
- const scrollWidth = this.hScrollbarThumb.parentElement.offsetWidth;
2288
- const scrollBarWidth = this.hScrollbarThumb.offsetWidth;
2289
- this.setScrollBarValue( ( scrollWidth - scrollBarWidth ) * ( value / maxWidth ), 'horizontal' );
2290
- }
2291
-
2292
- // Update cursor
2293
- var cursor = this.cursors.children[0];
2294
- cursor.style.left = "calc( " + (cursor._left - value) + "px + " + this.xPadding + ")";
2295
-
2296
- // Update selection
2297
- for( let s of this.selections.childNodes ) {
2298
- s.style.left = "calc( " + (s._left - value) + "px + " + this.xPadding + ")";
2299
- }
2300
-
2301
- this.code.customScroll.x = value;
2405
+ if(!this.codeScroller) return;
2406
+ this.codeScroller.scrollLeft = value;
2407
+ this.setScrollBarValue( 'horizontal', 0 );
2302
2408
  }
2303
2409
 
2304
- setScrollTop( value, keepScrollBar ) {
2305
-
2306
- if(!this.code) return;
2307
-
2308
- const realClientHeight = this.code.parentElement.offsetHeight - 36;
2309
- const maxHeight = Math.max( this.code.scrollHeight - realClientHeight, 0 );
2310
-
2311
- value = LX.UTILS.clamp( value, 0, maxHeight );
2410
+ setScrollTop( value ) {
2312
2411
 
2313
- this.gutter.scrollTop = value;
2314
-
2315
- this.code.style.marginTop = (-value) + "px";
2316
-
2317
- if( !keepScrollBar )
2318
- {
2319
- const scrollHeight = this.scrollbarThumb.parentElement.offsetHeight;
2320
- const scrollBarHeight = this.scrollbarThumb.offsetHeight;
2321
- // this.setScrollBarValue( ( scrollHeight - scrollBarHeight ) * ( value / maxHeight ) )
2322
- const firstLineInViewport = (this.getScrollTop() / this.lineHeight)|0;
2323
- this.setScrollBarValue( ( scrollHeight - scrollBarHeight ) * ( firstLineInViewport / this.code.lines.length ) )
2324
- }
2325
-
2326
- // Update cursor
2327
- var cursor = this.cursors.children[0];
2328
- cursor.style.top = (cursor._top - value) + "px";
2329
-
2330
- // Update selection
2331
- for( let s of this.selections.childNodes ) {
2332
- s.style.top = (s._top - value) + "px";
2333
- }
2334
-
2335
- this.code.customScroll.y = value;
2412
+ if(!this.codeScroller) return;
2413
+ this.codeScroller.scrollTop = value;
2414
+ this.setScrollBarValue( 'vertical' );
2336
2415
  }
2337
2416
 
2338
- resizeScrollBars() {
2339
-
2340
- const numViewportLines = Math.floor( (this.code.parentElement.offsetHeight - 36) / this.lineHeight );
2417
+ resizeScrollBars( numViewportLines ) {
2341
2418
 
2342
2419
  if( numViewportLines > this.code.lines.length )
2343
2420
  {
2344
- this.scrollbar.classList.add( 'scrollbar-unused' );
2345
- this.tabs.area.root.classList.remove( 'with-vscrollbar' );
2421
+ this.codeScroller.classList.remove( 'with-vscrollbar' );
2422
+ this.vScrollbar.root.classList.add( 'scrollbar-unused' );
2346
2423
  }
2347
2424
  else
2348
2425
  {
2349
- this.scrollbar.classList.remove( 'scrollbar-unused' );
2350
- this.tabs.area.root.classList.add( 'with-vscrollbar' );
2351
- this.scrollbarThumb.size = (numViewportLines / this.code.lines.length);
2352
- this.scrollbarThumb.style.height = (this.scrollbarThumb.size * 100.0) + "%";
2426
+ this.codeScroller.classList.add( 'with-vscrollbar' );
2427
+ this.vScrollbar.root.classList.remove( 'scrollbar-unused' );
2428
+ this.vScrollbar.thumb.size = (numViewportLines / this.code.lines.length);
2429
+ this.vScrollbar.thumb.style.height = (this.vScrollbar.thumb.size * 100.0) + "%";
2353
2430
  }
2354
2431
 
2355
- const numViewportChars = Math.floor( this.code.clientWidth / this.charWidth );
2432
+ const numViewportChars = Math.floor( this.codeScroller.clientWidth / this.charWidth );
2356
2433
  const line_lengths = this.code.lines.map( value => value.length );
2357
2434
  const maxLineLength = Math.max(...line_lengths);
2358
2435
 
2359
2436
  if( numViewportChars > maxLineLength )
2360
2437
  {
2361
- this.hScrollbar.classList.add( 'scrollbar-unused' );
2362
- this.tabs.area.root.classList.remove( 'with-hscrollbar' );
2438
+ this.codeScroller.classList.remove( 'with-hscrollbar' );
2439
+ this.hScrollbar.root.classList.add( 'scrollbar-unused' );
2363
2440
  }
2364
2441
  else
2365
2442
  {
2366
- this.hScrollbar.classList.remove( 'scrollbar-unused' );
2367
- this.tabs.area.root.classList.add( 'with-hscrollbar' );
2368
- this.hScrollbarThumb.size = (numViewportChars / maxLineLength);
2369
- this.hScrollbarThumb.style.width = (this.hScrollbarThumb.size * 100.0) + "%";
2443
+ this.codeScroller.classList.add( 'with-hscrollbar' );
2444
+ this.hScrollbar.root.classList.remove( 'scrollbar-unused' );
2445
+ this.hScrollbar.thumb.size = (numViewportChars / maxLineLength);
2446
+ this.hScrollbar.thumb.style.width = (this.hScrollbar.thumb.size * 100.0) + "%";
2370
2447
  }
2371
2448
  }
2372
2449
 
2373
- setScrollBarValue( value, type = 'vertical' ) {
2450
+ setScrollBarValue( type = 'vertical', value ) {
2374
2451
 
2375
2452
  if( type == 'vertical' )
2376
2453
  {
2377
- const scrollHeight = this.scrollbarThumb.parentElement.offsetHeight;
2378
- const scrollBarHeight = this.scrollbarThumb.offsetHeight;
2454
+ const scrollBarHeight = this.vScrollbar.thumb.parentElement.offsetHeight;
2455
+ const scrollThumbHeight = this.vScrollbar.thumb.offsetHeight;
2379
2456
 
2380
- value = LX.UTILS.clamp( value, 0, ( scrollHeight - scrollBarHeight ) );
2381
-
2382
- this.scrollbarThumb._top = value;
2383
- this.scrollbarThumb.style.top = this.scrollbarThumb._top + "px";
2457
+ const scrollHeight = this.codeScroller.scrollHeight - this.codeScroller.clientHeight;
2458
+ const currentScroll = this.codeScroller.scrollTop;
2459
+
2460
+ this.vScrollbar.thumb._top = ( currentScroll / scrollHeight ) * ( scrollBarHeight - scrollThumbHeight );
2461
+ this.vScrollbar.thumb.style.top = this.vScrollbar.thumb._top + "px";
2384
2462
  }
2385
2463
  else
2386
2464
  {
2387
- const scrollWidth = this.hScrollbarThumb.parentElement.offsetWidth;
2388
- const scrollBarWidth = this.hScrollbarThumb.offsetWidth;
2389
-
2390
- value = LX.UTILS.clamp( value, 0, ( scrollWidth - scrollBarWidth ) );
2391
-
2392
- this.hScrollbarThumb._left = value;
2393
- this.hScrollbarThumb.style.left = this.hScrollbarThumb._left + "px";
2465
+ this.codeScroller.scrollLeft += value;
2466
+
2467
+ const scrollBarWidth = this.hScrollbar.thumb.parentElement.offsetWidth;
2468
+ const scrollThumbWidth = this.hScrollbar.thumb.offsetWidth;
2469
+
2470
+ const scrollWidth = this.codeScroller.scrollWidth - this.codeScroller.clientWidth;
2471
+ const currentScroll = this.codeScroller.scrollLeft;
2472
+
2473
+ this.hScrollbar.thumb._left = ( currentScroll / scrollWidth ) * ( scrollBarWidth - scrollThumbWidth );
2474
+ this.hScrollbar.thumb.style.left = this.hScrollbar.thumb._left + "px";
2394
2475
  }
2395
2476
  }
2396
2477
 
2397
- applyHorizontalScrollFromScrollBar( value ) {
2478
+ updateHorizontalScrollFromScrollBar( value ) {
2479
+
2480
+ value = this.hScrollbar.thumb._left - value;
2481
+
2482
+ // Move scrollbar thumb
2483
+
2484
+ const scrollBarWidth = this.hScrollbar.thumb.parentElement.offsetWidth;
2485
+ const scrollThumbWidth = this.hScrollbar.thumb.offsetWidth;
2486
+
2487
+ this.hScrollbar.thumb._left = LX.UTILS.clamp( value, 0, ( scrollBarWidth - scrollThumbWidth ) );
2488
+ this.hScrollbar.thumb.style.left = this.hScrollbar.thumb._left + "px";
2398
2489
 
2399
- this.setScrollBarValue( value, 'horizontal');
2400
- this.setScrollLeft( value / this.hScrollbarThumb.size, true );
2490
+ // Scroll code
2491
+
2492
+ const scrollWidth = this.codeScroller.scrollWidth - this.codeScroller.clientWidth;
2493
+ const currentScroll = (this.hScrollbar.thumb._left * scrollWidth) / ( scrollBarWidth - scrollThumbWidth );
2494
+ this.codeScroller.scrollLeft = currentScroll;
2495
+
2496
+
2497
+ this._discardScroll = true;
2401
2498
  }
2402
2499
 
2403
- applyVerticalScrollFromScrollBar( value ) {
2500
+ updateVerticalScrollFromScrollBar( value ) {
2501
+
2502
+ value = this.vScrollbar.thumb._top - value;
2503
+
2504
+ // Move scrollbar thumb
2505
+
2506
+ const scrollBarHeight = this.vScrollbar.thumb.parentElement.offsetHeight;
2507
+ const scrollThumbHeight = this.vScrollbar.thumb.offsetHeight;
2508
+
2509
+ this.vScrollbar.thumb._top = LX.UTILS.clamp( value, 0, ( scrollBarHeight - scrollThumbHeight ) );
2510
+ this.vScrollbar.thumb.style.top = this.vScrollbar.thumb._top + "px";
2511
+
2512
+ // Scroll code
2404
2513
 
2405
- this.setScrollBarValue( value );
2406
- this.setScrollTop( value / this.scrollbarThumb.size, true );
2514
+ const scrollHeight = this.codeScroller.scrollHeight - this.codeScroller.clientHeight;
2515
+ const currentScroll = (this.vScrollbar.thumb._top * scrollHeight) / ( scrollBarHeight - scrollThumbHeight );
2516
+ this.codeScroller.scrollTop = currentScroll;
2407
2517
  }
2408
2518
 
2409
2519
  getCharAtPos( cursor, offset = 0 ) {
2410
2520
 
2411
- cursor = cursor ?? this.cursors.children[0];
2412
- return this.code.lines[cursor.line][cursor.position + offset];
2521
+ cursor = cursor ?? this.cursors.children[ 0 ];
2522
+ return this.code.lines[ cursor.line ][cursor.position + offset];
2413
2523
  }
2414
2524
 
2415
2525
  getWordAtPos( cursor, offset = 0 ) {
2416
2526
 
2417
- cursor = cursor ?? this.cursors.children[0];
2527
+ cursor = cursor ?? this.cursors.children[ 0 ];
2418
2528
  const col = cursor.line;
2419
2529
  const words = this.code.lines[col];
2420
2530
 
@@ -2449,7 +2559,7 @@ class CodeEditor {
2449
2559
  var rect = test.getBoundingClientRect();
2450
2560
  deleteElement( test );
2451
2561
  const bb = [Math.floor(rect.width), Math.floor(rect.height)];
2452
- return get_bb ? bb : bb[0];
2562
+ return get_bb ? bb : bb[ 0 ];
2453
2563
  }
2454
2564
 
2455
2565
  measureString( str ) {
@@ -2462,10 +2572,10 @@ class CodeEditor {
2462
2572
  var script = document.createElement('script');
2463
2573
  script.type = 'module';
2464
2574
  script.innerHTML = code;
2465
- // script.src = url[i] + ( version ? "?version=" + version : "" );
2575
+ // script.src = url[ i ] + ( version ? "?version=" + version : "" );
2466
2576
  script.async = false;
2467
2577
  // script.onload = function(e) { };
2468
- document.getElementsByTagName('head')[0].appendChild(script);
2578
+ document.getElementsByTagName('head')[ 0 ].appendChild(script);
2469
2579
  }
2470
2580
 
2471
2581
  toJSONFormat( text ) {
@@ -2473,19 +2583,19 @@ class CodeEditor {
2473
2583
  let params = text.split(":");
2474
2584
 
2475
2585
  for(let i = 0; i < params.length; i++) {
2476
- let key = params[i].split(',');
2586
+ let key = params[ i ].split(',');
2477
2587
  if(key.length > 1) {
2478
2588
  if(key[key.length-1].includes("]"))
2479
2589
  continue;
2480
2590
  key = key[key.length-1];
2481
2591
  }
2482
- else if(key[0].includes("}"))
2592
+ else if(key[ 0 ].includes("}"))
2483
2593
  continue;
2484
2594
  else
2485
- key = key[0];
2595
+ key = key[ 0 ];
2486
2596
  key = key.replaceAll(/[{}\n\r]/g,"").replaceAll(" ","")
2487
- if(key[0] != '"' && key[key.length - 1] != '"') {
2488
- params[i] = params[i].replace(key, '"' + key + '"');
2597
+ if(key[ 0 ] != '"' && key[key.length - 1] != '"') {
2598
+ params[ i ] = params[ i ].replace(key, '"' + key + '"');
2489
2599
  }
2490
2600
  }
2491
2601
 
@@ -2622,7 +2732,7 @@ class CodeEditor {
2622
2732
 
2623
2733
  for( let i = 0; i < this.autocomplete.childElementCount; ++i )
2624
2734
  {
2625
- const child = this.autocomplete.childNodes[i];
2735
+ const child = this.autocomplete.childNodes[ i ];
2626
2736
  if( child.classList.contains('selected') )
2627
2737
  {
2628
2738
  var word = "";