lexgui 0.1.17 → 0.1.19
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.
- package/build/components/codeeditor.js +507 -210
- package/build/lexgui.css +76 -18
- package/build/lexgui.js +110 -64
- package/build/lexgui.module.js +100 -55
- package/demo.js +1 -1
- package/examples/code_editor.html +32 -38
- package/package.json +1 -1
|
@@ -170,11 +170,74 @@ class CodeEditor {
|
|
|
170
170
|
window.editor = this;
|
|
171
171
|
|
|
172
172
|
CodeEditor.__instances.push( this );
|
|
173
|
+
|
|
174
|
+
// File explorer
|
|
175
|
+
if( options.file_explorer ?? false )
|
|
176
|
+
{
|
|
177
|
+
var [explorerArea, codeArea] = area.split({ sizes:["15%","85%"] });
|
|
178
|
+
explorerArea.setLimitBox( 180, 20, 512 );
|
|
179
|
+
this.explorerArea = explorerArea;
|
|
180
|
+
|
|
181
|
+
let panel = new LX.Panel();
|
|
182
|
+
|
|
183
|
+
panel.addTitle( "EXPLORER" );
|
|
184
|
+
|
|
185
|
+
let sceneData = {
|
|
186
|
+
'id': 'WORKSPACE',
|
|
187
|
+
'skipVisibility': true,
|
|
188
|
+
'children': []
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
this.explorer = panel.addTree( null, sceneData, {
|
|
192
|
+
filter: false,
|
|
193
|
+
rename: false,
|
|
194
|
+
skip_default_icon: true,
|
|
195
|
+
onevent: (event) => {
|
|
196
|
+
switch(event.type) {
|
|
197
|
+
// case LX.TreeEvent.NODE_SELECTED:
|
|
198
|
+
// if( !this.tabs.tabDOMs[ event.node.id ] ) break;
|
|
199
|
+
case LX.TreeEvent.NODE_DBLCLICKED:
|
|
200
|
+
this.loadTab( event.node.id );
|
|
201
|
+
break;
|
|
202
|
+
case LX.TreeEvent.NODE_DELETED:
|
|
203
|
+
this.tabs.delete( event.node.id );
|
|
204
|
+
delete this.loadedTabs[ event.node.id ];
|
|
205
|
+
break;
|
|
206
|
+
// case LX.TreeEvent.NODE_CONTEXTMENU:
|
|
207
|
+
// LX.addContextMenu( event.multiple ? "Selected Nodes" : event.node.id, event.value, m => {
|
|
208
|
+
//
|
|
209
|
+
// });
|
|
210
|
+
// break;
|
|
211
|
+
// case LX.TreeEvent.NODE_DRAGGED:
|
|
212
|
+
// console.log(event.node.id + " is now child of " + event.value.id);
|
|
213
|
+
// break;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
this.addExplorerItem = function( item )
|
|
219
|
+
{
|
|
220
|
+
if( !this.explorer.data.children.find( (value, index) => value.id === item.id ) )
|
|
221
|
+
this.explorer.data.children.push( item );
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
explorerArea.attach( panel );
|
|
225
|
+
|
|
226
|
+
// Update area
|
|
227
|
+
area = codeArea;
|
|
228
|
+
}
|
|
173
229
|
|
|
174
230
|
this.base_area = area;
|
|
175
231
|
this.area = new LX.Area( { className: "lexcodeeditor", height: "auto", no_append: true } );
|
|
176
232
|
|
|
177
|
-
this.tabs = this.area.addTabs( { onclose: (name) =>
|
|
233
|
+
this.tabs = this.area.addTabs( { onclose: (name) => {
|
|
234
|
+
delete this.openedTabs[ name ];
|
|
235
|
+
if( Object.keys( this.openedTabs ).length < 2 )
|
|
236
|
+
{
|
|
237
|
+
clearInterval( this.blinker );
|
|
238
|
+
this.cursors.classList.remove('show');
|
|
239
|
+
}
|
|
240
|
+
} } );
|
|
178
241
|
this.tabs.root.addEventListener( 'dblclick', (e) => {
|
|
179
242
|
if( options.allow_add_scripts ?? true ) {
|
|
180
243
|
e.preventDefault();
|
|
@@ -245,12 +308,12 @@ class CodeEditor {
|
|
|
245
308
|
// Scroll stuff
|
|
246
309
|
{
|
|
247
310
|
this.codeScroller = this.tabs.area.root;
|
|
248
|
-
this.
|
|
311
|
+
this.firstLineInViewport = 0;
|
|
249
312
|
this.lineScrollMargin = new LX.vec2( 20, 20 ); // [ mUp, mDown ]
|
|
250
313
|
window.scroller = this.codeScroller;
|
|
251
314
|
|
|
252
315
|
let lastScrollTopValue = -1;
|
|
253
|
-
this.codeScroller.addEventListener( 'scroll',
|
|
316
|
+
this.codeScroller.addEventListener( 'scroll', e => {
|
|
254
317
|
|
|
255
318
|
if( this._discardScroll )
|
|
256
319
|
{
|
|
@@ -265,36 +328,29 @@ class CodeEditor {
|
|
|
265
328
|
// Scroll down...
|
|
266
329
|
if( scrollTop > lastScrollTopValue )
|
|
267
330
|
{
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
if( scrollTop > scrollDownBoundary )
|
|
331
|
+
if( this.visibleLinesViewport.y < (this.code.lines.length - 1) )
|
|
271
332
|
{
|
|
272
|
-
|
|
273
|
-
|
|
333
|
+
const totalLinesInViewport = ((this.codeScroller.offsetHeight - 36) / this.lineHeight)|0;
|
|
334
|
+
const scrollDownBoundary =
|
|
335
|
+
( Math.max( this.visibleLinesViewport.y - totalLinesInViewport, 0 ) - 1 ) * this.lineHeight;
|
|
336
|
+
|
|
337
|
+
if( scrollTop >= scrollDownBoundary )
|
|
338
|
+
this.processLines( CodeEditor.UPDATE_VISIBLE_LINES );
|
|
274
339
|
}
|
|
275
340
|
}
|
|
276
341
|
// Scroll up...
|
|
277
342
|
else
|
|
278
343
|
{
|
|
279
|
-
const scrollUpBoundary = (
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
if( scrollTop <= scrollUpBoundary )
|
|
283
|
-
{
|
|
284
|
-
this.viewportRangeStart -= this.lineScrollMargin.x;
|
|
285
|
-
this.viewportRangeStart = Math.max( this.viewportRangeStart, 0 );
|
|
286
|
-
|
|
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
|
-
}
|
|
344
|
+
const scrollUpBoundary = parseInt( this.code.style.top );
|
|
345
|
+
if( scrollTop < scrollUpBoundary )
|
|
346
|
+
this.processLines( CodeEditor.UPDATE_VISIBLE_LINES );
|
|
291
347
|
}
|
|
292
348
|
|
|
293
349
|
lastScrollTopValue = scrollTop;
|
|
294
350
|
});
|
|
295
351
|
|
|
296
|
-
this.codeScroller.addEventListener( 'wheel',
|
|
297
|
-
const dX = (e.deltaY > 0.0 ? 10.0 : -10.0) * ( e.shiftKey ? 1.0 : 0.0 );
|
|
352
|
+
this.codeScroller.addEventListener( 'wheel', e => {
|
|
353
|
+
const dX = ( e.deltaY > 0.0 ? 10.0 : -10.0 ) * ( e.shiftKey ? 1.0 : 0.0 );
|
|
298
354
|
if( dX != 0.0 ) this.setScrollBarValue( 'horizontal', dX );
|
|
299
355
|
});
|
|
300
356
|
}
|
|
@@ -309,13 +365,13 @@ class CodeEditor {
|
|
|
309
365
|
// Add custom vertical scroll bar
|
|
310
366
|
{
|
|
311
367
|
this.vScrollbar = new ScrollBar( this, ScrollBar.SCROLLBAR_VERTICAL );
|
|
312
|
-
area.attach(this.vScrollbar.root);
|
|
368
|
+
area.attach( this.vScrollbar.root );
|
|
313
369
|
}
|
|
314
370
|
|
|
315
371
|
// Add custom horizontal scroll bar
|
|
316
372
|
{
|
|
317
373
|
this.hScrollbar = new ScrollBar( this, ScrollBar.SCROLLBAR_HORIZONTAL );
|
|
318
|
-
area.attach(this.hScrollbar.root);
|
|
374
|
+
area.attach( this.hScrollbar.root );
|
|
319
375
|
}
|
|
320
376
|
|
|
321
377
|
// Add autocomplete box
|
|
@@ -323,7 +379,7 @@ class CodeEditor {
|
|
|
323
379
|
var box = document.createElement( 'div' );
|
|
324
380
|
box.className = "autocomplete";
|
|
325
381
|
this.autocomplete = box;
|
|
326
|
-
this.tabs.area.attach(box);
|
|
382
|
+
this.tabs.area.attach( box );
|
|
327
383
|
|
|
328
384
|
this.isAutoCompleteActive = false;
|
|
329
385
|
}
|
|
@@ -351,7 +407,7 @@ class CodeEditor {
|
|
|
351
407
|
|
|
352
408
|
this.useAutoComplete = options.autocomplete ?? true;
|
|
353
409
|
this.highlight = options.highlight ?? 'Plain Text';
|
|
354
|
-
this.onsave = options.onsave ?? ((code) => {
|
|
410
|
+
this.onsave = options.onsave ?? ((code) => { console.log( code, "save" ) });
|
|
355
411
|
this.onrun = options.onrun ?? ((code) => { this.runScript(code) });
|
|
356
412
|
this.actions = {};
|
|
357
413
|
this.cursorBlinkRate = 550;
|
|
@@ -379,16 +435,17 @@ class CodeEditor {
|
|
|
379
435
|
// setInterval( this.scanWordSuggestions.bind( this ), 2000 );
|
|
380
436
|
|
|
381
437
|
this.languages = {
|
|
382
|
-
'Plain Text': { },
|
|
383
|
-
'JavaScript': { },
|
|
384
|
-
'C++': { },
|
|
385
|
-
'CSS': { },
|
|
386
|
-
'GLSL': { },
|
|
387
|
-
'WGSL': { },
|
|
388
|
-
'JSON': { },
|
|
389
|
-
'XML': { },
|
|
390
|
-
'Python': { },
|
|
391
|
-
'
|
|
438
|
+
'Plain Text': { ext: 'txt' },
|
|
439
|
+
'JavaScript': { ext: 'js' },
|
|
440
|
+
'C++': { ext: 'cpp' },
|
|
441
|
+
'CSS': { ext: 'css' },
|
|
442
|
+
'GLSL': { ext: 'glsl' },
|
|
443
|
+
'WGSL': { ext: 'wgsl' },
|
|
444
|
+
'JSON': { ext: 'json' },
|
|
445
|
+
'XML': { ext: 'xml' },
|
|
446
|
+
'Python': { ext: 'py', singleLineCommentToken: '#' },
|
|
447
|
+
'HTML': { ext: 'html' },
|
|
448
|
+
'Batch': { ext: 'bat', blockComments: false, singleLineCommentToken: '::' }
|
|
392
449
|
};
|
|
393
450
|
|
|
394
451
|
this.specialKeys = [
|
|
@@ -411,7 +468,8 @@ class CodeEditor {
|
|
|
411
468
|
'texture_storage_2d_array', 'texture_storage_3d'],
|
|
412
469
|
'Python': ['False', 'def', 'None', 'True', 'in', 'is', 'and', 'lambda', 'nonlocal', 'not', 'or'],
|
|
413
470
|
'Batch': ['set', 'SET', 'echo', 'ECHO', 'off', 'OFF', 'del', 'DEL', 'defined', 'DEFINED', 'setlocal', 'SETLOCAL', 'enabledelayedexpansion', 'ENABLEDELAYEDEXPANSION', 'driverquery',
|
|
414
|
-
'DRIVERQUERY', 'print', 'PRINT']
|
|
471
|
+
'DRIVERQUERY', 'print', 'PRINT'],
|
|
472
|
+
'HTML': ['html', 'meta', 'title', 'link', 'script', 'body', 'DOCTYPE', 'head'],
|
|
415
473
|
};
|
|
416
474
|
this.utils = { // These ones don't have hightlight, used as suggestions to autocomplete only...
|
|
417
475
|
'JavaScript': ['querySelector', 'body', 'addEventListener', 'removeEventListener', 'remove', 'sort', 'keys', 'filter', 'isNaN', 'parseFloat', 'parseInt', 'EPSILON', 'isFinite',
|
|
@@ -428,13 +486,14 @@ class CodeEditor {
|
|
|
428
486
|
'Python': ['int', 'type', 'float', 'map', 'list', 'ArithmeticError', 'AssertionError', 'AttributeError', 'Exception', 'EOFError', 'FloatingPointError', 'GeneratorExit',
|
|
429
487
|
'ImportError', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'NotImplementedError', 'OSError',
|
|
430
488
|
'OverflowError', 'ReferenceError', 'RuntimeError', 'StopIteration', 'SyntaxError', 'TabError', 'SystemError', 'SystemExit', 'TypeError', 'UnboundLocalError',
|
|
431
|
-
'UnicodeError', 'UnicodeEncodeError', 'UnicodeDecodeError', 'UnicodeTranslateError', 'ValueError', 'ZeroDivisionError'
|
|
489
|
+
'UnicodeError', 'UnicodeEncodeError', 'UnicodeDecodeError', 'UnicodeTranslateError', 'ValueError', 'ZeroDivisionError'],
|
|
432
490
|
'C++': ['uint8_t', 'uint16_t', 'uint32_t']
|
|
433
491
|
};
|
|
434
492
|
this.builtin = {
|
|
435
493
|
'JavaScript': ['document', 'console', 'window', 'navigator', 'performance'],
|
|
436
494
|
'CSS': ['*', '!important'],
|
|
437
|
-
'C++': ['vector', 'list', 'map']
|
|
495
|
+
'C++': ['vector', 'list', 'map'],
|
|
496
|
+
'HTML': ['type', 'xmlns', 'PUBLIC', 'http-equiv', 'src', 'lang', 'href', 'rel', 'content', 'xml'], // attributes
|
|
438
497
|
};
|
|
439
498
|
this.statementsAndDeclarations = {
|
|
440
499
|
'JavaScript': ['for', 'if', 'else', 'case', 'switch', 'return', 'while', 'continue', 'break', 'do', 'import', 'from', 'throw', 'async', 'try', 'catch', 'await'],
|
|
@@ -454,6 +513,7 @@ class CodeEditor {
|
|
|
454
513
|
'CSS': ['{', '}', '(', ')', '*'],
|
|
455
514
|
'Python': ['<', '>', '[', ']', '(', ')', '='],
|
|
456
515
|
'Batch': ['[', ']', '(', ')', '%'],
|
|
516
|
+
'HTML': ['<', '>', '/']
|
|
457
517
|
};
|
|
458
518
|
|
|
459
519
|
// Convert reserved word arrays to maps so we can search tokens faster
|
|
@@ -478,7 +538,7 @@ class CodeEditor {
|
|
|
478
538
|
if( this.selection ) {
|
|
479
539
|
this.deleteSelection( cursor );
|
|
480
540
|
// Remove entire line when selecting with triple click
|
|
481
|
-
if(
|
|
541
|
+
if( this._tripleClickSelection )
|
|
482
542
|
{
|
|
483
543
|
this.actions['Backspace'].callback( ln, cursor, e );
|
|
484
544
|
this.lineDown( cursor, true );
|
|
@@ -559,9 +619,15 @@ class CodeEditor {
|
|
|
559
619
|
if( this.selection )
|
|
560
620
|
lastX += this.selection.chars;
|
|
561
621
|
|
|
562
|
-
this.
|
|
622
|
+
if( !this.selection )
|
|
623
|
+
this.startSelection( cursor );
|
|
563
624
|
var string = this.code.lines[ ln ].substring( idx, lastX );
|
|
564
|
-
this.selection.
|
|
625
|
+
if( this.selection.sameLine() )
|
|
626
|
+
this.selection.selectInline( idx, cursor.line, this.measureString( string ) );
|
|
627
|
+
else
|
|
628
|
+
{
|
|
629
|
+
this.processSelection();
|
|
630
|
+
}
|
|
565
631
|
} else if( !e.keepSelection )
|
|
566
632
|
this.endSelection();
|
|
567
633
|
});
|
|
@@ -570,18 +636,25 @@ class CodeEditor {
|
|
|
570
636
|
|
|
571
637
|
if( e.shiftKey || e._shiftKey ) {
|
|
572
638
|
|
|
573
|
-
var string = this.code.lines[ ln ].substring(cursor.position);
|
|
639
|
+
var string = this.code.lines[ ln ].substring( cursor.position );
|
|
574
640
|
if( !this.selection )
|
|
575
641
|
this.startSelection( cursor );
|
|
576
|
-
this.selection.
|
|
642
|
+
if( this.selection.sameLine() )
|
|
643
|
+
this.selection.selectInline(cursor.position, cursor.line, this.measureString( string ));
|
|
644
|
+
else
|
|
645
|
+
{
|
|
646
|
+
this.resetCursorPos( CodeEditor.CURSOR_LEFT );
|
|
647
|
+
this.cursorToString( cursor, this.code.lines[ ln ] );
|
|
648
|
+
this.processSelection();
|
|
649
|
+
}
|
|
577
650
|
} else
|
|
578
651
|
this.endSelection();
|
|
579
652
|
|
|
580
653
|
this.resetCursorPos( CodeEditor.CURSOR_LEFT );
|
|
581
654
|
this.cursorToString( cursor, this.code.lines[ ln ] );
|
|
582
655
|
|
|
583
|
-
const last_char = (this.code.clientWidth / this.charWidth)|0;
|
|
584
|
-
this.setScrollLeft( cursor.position >= last_char ? (cursor.position - last_char) * this.charWidth : 0 );
|
|
656
|
+
const last_char = ( this.code.clientWidth / this.charWidth )|0;
|
|
657
|
+
this.setScrollLeft( cursor.position >= last_char ? ( cursor.position - last_char ) * this.charWidth : 0 );
|
|
585
658
|
});
|
|
586
659
|
|
|
587
660
|
this.action( 'Enter', true, ( ln, cursor, e ) => {
|
|
@@ -708,7 +781,8 @@ class CodeEditor {
|
|
|
708
781
|
var diff = Math.max(cursor.position - from, 1);
|
|
709
782
|
var substr = word.substr(0, diff);
|
|
710
783
|
// Selections...
|
|
711
|
-
if( e.shiftKey ) if( !this.selection ) this.startSelection( cursor );
|
|
784
|
+
if( e.shiftKey ) { if( !this.selection ) this.startSelection( cursor ); }
|
|
785
|
+
else this.endSelection();
|
|
712
786
|
this.cursorToString(cursor, substr, true);
|
|
713
787
|
if( e.shiftKey ) this.processSelection();
|
|
714
788
|
}
|
|
@@ -772,7 +846,8 @@ class CodeEditor {
|
|
|
772
846
|
var diff = cursor.position - from;
|
|
773
847
|
var substr = word.substr( diff );
|
|
774
848
|
// Selections...
|
|
775
|
-
if( e.shiftKey ) if( !this.selection ) this.startSelection( cursor );
|
|
849
|
+
if( e.shiftKey ) { if( !this.selection ) this.startSelection( cursor ); }
|
|
850
|
+
else this.endSelection();
|
|
776
851
|
this.cursorToString( cursor, substr);
|
|
777
852
|
if( e.shiftKey ) this.processSelection();
|
|
778
853
|
} else {
|
|
@@ -833,6 +908,7 @@ class CodeEditor {
|
|
|
833
908
|
|
|
834
909
|
// Default code tab
|
|
835
910
|
|
|
911
|
+
this.loadedTabs = { };
|
|
836
912
|
this.openedTabs = { };
|
|
837
913
|
|
|
838
914
|
if( options.allow_add_scripts ?? true )
|
|
@@ -850,6 +926,26 @@ class CodeEditor {
|
|
|
850
926
|
return CodeEditor.__instances;
|
|
851
927
|
}
|
|
852
928
|
|
|
929
|
+
// This received key inputs from the entire document...
|
|
930
|
+
onKeyPressed( e ) {
|
|
931
|
+
|
|
932
|
+
// Toggle visibility of the file explorer
|
|
933
|
+
if( e.key == 'b' && e.ctrlKey && this.explorer )
|
|
934
|
+
{
|
|
935
|
+
this.explorerArea.root.classList.toggle( "hidden" );
|
|
936
|
+
if( this._lastBaseareaWidth )
|
|
937
|
+
{
|
|
938
|
+
this.base_area.root.style.width = this._lastBaseareaWidth;
|
|
939
|
+
delete this._lastBaseareaWidth;
|
|
940
|
+
|
|
941
|
+
} else
|
|
942
|
+
{
|
|
943
|
+
this._lastBaseareaWidth = this.base_area.root.style.width;
|
|
944
|
+
this.base_area.root.style.width = "100%";
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
853
949
|
getText( min ) {
|
|
854
950
|
return this.code.lines.join(min ? ' ' : '\n');
|
|
855
951
|
}
|
|
@@ -939,11 +1035,22 @@ class CodeEditor {
|
|
|
939
1035
|
loadFile( file ) {
|
|
940
1036
|
|
|
941
1037
|
const inner_add_tab = ( text, name, title ) => {
|
|
942
|
-
|
|
943
|
-
|
|
1038
|
+
|
|
1039
|
+
// Set current text and language
|
|
1040
|
+
const lines = text.replaceAll( '\r', '' ).split( '\n' );
|
|
1041
|
+
|
|
1042
|
+
// Add item in the explorer if used
|
|
1043
|
+
if( this.explorer )
|
|
1044
|
+
{
|
|
1045
|
+
this._storedLines = this._storedLines ?? {};
|
|
1046
|
+
this._storedLines[ name ] = lines;
|
|
1047
|
+
this.addExplorerItem( { 'id': name, 'skipVisibility': true, 'icon': this._getFileIcon( name ) } );
|
|
1048
|
+
this.explorer.frefresh( name );
|
|
1049
|
+
}
|
|
1050
|
+
else
|
|
944
1051
|
{
|
|
945
|
-
|
|
946
|
-
this.code.lines =
|
|
1052
|
+
this.addTab(name, true, title);
|
|
1053
|
+
this.code.lines = lines;
|
|
947
1054
|
this._changeLanguageFromExtension( LX.getExtension( name ) );
|
|
948
1055
|
}
|
|
949
1056
|
};
|
|
@@ -970,12 +1077,28 @@ class CodeEditor {
|
|
|
970
1077
|
|
|
971
1078
|
_addUndoStep( cursor ) {
|
|
972
1079
|
|
|
1080
|
+
const d = new Date();
|
|
1081
|
+
const current = d.getTime();
|
|
1082
|
+
|
|
1083
|
+
if( !this._lastTime ) {
|
|
1084
|
+
this._lastTime = current;
|
|
1085
|
+
} else {
|
|
1086
|
+
if( ( current - this._lastTime ) > 3000 ){
|
|
1087
|
+
this._lastTime = null;
|
|
1088
|
+
} else {
|
|
1089
|
+
// If time not enough, reset timer
|
|
1090
|
+
this._lastTime = current;
|
|
1091
|
+
return;
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
|
|
973
1095
|
var cursor = cursor ?? this.cursors.children[ 0 ];
|
|
974
1096
|
|
|
975
1097
|
this.code.undoSteps.push( {
|
|
976
1098
|
lines: LX.deepCopy( this.code.lines ),
|
|
977
1099
|
cursor: this.saveCursor( cursor ),
|
|
978
|
-
line: cursor.line
|
|
1100
|
+
line: cursor.line,
|
|
1101
|
+
position: cursor.position
|
|
979
1102
|
} );
|
|
980
1103
|
}
|
|
981
1104
|
|
|
@@ -985,6 +1108,35 @@ class CodeEditor {
|
|
|
985
1108
|
this.highlight = lang;
|
|
986
1109
|
this._refreshCodeInfo();
|
|
987
1110
|
this.processLines();
|
|
1111
|
+
|
|
1112
|
+
const ext = this.languages[ lang ].ext;
|
|
1113
|
+
const icon = this._getFileIcon( null, ext );
|
|
1114
|
+
|
|
1115
|
+
// Update tab icon
|
|
1116
|
+
{
|
|
1117
|
+
const tab = this.tabs.tabDOMs[ this.code.tabName ];
|
|
1118
|
+
tab.firstChild.remove();
|
|
1119
|
+
console.assert( tab != undefined );
|
|
1120
|
+
var iconEl;
|
|
1121
|
+
if( icon.includes( 'fa-' ) )
|
|
1122
|
+
{
|
|
1123
|
+
iconEl = document.createElement( 'i' );
|
|
1124
|
+
iconEl.className = icon;
|
|
1125
|
+
} else {
|
|
1126
|
+
iconEl = document.createElement( 'img' );
|
|
1127
|
+
iconEl.src = "https://raw.githubusercontent.com/jxarco/lexgui.js/master/" + icon;
|
|
1128
|
+
}
|
|
1129
|
+
tab.prepend( iconEl );
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// Update explorer icon
|
|
1133
|
+
if( this.explorer )
|
|
1134
|
+
{
|
|
1135
|
+
const item = this.explorer.data.children.filter( (v) => v.id === this.code.tabName )[ 0 ];
|
|
1136
|
+
console.assert( item != undefined );
|
|
1137
|
+
item.icon = icon;
|
|
1138
|
+
this.explorer.frefresh( this.code.tabName );
|
|
1139
|
+
}
|
|
988
1140
|
}
|
|
989
1141
|
|
|
990
1142
|
_changeLanguageFromExtension( ext ) {
|
|
@@ -1004,6 +1156,7 @@ class CodeEditor {
|
|
|
1004
1156
|
case 'wgsl': return this._changeLanguage( 'WGSL' );
|
|
1005
1157
|
case 'py': return this._changeLanguage( 'Python' );
|
|
1006
1158
|
case 'bat': return this._changeLanguage( 'Batch' );
|
|
1159
|
+
case 'html': return this._changeLanguage( 'HTML' );
|
|
1007
1160
|
case 'txt':
|
|
1008
1161
|
default:
|
|
1009
1162
|
this._changeLanguage( 'Plain Text' );
|
|
@@ -1054,23 +1207,40 @@ class CodeEditor {
|
|
|
1054
1207
|
}
|
|
1055
1208
|
}
|
|
1056
1209
|
|
|
1210
|
+
_getFileIcon( name, extension ) {
|
|
1211
|
+
|
|
1212
|
+
const isNewTabButton = name ? ( name === '+' ) : false;
|
|
1213
|
+
const ext = extension ?? LX.getExtension( name );
|
|
1214
|
+
return ext == 'html' ? "fa-solid fa-code orange" :
|
|
1215
|
+
ext == "css" ? "fa-solid fa-hashtag dodgerblue" :
|
|
1216
|
+
ext == "xml" ? "fa-solid fa-rss orange" :
|
|
1217
|
+
ext == "bat" ? "fa-brands fa-windows lightblue" :
|
|
1218
|
+
[ "js", "py", "json", "cpp" ].indexOf( ext ) > -1 ? "images/" + ext + ".png" :
|
|
1219
|
+
!isNewTabButton ? "fa-solid fa-align-left gray" : undefined;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1057
1222
|
_onNewTab( e ) {
|
|
1058
1223
|
|
|
1059
1224
|
this.processFocus(false);
|
|
1060
1225
|
|
|
1061
1226
|
LX.addContextMenu( null, e, m => {
|
|
1062
1227
|
m.add( "Create", this.addTab.bind( this, "unnamed.js", true ) );
|
|
1063
|
-
m.add( "Load", this.
|
|
1228
|
+
m.add( "Load", this.loadTabFromFile.bind( this, "unnamed.js", true ) );
|
|
1064
1229
|
});
|
|
1065
1230
|
}
|
|
1066
1231
|
|
|
1067
|
-
addTab(name, selected, title) {
|
|
1232
|
+
addTab( name, selected, title ) {
|
|
1068
1233
|
|
|
1069
|
-
|
|
1070
|
-
{
|
|
1071
|
-
|
|
1072
|
-
return
|
|
1073
|
-
}
|
|
1234
|
+
// If already loaded, set new name...
|
|
1235
|
+
const repeats = Object.keys( editor.loadedTabs ).slice( 1 ).reduce( ( v, key ) => {
|
|
1236
|
+
const noRepeatName = key.replace( /[_\d+]/g, '');
|
|
1237
|
+
return v + ( noRepeatName == name );
|
|
1238
|
+
}, 0 );
|
|
1239
|
+
|
|
1240
|
+
if( repeats > 0 )
|
|
1241
|
+
name = name.split( '.' ).join( '_' + repeats + '.' );
|
|
1242
|
+
|
|
1243
|
+
const isNewTabButton = ( name === '+' );
|
|
1074
1244
|
|
|
1075
1245
|
// Create code content
|
|
1076
1246
|
let code = document.createElement( 'div' );
|
|
@@ -1082,6 +1252,8 @@ class CodeEditor {
|
|
|
1082
1252
|
code.tabName = name;
|
|
1083
1253
|
code.title = title ?? name;
|
|
1084
1254
|
code.tokens = {};
|
|
1255
|
+
code.style.left = "0px";
|
|
1256
|
+
code.style.top = "0px";
|
|
1085
1257
|
|
|
1086
1258
|
code.addEventListener( 'dragenter', function(e) {
|
|
1087
1259
|
e.preventDefault();
|
|
@@ -1098,24 +1270,39 @@ class CodeEditor {
|
|
|
1098
1270
|
this.loadFile( e.dataTransfer.files[ i ] );
|
|
1099
1271
|
});
|
|
1100
1272
|
|
|
1273
|
+
this.loadedTabs[ name ] = code;
|
|
1101
1274
|
this.openedTabs[ name ] = code;
|
|
1275
|
+
|
|
1276
|
+
const tabIcon = this._getFileIcon( name );
|
|
1102
1277
|
|
|
1103
|
-
|
|
1278
|
+
if( this.explorer && !isNewTabButton )
|
|
1279
|
+
{
|
|
1280
|
+
this.addExplorerItem( { 'id': name, 'skipVisibility': true, 'icon': tabIcon } );
|
|
1281
|
+
this.explorer.frefresh( name );
|
|
1282
|
+
}
|
|
1104
1283
|
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1284
|
+
this.tabs.add(name, code, {
|
|
1285
|
+
selected: selected,
|
|
1286
|
+
fixed: isNewTabButton,
|
|
1287
|
+
title: code.title,
|
|
1288
|
+
icon: tabIcon,
|
|
1289
|
+
onSelect: (e, tabname) => {
|
|
1110
1290
|
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1291
|
+
if( isNewTabButton )
|
|
1292
|
+
{
|
|
1293
|
+
this._onNewTab( e );
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
var cursor = cursor ?? this.cursors.children[ 0 ];
|
|
1298
|
+
this.saveCursor( cursor, this.code.cursorState );
|
|
1299
|
+
this.code = this.loadedTabs[ tabname ];
|
|
1300
|
+
this.restoreCursor( cursor, this.code.cursorState );
|
|
1301
|
+
this.endSelection();
|
|
1302
|
+
this._changeLanguageFromExtension( LX.getExtension( tabname ) );
|
|
1303
|
+
this._refreshCodeInfo( cursor.line, cursor.position );
|
|
1304
|
+
}
|
|
1305
|
+
});
|
|
1119
1306
|
|
|
1120
1307
|
// Move into the sizer..
|
|
1121
1308
|
this.codeSizer.appendChild( code );
|
|
@@ -1129,9 +1316,77 @@ class CodeEditor {
|
|
|
1129
1316
|
this.processLines();
|
|
1130
1317
|
doAsync( () => this._refreshCodeInfo( 0, 0 ), 50 );
|
|
1131
1318
|
}
|
|
1319
|
+
|
|
1320
|
+
// Bc it could be overrided..
|
|
1321
|
+
return name;
|
|
1132
1322
|
}
|
|
1133
1323
|
|
|
1134
|
-
loadTab() {
|
|
1324
|
+
loadTab( name ) {
|
|
1325
|
+
|
|
1326
|
+
// Already open...
|
|
1327
|
+
if( this.openedTabs[ name ] )
|
|
1328
|
+
{
|
|
1329
|
+
this.tabs.select( name );
|
|
1330
|
+
return;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
let code = this.loadedTabs[ name ]
|
|
1334
|
+
|
|
1335
|
+
if( !code )
|
|
1336
|
+
{
|
|
1337
|
+
this.addTab( name, true );
|
|
1338
|
+
// Unload lines from file...
|
|
1339
|
+
if( this._storedLines[ name ] )
|
|
1340
|
+
{
|
|
1341
|
+
this.code.lines = this._storedLines[ name ];
|
|
1342
|
+
delete this._storedLines[ name ];
|
|
1343
|
+
}
|
|
1344
|
+
this._changeLanguageFromExtension( LX.getExtension( name ) );
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
this.openedTabs[ name ] = code;
|
|
1349
|
+
|
|
1350
|
+
const isNewTabButton = ( name === '+' );
|
|
1351
|
+
const tabIcon = this._getFileIcon( name );
|
|
1352
|
+
|
|
1353
|
+
this.tabs.add(name, code, {
|
|
1354
|
+
selected: true,
|
|
1355
|
+
fixed: isNewTabButton,
|
|
1356
|
+
title: code.title,
|
|
1357
|
+
icon: tabIcon,
|
|
1358
|
+
onSelect: (e, tabname) => {
|
|
1359
|
+
|
|
1360
|
+
if( isNewTabButton )
|
|
1361
|
+
{
|
|
1362
|
+
this._onNewTab( e );
|
|
1363
|
+
return;
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
var cursor = cursor ?? this.cursors.children[ 0 ];
|
|
1367
|
+
this.saveCursor( cursor, this.code.cursorState );
|
|
1368
|
+
this.code = this.loadedTabs[ tabname ];
|
|
1369
|
+
this.restoreCursor( cursor, this.code.cursorState );
|
|
1370
|
+
this.endSelection();
|
|
1371
|
+
this._changeLanguageFromExtension( LX.getExtension( tabname ) );
|
|
1372
|
+
this._refreshCodeInfo( cursor.line, cursor.position );
|
|
1373
|
+
}
|
|
1374
|
+
});
|
|
1375
|
+
|
|
1376
|
+
// Move into the sizer..
|
|
1377
|
+
this.codeSizer.appendChild( code );
|
|
1378
|
+
|
|
1379
|
+
this.endSelection();
|
|
1380
|
+
|
|
1381
|
+
// Select as current...
|
|
1382
|
+
this.code = code;
|
|
1383
|
+
this.resetCursorPos( CodeEditor.CURSOR_LEFT | CodeEditor.CURSOR_TOP );
|
|
1384
|
+
this.processLines();
|
|
1385
|
+
this._changeLanguageFromExtension( LX.getExtension( name ) );
|
|
1386
|
+
doAsync( () => this._refreshCodeInfo( 0, 0 ), 50 );
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
loadTabFromFile() {
|
|
1135
1390
|
const input = document.createElement( 'input' );
|
|
1136
1391
|
input.type = 'file';
|
|
1137
1392
|
document.body.appendChild( input );
|
|
@@ -1221,7 +1476,7 @@ class CodeEditor {
|
|
|
1221
1476
|
this.resetCursorPos( CodeEditor.CURSOR_LEFT );
|
|
1222
1477
|
this.cursorToPosition( cursor, from );
|
|
1223
1478
|
this.startSelection( cursor );
|
|
1224
|
-
this.selection.selectInline(from, cursor.line, this.measureString(word));
|
|
1479
|
+
this.selection.selectInline( from, cursor.line, this.measureString( word ) );
|
|
1225
1480
|
this.cursorToString( cursor, word ); // Go to the end of the word
|
|
1226
1481
|
break;
|
|
1227
1482
|
// Select entire line
|
|
@@ -1229,6 +1484,7 @@ class CodeEditor {
|
|
|
1229
1484
|
this.resetCursorPos( CodeEditor.CURSOR_LEFT );
|
|
1230
1485
|
e._shiftKey = true;
|
|
1231
1486
|
this.actions['End'].callback(cursor.line, cursor, e);
|
|
1487
|
+
this._tripleClickSelection = true;
|
|
1232
1488
|
break;
|
|
1233
1489
|
}
|
|
1234
1490
|
}
|
|
@@ -1248,8 +1504,8 @@ class CodeEditor {
|
|
|
1248
1504
|
m.add( "Paste", () => { this._pasteContent(); } );
|
|
1249
1505
|
m.add( "" );
|
|
1250
1506
|
m.add( "Format/JSON", () => {
|
|
1251
|
-
let json = this.toJSONFormat(this.getText());
|
|
1252
|
-
this.code.lines = json.split("\n");
|
|
1507
|
+
let json = this.toJSONFormat( this.getText() );
|
|
1508
|
+
this.code.lines = json.split( "\n" );
|
|
1253
1509
|
this.processLines();
|
|
1254
1510
|
} );
|
|
1255
1511
|
}
|
|
@@ -1285,7 +1541,7 @@ class CodeEditor {
|
|
|
1285
1541
|
|
|
1286
1542
|
var cursor = this.cursors.children[ 0 ];
|
|
1287
1543
|
|
|
1288
|
-
if(e) this.processClick( e, true );
|
|
1544
|
+
if( e ) this.processClick( e, true );
|
|
1289
1545
|
if( !this.selection )
|
|
1290
1546
|
this.startSelection( cursor );
|
|
1291
1547
|
|
|
@@ -1307,20 +1563,25 @@ class CodeEditor {
|
|
|
1307
1563
|
// Selection goes down...
|
|
1308
1564
|
if( deltaY >= 0 )
|
|
1309
1565
|
{
|
|
1310
|
-
while( deltaY < (this.selections.childElementCount - 1) )
|
|
1566
|
+
while( deltaY < ( this.selections.childElementCount - 1 ) )
|
|
1311
1567
|
deleteElement( this.selections.lastChild );
|
|
1312
1568
|
|
|
1313
1569
|
for(let i = fromY; i <= toY; i++){
|
|
1314
1570
|
|
|
1315
1571
|
const sId = i - fromY;
|
|
1572
|
+
const isVisible = i >= this.visibleLinesViewport.x && i <= this.visibleLinesViewport.y;
|
|
1573
|
+
let domEl = null;
|
|
1316
1574
|
|
|
1317
|
-
|
|
1318
|
-
let domEl = this.selections.childNodes[sId];
|
|
1319
|
-
if(!domEl)
|
|
1575
|
+
if( isVisible )
|
|
1320
1576
|
{
|
|
1321
|
-
|
|
1322
|
-
domEl
|
|
1323
|
-
|
|
1577
|
+
// Make sure that the line selection is generated...
|
|
1578
|
+
domEl = this.selections.childNodes[ sId ];
|
|
1579
|
+
if(!domEl)
|
|
1580
|
+
{
|
|
1581
|
+
domEl = document.createElement( 'div' );
|
|
1582
|
+
domEl.className = "lexcodeselection";
|
|
1583
|
+
this.selections.appendChild( domEl );
|
|
1584
|
+
}
|
|
1324
1585
|
}
|
|
1325
1586
|
|
|
1326
1587
|
// Compute new width and selection margins
|
|
@@ -1329,67 +1590,80 @@ class CodeEditor {
|
|
|
1329
1590
|
if(sId == 0) // First line 2 cases (single line, multiline)
|
|
1330
1591
|
{
|
|
1331
1592
|
const reverse = fromX > toX;
|
|
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);
|
|
1593
|
+
if(deltaY == 0) string = !reverse ? this.code.lines[ i ].substring( fromX, toX ) : this.code.lines[ i ].substring(toX, fromX);
|
|
1594
|
+
else string = this.code.lines[ i ].substr( fromX );
|
|
1334
1595
|
const pixels = (reverse && deltaY == 0 ? toX : fromX) * this.charWidth;
|
|
1335
|
-
domEl.style.left = "calc(" + pixels + "px + " + this.xPadding + ")";
|
|
1596
|
+
if( isVisible ) domEl.style.left = "calc(" + pixels + "px + " + this.xPadding + ")";
|
|
1336
1597
|
}
|
|
1337
1598
|
else
|
|
1338
1599
|
{
|
|
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;
|
|
1600
|
+
string = (i == toY) ? this.code.lines[ i ].substring( 0, toX ) : this.code.lines[ i ]; // Last line, any multiple line...
|
|
1601
|
+
if( isVisible ) domEl.style.left = this.xPadding;
|
|
1341
1602
|
}
|
|
1342
1603
|
|
|
1343
|
-
const stringWidth = this.measureString(string);
|
|
1344
|
-
domEl.style.width = (stringWidth || 8) + "px";
|
|
1345
|
-
domEl._top = i * this.lineHeight;
|
|
1346
|
-
domEl.style.top = domEl._top + "px";
|
|
1604
|
+
const stringWidth = this.measureString( string );
|
|
1347
1605
|
this.selection.chars += stringWidth / this.charWidth;
|
|
1606
|
+
|
|
1607
|
+
if( isVisible )
|
|
1608
|
+
{
|
|
1609
|
+
domEl.style.width = (stringWidth || 8) + "px";
|
|
1610
|
+
domEl._top = i * this.lineHeight;
|
|
1611
|
+
domEl.style.top = domEl._top + "px";
|
|
1612
|
+
}
|
|
1348
1613
|
}
|
|
1349
1614
|
}
|
|
1350
1615
|
else // Selection goes up...
|
|
1351
1616
|
{
|
|
1352
|
-
while( Math.abs(deltaY) < (this.selections.childElementCount - 1) )
|
|
1617
|
+
while( Math.abs( deltaY ) < ( this.selections.childElementCount - 1 ) )
|
|
1353
1618
|
deleteElement( this.selections.firstChild );
|
|
1354
1619
|
|
|
1355
|
-
for(let i = toY; i <= fromY; i++){
|
|
1620
|
+
for( let i = toY; i <= fromY; i++ ){
|
|
1356
1621
|
|
|
1357
1622
|
const sId = i - toY;
|
|
1623
|
+
const isVisible = i >= this.visibleLinesViewport.x && i <= this.visibleLinesViewport.y;
|
|
1624
|
+
let domEl = null;
|
|
1358
1625
|
|
|
1359
|
-
|
|
1360
|
-
let domEl = this.selections.childNodes[sId];
|
|
1361
|
-
if(!domEl)
|
|
1626
|
+
if( isVisible )
|
|
1362
1627
|
{
|
|
1363
|
-
|
|
1364
|
-
domEl
|
|
1365
|
-
|
|
1628
|
+
// Make sure that the line selection is generated...
|
|
1629
|
+
domEl = this.selections.childNodes[ sId ];
|
|
1630
|
+
if(!domEl)
|
|
1631
|
+
{
|
|
1632
|
+
domEl = document.createElement( 'div' );
|
|
1633
|
+
domEl.className = "lexcodeselection";
|
|
1634
|
+
this.selections.appendChild( domEl );
|
|
1635
|
+
}
|
|
1366
1636
|
}
|
|
1367
1637
|
|
|
1368
1638
|
// Compute new width and selection margins
|
|
1369
1639
|
let string;
|
|
1370
1640
|
|
|
1371
|
-
if(sId == 0)
|
|
1641
|
+
if( sId == 0 )
|
|
1372
1642
|
{
|
|
1373
1643
|
string = this.code.lines[ i ].substr(toX);
|
|
1374
1644
|
const pixels = toX * this.charWidth;
|
|
1375
|
-
domEl.style.left = "calc(" + pixels + "px + " + this.xPadding + ")";
|
|
1645
|
+
if( isVisible ) domEl.style.left = "calc(" + pixels + "px + " + this.xPadding + ")";
|
|
1376
1646
|
}
|
|
1377
1647
|
else
|
|
1378
1648
|
{
|
|
1379
1649
|
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;
|
|
1650
|
+
if( isVisible ) domEl.style.left = this.xPadding;
|
|
1381
1651
|
}
|
|
1382
1652
|
|
|
1383
|
-
const stringWidth = this.measureString(string);
|
|
1384
|
-
domEl.style.width = (stringWidth || 8) + "px";
|
|
1385
|
-
domEl._top = i * this.lineHeight;
|
|
1386
|
-
domEl.style.top = domEl._top + "px";
|
|
1653
|
+
const stringWidth = this.measureString( string );
|
|
1387
1654
|
this.selection.chars += stringWidth / this.charWidth;
|
|
1655
|
+
|
|
1656
|
+
if( isVisible )
|
|
1657
|
+
{
|
|
1658
|
+
domEl.style.width = (stringWidth || 8) + "px";
|
|
1659
|
+
domEl._top = i * this.lineHeight;
|
|
1660
|
+
domEl.style.top = domEl._top + "px";
|
|
1661
|
+
}
|
|
1388
1662
|
}
|
|
1389
1663
|
}
|
|
1390
1664
|
}
|
|
1391
1665
|
|
|
1392
|
-
async processKey(e) {
|
|
1666
|
+
async processKey( e ) {
|
|
1393
1667
|
|
|
1394
1668
|
if( !this.code )
|
|
1395
1669
|
return;
|
|
@@ -1399,7 +1673,7 @@ class CodeEditor {
|
|
|
1399
1673
|
const skip_undo = e.detail.skip_undo ?? false;
|
|
1400
1674
|
|
|
1401
1675
|
// keys with length > 1 are probably special keys
|
|
1402
|
-
if( key.length > 1 && this.specialKeys.indexOf(key) == -1 )
|
|
1676
|
+
if( key.length > 1 && this.specialKeys.indexOf( key ) == -1 )
|
|
1403
1677
|
return;
|
|
1404
1678
|
|
|
1405
1679
|
let cursor = this.cursors.children[ 0 ];
|
|
@@ -1446,7 +1720,6 @@ class CodeEditor {
|
|
|
1446
1720
|
return;
|
|
1447
1721
|
const step = this.code.undoSteps.pop();
|
|
1448
1722
|
this.code.lines = step.lines;
|
|
1449
|
-
cursor.line = step.line;
|
|
1450
1723
|
this.restoreCursor( cursor, step.cursor );
|
|
1451
1724
|
this.processLines();
|
|
1452
1725
|
return;
|
|
@@ -1493,23 +1766,9 @@ class CodeEditor {
|
|
|
1493
1766
|
|
|
1494
1767
|
// Add undo steps
|
|
1495
1768
|
|
|
1496
|
-
|
|
1497
|
-
const current = d.getTime();
|
|
1498
|
-
|
|
1499
|
-
if( !skip_undo )
|
|
1769
|
+
if( !skip_undo && this.code.lines.length )
|
|
1500
1770
|
{
|
|
1501
|
-
|
|
1502
|
-
this._lastTime = current;
|
|
1503
|
-
this._addUndoStep( cursor );
|
|
1504
|
-
} else {
|
|
1505
|
-
if( (current - this._lastTime) > 3000 && this.code.lines.length){
|
|
1506
|
-
this._lastTime = null;
|
|
1507
|
-
this._addUndoStep( cursor );
|
|
1508
|
-
}else{
|
|
1509
|
-
// If time not enough, reset timer
|
|
1510
|
-
this._lastTime = current;
|
|
1511
|
-
}
|
|
1512
|
-
}
|
|
1771
|
+
this._addUndoStep( cursor );
|
|
1513
1772
|
}
|
|
1514
1773
|
|
|
1515
1774
|
// Some custom cases for word enclosing (), {}, "", '', ...
|
|
@@ -1517,7 +1776,7 @@ class CodeEditor {
|
|
|
1517
1776
|
const enclosableKeys = ["\"", "'", "(", "{"];
|
|
1518
1777
|
if( enclosableKeys.indexOf( key ) > -1 )
|
|
1519
1778
|
{
|
|
1520
|
-
if( this.
|
|
1779
|
+
if( this._encloseSelectedWordWithKey(key, lidx, cursor) )
|
|
1521
1780
|
return;
|
|
1522
1781
|
}
|
|
1523
1782
|
|
|
@@ -1527,7 +1786,6 @@ class CodeEditor {
|
|
|
1527
1786
|
if( this.selection )
|
|
1528
1787
|
{
|
|
1529
1788
|
this.actions['Backspace'].callback(lidx, cursor, e);
|
|
1530
|
-
lidx = cursor.line; // Update this, since it's from the old code
|
|
1531
1789
|
}
|
|
1532
1790
|
|
|
1533
1791
|
// Append key
|
|
@@ -1567,6 +1825,14 @@ class CodeEditor {
|
|
|
1567
1825
|
// Update only the current line, since it's only an appended key
|
|
1568
1826
|
this.processLine( lidx );
|
|
1569
1827
|
|
|
1828
|
+
// We are out of the viewport and max length is different? Resize scrollbars...
|
|
1829
|
+
const maxLineLength = this.getMaxLineLength();
|
|
1830
|
+
const numViewportChars = Math.floor( this.codeScroller.clientWidth / this.charWidth );
|
|
1831
|
+
if( maxLineLength >= numViewportChars && maxLineLength != this._lastMaxLineLength )
|
|
1832
|
+
{
|
|
1833
|
+
this.resize( maxLineLength );
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1570
1836
|
// Manage autocomplete
|
|
1571
1837
|
|
|
1572
1838
|
if( this.useAutoComplete )
|
|
@@ -1587,6 +1853,10 @@ class CodeEditor {
|
|
|
1587
1853
|
text_to_copy = "\n" + this.code.lines[ cursor.line ];
|
|
1588
1854
|
}
|
|
1589
1855
|
else {
|
|
1856
|
+
|
|
1857
|
+
// Some selections don't depend on mouse up..
|
|
1858
|
+
if( this.selection ) this.selection.invertIfNecessary();
|
|
1859
|
+
|
|
1590
1860
|
const separator = "_NEWLINE_";
|
|
1591
1861
|
let code = this.code.lines.join(separator);
|
|
1592
1862
|
|
|
@@ -1603,7 +1873,7 @@ class CodeEditor {
|
|
|
1603
1873
|
text_to_copy = lines.join('\n');
|
|
1604
1874
|
}
|
|
1605
1875
|
|
|
1606
|
-
navigator.clipboard.writeText(text_to_copy).then(() => console.log("Successfully copied"), (err) => console.error("Error"));
|
|
1876
|
+
navigator.clipboard.writeText( text_to_copy ).then(() => console.log("Successfully copied"), (err) => console.error("Error"));
|
|
1607
1877
|
}
|
|
1608
1878
|
|
|
1609
1879
|
async _cutContent() {
|
|
@@ -1619,6 +1889,10 @@ class CodeEditor {
|
|
|
1619
1889
|
this.resetCursorPos( CodeEditor.CURSOR_LEFT );
|
|
1620
1890
|
}
|
|
1621
1891
|
else {
|
|
1892
|
+
|
|
1893
|
+
// Some selections don't depend on mouse up..
|
|
1894
|
+
if( this.selection ) this.selection.invertIfNecessary();
|
|
1895
|
+
|
|
1622
1896
|
const separator = "_NEWLINE_";
|
|
1623
1897
|
let code = this.code.lines.join(separator);
|
|
1624
1898
|
|
|
@@ -1637,7 +1911,7 @@ class CodeEditor {
|
|
|
1637
1911
|
this.deleteSelection( cursor );
|
|
1638
1912
|
}
|
|
1639
1913
|
|
|
1640
|
-
navigator.clipboard.writeText(text_to_cut).then(() => console.log("Successfully cut"), (err) => console.error("Error"));
|
|
1914
|
+
navigator.clipboard.writeText( text_to_cut ).then(() => console.log("Successfully cut"), (err) => console.error("Error"));
|
|
1641
1915
|
}
|
|
1642
1916
|
|
|
1643
1917
|
action( key, deleteSelection, fn ) {
|
|
@@ -1662,7 +1936,7 @@ class CodeEditor {
|
|
|
1662
1936
|
|
|
1663
1937
|
toLocalLine( line ) {
|
|
1664
1938
|
|
|
1665
|
-
const d = Math.max( this.
|
|
1939
|
+
const d = Math.max( this.firstLineInViewport - this.lineScrollMargin.x, 0 );
|
|
1666
1940
|
return Math.min( Math.max( line - d, 0 ), this.code.lines.length - 1 );
|
|
1667
1941
|
}
|
|
1668
1942
|
|
|
@@ -1672,38 +1946,34 @@ class CodeEditor {
|
|
|
1672
1946
|
}
|
|
1673
1947
|
|
|
1674
1948
|
processLines( mode ) {
|
|
1675
|
-
|
|
1676
|
-
mode = mode ?? CodeEditor.KEEP_VISIBLE_LINES;
|
|
1677
|
-
|
|
1678
|
-
// console.clear();
|
|
1679
|
-
console.log("--------------------------------------------");
|
|
1680
|
-
|
|
1681
|
-
const lastScrollTop = this.getScrollTop();
|
|
1949
|
+
|
|
1682
1950
|
const start = performance.now();
|
|
1683
1951
|
|
|
1684
|
-
var gutter_html = ""
|
|
1685
|
-
|
|
1952
|
+
var gutter_html = "";
|
|
1953
|
+
var code_html = "";
|
|
1954
|
+
|
|
1955
|
+
// Reset all lines content
|
|
1686
1956
|
this.code.innerHTML = "";
|
|
1687
|
-
|
|
1957
|
+
|
|
1688
1958
|
// Get info about lines in viewport
|
|
1689
|
-
const
|
|
1690
|
-
|
|
1959
|
+
const lastScrollTop = this.getScrollTop();
|
|
1960
|
+
this.firstLineInViewport = ( mode ?? CodeEditor.KEEP_VISIBLE_LINES ) & CodeEditor.UPDATE_VISIBLE_LINES ?
|
|
1961
|
+
( (lastScrollTop / this.lineHeight)|0 ) : this.firstLineInViewport;
|
|
1691
1962
|
const totalLinesInViewport = ((this.codeScroller.offsetHeight - 36) / this.lineHeight)|0;
|
|
1692
|
-
this.
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
Math.max( firstLineInViewport - this.lineScrollMargin.x, 0 ),
|
|
1696
|
-
Math.min( firstLineInViewport + totalLinesInViewport + this.lineScrollMargin.y, this.code.lines.length )
|
|
1963
|
+
this.visibleLinesViewport = new LX.vec2(
|
|
1964
|
+
Math.max( this.firstLineInViewport - this.lineScrollMargin.x, 0 ),
|
|
1965
|
+
Math.min( this.firstLineInViewport + totalLinesInViewport + this.lineScrollMargin.y, this.code.lines.length )
|
|
1697
1966
|
);
|
|
1698
1967
|
|
|
1699
1968
|
// Add remaining lines if we are near the end of the scroll
|
|
1700
1969
|
{
|
|
1701
|
-
const diff = Math.max( this.code.lines.length -
|
|
1702
|
-
if( diff
|
|
1703
|
-
|
|
1970
|
+
const diff = Math.max( this.code.lines.length - this.visibleLinesViewport.y, 0 );
|
|
1971
|
+
if( diff <= this.lineScrollMargin.y )
|
|
1972
|
+
this.visibleLinesViewport.y += diff;
|
|
1704
1973
|
}
|
|
1705
|
-
|
|
1706
|
-
|
|
1974
|
+
|
|
1975
|
+
// Process visible lines
|
|
1976
|
+
for( let i = this.visibleLinesViewport.x; i < this.visibleLinesViewport.y; ++i )
|
|
1707
1977
|
{
|
|
1708
1978
|
gutter_html += "<span>" + (i + 1) + "</span>";
|
|
1709
1979
|
code_html += this.processLine( i, true );
|
|
@@ -1711,24 +1981,23 @@ class CodeEditor {
|
|
|
1711
1981
|
|
|
1712
1982
|
this.code.innerHTML = code_html;
|
|
1713
1983
|
|
|
1714
|
-
console.log("RANGE:",
|
|
1715
|
-
console.log( "Num lines processed:", (
|
|
1716
|
-
console.log("--------------------------------------------");
|
|
1984
|
+
// console.log("RANGE:", this.visibleLinesViewport);
|
|
1985
|
+
// console.log( "Num lines processed:", (this.visibleLinesViewport.y - this.visibleLinesViewport.x), performance.now() - start );
|
|
1986
|
+
// console.log("--------------------------------------------");
|
|
1717
1987
|
|
|
1988
|
+
// Update scroll data
|
|
1718
1989
|
this.codeScroller.scrollTop = lastScrollTop;
|
|
1990
|
+
this.code.style.top = ( this.visibleLinesViewport.x * this.lineHeight ) + "px";
|
|
1719
1991
|
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
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";
|
|
1992
|
+
// Update selections
|
|
1993
|
+
if( this.selection )
|
|
1994
|
+
this.processSelection( null, true );
|
|
1728
1995
|
|
|
1729
|
-
|
|
1996
|
+
// Clear tmp vars
|
|
1997
|
+
delete this._buildingString;
|
|
1998
|
+
delete this._pendingString;
|
|
1730
1999
|
|
|
1731
|
-
|
|
2000
|
+
this.resize();
|
|
1732
2001
|
}
|
|
1733
2002
|
|
|
1734
2003
|
processLine( linenum, force ) {
|
|
@@ -1796,7 +2065,7 @@ class CodeEditor {
|
|
|
1796
2065
|
delete this._buildingBlockComment;
|
|
1797
2066
|
}
|
|
1798
2067
|
|
|
1799
|
-
line_inner_html += this.
|
|
2068
|
+
line_inner_html += this._evaluateToken( token, prev, next, (i == tokensToEvaluate.length - 1) );
|
|
1800
2069
|
}
|
|
1801
2070
|
|
|
1802
2071
|
return UPDATE_LINE( line_inner_html );
|
|
@@ -1829,7 +2098,7 @@ class CodeEditor {
|
|
|
1829
2098
|
|
|
1830
2099
|
const singleLineCommentToken = this.languages[ this.highlight ].singleLineCommentToken ?? this.defaultSingleLineCommentToken;
|
|
1831
2100
|
const idx = linestring.indexOf( singleLineCommentToken );
|
|
1832
|
-
|
|
2101
|
+
|
|
1833
2102
|
if( idx > -1 )
|
|
1834
2103
|
{
|
|
1835
2104
|
const stringKeys = Object.values( this.stringKeys );
|
|
@@ -1870,7 +2139,7 @@ class CodeEditor {
|
|
|
1870
2139
|
tokensToEvaluate.push( t );
|
|
1871
2140
|
};
|
|
1872
2141
|
|
|
1873
|
-
let iter = linestring.matchAll(/(::|[\[\](){}<>.,;:*"'
|
|
2142
|
+
let iter = linestring.matchAll(/(::|[\[\](){}<>.,;:*"'%@!/= ])/g);
|
|
1874
2143
|
let subtokens = iter.next();
|
|
1875
2144
|
if( subtokens.value )
|
|
1876
2145
|
{
|
|
@@ -1923,7 +2192,7 @@ class CodeEditor {
|
|
|
1923
2192
|
return kindArray[this.highlight] && kindArray[this.highlight][token] != undefined;
|
|
1924
2193
|
}
|
|
1925
2194
|
|
|
1926
|
-
|
|
2195
|
+
_evaluateToken( token, prev, next, isLastToken ) {
|
|
1927
2196
|
|
|
1928
2197
|
const highlight = this.highlight.replace(/\s/g, '').replaceAll("+", "p").toLowerCase();
|
|
1929
2198
|
const customStringKeys = Object.assign( {}, this.stringKeys );
|
|
@@ -1950,7 +2219,7 @@ class CodeEditor {
|
|
|
1950
2219
|
{
|
|
1951
2220
|
if( this._buildingString != undefined )
|
|
1952
2221
|
{
|
|
1953
|
-
this.
|
|
2222
|
+
this._appendStringToken( token );
|
|
1954
2223
|
return "";
|
|
1955
2224
|
}
|
|
1956
2225
|
return token;
|
|
@@ -1967,7 +2236,7 @@ class CodeEditor {
|
|
|
1967
2236
|
token_classname = "cm-com";
|
|
1968
2237
|
|
|
1969
2238
|
else if( this._buildingString != undefined )
|
|
1970
|
-
discardToken = this.
|
|
2239
|
+
discardToken = this._appendStringToken( token );
|
|
1971
2240
|
|
|
1972
2241
|
else if( this._mustHightlightWord( token, this.keywords ) )
|
|
1973
2242
|
token_classname = "cm-kwd";
|
|
@@ -1981,28 +2250,28 @@ class CodeEditor {
|
|
|
1981
2250
|
else if( this._mustHightlightWord( token, this.symbols ) )
|
|
1982
2251
|
token_classname = "cm-sym";
|
|
1983
2252
|
|
|
1984
|
-
else if( token.substr(0,
|
|
2253
|
+
else if( token.substr( 0, singleLineCommentToken.length ) == singleLineCommentToken )
|
|
1985
2254
|
token_classname = "cm-com";
|
|
1986
2255
|
|
|
1987
|
-
else if( usesBlockComments && token.substr(0, 2) == '/*' )
|
|
2256
|
+
else if( usesBlockComments && token.substr( 0, 2 ) == '/*' )
|
|
1988
2257
|
token_classname = "cm-com";
|
|
1989
2258
|
|
|
1990
|
-
else if( usesBlockComments && token.substr(token.length - 2) == '*/' )
|
|
2259
|
+
else if( usesBlockComments && token.substr( token.length - 2 ) == '*/' )
|
|
1991
2260
|
token_classname = "cm-com";
|
|
1992
2261
|
|
|
1993
|
-
else if( this.isNumber(token) || this.isNumber( token.replace(/[px]|[em]|%/g,'') ) )
|
|
2262
|
+
else if( this.isNumber( token ) || this.isNumber( token.replace(/[px]|[em]|%/g,'') ) )
|
|
1994
2263
|
token_classname = "cm-dec";
|
|
1995
2264
|
|
|
1996
|
-
else if( this.
|
|
2265
|
+
else if( this._isCSSClass( token, prev, next ) )
|
|
1997
2266
|
token_classname = "cm-kwd";
|
|
1998
2267
|
|
|
1999
|
-
else if ( this.
|
|
2268
|
+
else if ( this._isType( token, prev, next ) )
|
|
2000
2269
|
token_classname = "cm-typ";
|
|
2001
2270
|
|
|
2002
|
-
else if ( highlight == 'batch' && (token == '@' || prev == ':' || prev == '@') )
|
|
2271
|
+
else if ( highlight == 'batch' && ( token == '@' || prev == ':' || prev == '@' ) )
|
|
2003
2272
|
token_classname = "cm-kwd";
|
|
2004
2273
|
|
|
2005
|
-
else if ( highlight == 'cpp' && token.includes('#') ) // C++ preprocessor
|
|
2274
|
+
else if ( highlight == 'cpp' && token.includes( '#' ) ) // C++ preprocessor
|
|
2006
2275
|
token_classname = "cm-ppc";
|
|
2007
2276
|
|
|
2008
2277
|
else if ( highlight == 'cpp' && prev == '<' && (next == '>' || next == '*') ) // Defining template type in C++
|
|
@@ -2024,7 +2293,7 @@ class CodeEditor {
|
|
|
2024
2293
|
// We finished constructing a string
|
|
2025
2294
|
if( this._buildingString && ( this._stringEnded || isLastToken ) )
|
|
2026
2295
|
{
|
|
2027
|
-
token = this.
|
|
2296
|
+
token = this._getCurrentString();
|
|
2028
2297
|
token_classname = "cm-str";
|
|
2029
2298
|
discardToken = false;
|
|
2030
2299
|
}
|
|
@@ -2035,6 +2304,9 @@ class CodeEditor {
|
|
|
2035
2304
|
if( discardToken )
|
|
2036
2305
|
return "";
|
|
2037
2306
|
|
|
2307
|
+
token = token.replace( "<", "<" );
|
|
2308
|
+
token = token.replace( ">", ">" );
|
|
2309
|
+
|
|
2038
2310
|
// No highlighting, no need to put it inside another span..
|
|
2039
2311
|
if( !token_classname.length )
|
|
2040
2312
|
return token;
|
|
@@ -2043,7 +2315,7 @@ class CodeEditor {
|
|
|
2043
2315
|
}
|
|
2044
2316
|
}
|
|
2045
2317
|
|
|
2046
|
-
|
|
2318
|
+
_appendStringToken( token ) {
|
|
2047
2319
|
|
|
2048
2320
|
if( !this._pendingString )
|
|
2049
2321
|
this._pendingString = "";
|
|
@@ -2053,14 +2325,14 @@ class CodeEditor {
|
|
|
2053
2325
|
return true;
|
|
2054
2326
|
}
|
|
2055
2327
|
|
|
2056
|
-
|
|
2328
|
+
_getCurrentString() {
|
|
2057
2329
|
|
|
2058
2330
|
const chars = this._pendingString;
|
|
2059
2331
|
delete this._pendingString;
|
|
2060
2332
|
return chars;
|
|
2061
2333
|
}
|
|
2062
2334
|
|
|
2063
|
-
|
|
2335
|
+
_isCSSClass( token, prev, next ) {
|
|
2064
2336
|
return this.highlight == 'CSS' && prev == '.';
|
|
2065
2337
|
}
|
|
2066
2338
|
|
|
@@ -2077,7 +2349,7 @@ class CodeEditor {
|
|
|
2077
2349
|
return token.length && token != ' ' && !Number.isNaN(+token);
|
|
2078
2350
|
}
|
|
2079
2351
|
|
|
2080
|
-
|
|
2352
|
+
_isType( token, prev, next ) {
|
|
2081
2353
|
|
|
2082
2354
|
// Common case
|
|
2083
2355
|
if( this._mustHightlightWord( token, this.types ) )
|
|
@@ -2101,7 +2373,7 @@ class CodeEditor {
|
|
|
2101
2373
|
}
|
|
2102
2374
|
}
|
|
2103
2375
|
|
|
2104
|
-
|
|
2376
|
+
_encloseSelectedWordWithKey( key, lidx, cursor ) {
|
|
2105
2377
|
|
|
2106
2378
|
if( !this.selection || (this.selection.fromY != this.selection.toY) )
|
|
2107
2379
|
return false;
|
|
@@ -2213,12 +2485,13 @@ class CodeEditor {
|
|
|
2213
2485
|
const post = code.slice( index + num_chars );
|
|
2214
2486
|
|
|
2215
2487
|
this.code.lines = ( pre + post ).split( separator );
|
|
2216
|
-
|
|
2217
|
-
|
|
2488
|
+
|
|
2218
2489
|
this.cursorToLine( cursor, this.selection.fromY, true );
|
|
2219
2490
|
this.cursorToPosition( cursor, this.selection.fromX );
|
|
2220
2491
|
|
|
2221
2492
|
this.endSelection();
|
|
2493
|
+
|
|
2494
|
+
this.processLines();
|
|
2222
2495
|
this._refreshCodeInfo( cursor.line, cursor.position );
|
|
2223
2496
|
}
|
|
2224
2497
|
|
|
@@ -2227,6 +2500,7 @@ class CodeEditor {
|
|
|
2227
2500
|
this.selections.classList.remove('show');
|
|
2228
2501
|
this.selections.innerHTML = "";
|
|
2229
2502
|
delete this.selection;
|
|
2503
|
+
delete this._tripleClickSelection;
|
|
2230
2504
|
}
|
|
2231
2505
|
|
|
2232
2506
|
cursorToRight( key, cursor ) {
|
|
@@ -2236,14 +2510,15 @@ class CodeEditor {
|
|
|
2236
2510
|
cursor._left += this.charWidth;
|
|
2237
2511
|
cursor.style.left = "calc( " + cursor._left + "px + " + this.xPadding + " )";
|
|
2238
2512
|
cursor.position++;
|
|
2513
|
+
|
|
2239
2514
|
this.restartBlink();
|
|
2240
2515
|
this._refreshCodeInfo( cursor.line, cursor.position );
|
|
2241
2516
|
|
|
2242
2517
|
// Add horizontal scroll
|
|
2243
2518
|
|
|
2244
2519
|
doAsync(() => {
|
|
2245
|
-
var
|
|
2246
|
-
if( cursor.position >=
|
|
2520
|
+
var viewportSizeX = ( this.codeScroller.clientWidth + this.getScrollLeft() ) - 48; // Gutter offset
|
|
2521
|
+
if( (cursor.position * this.charWidth) >= viewportSizeX )
|
|
2247
2522
|
this.setScrollLeft( this.getScrollLeft() + this.charWidth );
|
|
2248
2523
|
});
|
|
2249
2524
|
}
|
|
@@ -2253,18 +2528,18 @@ class CodeEditor {
|
|
|
2253
2528
|
if(!key) return;
|
|
2254
2529
|
cursor = cursor ?? this.cursors.children[ 0 ];
|
|
2255
2530
|
cursor._left -= this.charWidth;
|
|
2256
|
-
cursor._left = Math.max(cursor._left, 0);
|
|
2531
|
+
cursor._left = Math.max( cursor._left, 0 );
|
|
2257
2532
|
cursor.style.left = "calc( " + cursor._left + "px + " + this.xPadding + " )";
|
|
2258
2533
|
cursor.position--;
|
|
2259
|
-
cursor.position = Math.max(cursor.position, 0);
|
|
2534
|
+
cursor.position = Math.max( cursor.position, 0 );
|
|
2260
2535
|
this.restartBlink();
|
|
2261
2536
|
this._refreshCodeInfo( cursor.line, cursor.position );
|
|
2262
2537
|
|
|
2263
2538
|
// Add horizontal scroll
|
|
2264
2539
|
|
|
2265
2540
|
doAsync(() => {
|
|
2266
|
-
var
|
|
2267
|
-
if( (cursor.position - 1) <
|
|
2541
|
+
var viewportSizeX = this.getScrollLeft(); // Gutter offset
|
|
2542
|
+
if( ( ( cursor.position - 1 ) * this.charWidth ) < viewportSizeX )
|
|
2268
2543
|
this.setScrollLeft( this.getScrollLeft() - this.charWidth );
|
|
2269
2544
|
});
|
|
2270
2545
|
}
|
|
@@ -2283,7 +2558,7 @@ class CodeEditor {
|
|
|
2283
2558
|
this._refreshCodeInfo( cursor.line, cursor.position );
|
|
2284
2559
|
|
|
2285
2560
|
doAsync(() => {
|
|
2286
|
-
var first_line = (this.getScrollTop() / this.lineHeight)|0;
|
|
2561
|
+
var first_line = ( this.getScrollTop() / this.lineHeight )|0;
|
|
2287
2562
|
if( (cursor.line - 1) < first_line )
|
|
2288
2563
|
this.setScrollTop( this.getScrollTop() - this.lineHeight );
|
|
2289
2564
|
});
|
|
@@ -2302,7 +2577,7 @@ class CodeEditor {
|
|
|
2302
2577
|
this._refreshCodeInfo( cursor.line, cursor.position );
|
|
2303
2578
|
|
|
2304
2579
|
doAsync(() => {
|
|
2305
|
-
var last_line = ((this.codeScroller.offsetHeight + this.getScrollTop()) / this.lineHeight)|0;
|
|
2580
|
+
var last_line = ( ( this.codeScroller.offsetHeight + this.getScrollTop() ) / this.lineHeight )|0;
|
|
2306
2581
|
if( cursor.line >= last_line )
|
|
2307
2582
|
this.setScrollTop( this.getScrollTop() + this.lineHeight );
|
|
2308
2583
|
});
|
|
@@ -2344,7 +2619,7 @@ class CodeEditor {
|
|
|
2344
2619
|
|
|
2345
2620
|
cursor = cursor ?? this.cursors.children[ 0 ];
|
|
2346
2621
|
cursor.line = state.line ?? 0;
|
|
2347
|
-
cursor.position = state.
|
|
2622
|
+
cursor.position = state.position ?? 0;
|
|
2348
2623
|
|
|
2349
2624
|
cursor._left = state.left ?? 0;
|
|
2350
2625
|
cursor.style.left = "calc(" + (cursor._left - this.getScrollLeft()) + "px + " + this.xPadding + ")";
|
|
@@ -2414,9 +2689,32 @@ class CodeEditor {
|
|
|
2414
2689
|
this.setScrollBarValue( 'vertical' );
|
|
2415
2690
|
}
|
|
2416
2691
|
|
|
2417
|
-
|
|
2692
|
+
resize( pMaxLength ) {
|
|
2693
|
+
|
|
2694
|
+
setTimeout( () => {
|
|
2695
|
+
|
|
2696
|
+
// Update max viewport
|
|
2697
|
+
const maxLineLength = pMaxLength ?? this.getMaxLineLength();
|
|
2698
|
+
const scrollWidth = maxLineLength * this.charWidth;
|
|
2699
|
+
const scrollHeight = this.code.lines.length * this.lineHeight;
|
|
2700
|
+
|
|
2701
|
+
this._lastMaxLineLength = maxLineLength;
|
|
2702
|
+
|
|
2703
|
+
this.codeSizer.style.minWidth = scrollWidth + "px";
|
|
2704
|
+
this.codeSizer.style.minHeight = scrollHeight + "px";
|
|
2705
|
+
|
|
2706
|
+
this.resizeScrollBars();
|
|
2707
|
+
|
|
2708
|
+
// console.warn("Resize editor viewport");
|
|
2709
|
+
|
|
2710
|
+
}, 10 );
|
|
2711
|
+
}
|
|
2712
|
+
|
|
2713
|
+
resizeScrollBars() {
|
|
2714
|
+
|
|
2715
|
+
const totalLinesInViewport = ((this.codeScroller.offsetHeight - 36) / this.lineHeight)|0;
|
|
2418
2716
|
|
|
2419
|
-
if(
|
|
2717
|
+
if( totalLinesInViewport > this.code.lines.length )
|
|
2420
2718
|
{
|
|
2421
2719
|
this.codeScroller.classList.remove( 'with-vscrollbar' );
|
|
2422
2720
|
this.vScrollbar.root.classList.add( 'scrollbar-unused' );
|
|
@@ -2425,13 +2723,12 @@ class CodeEditor {
|
|
|
2425
2723
|
{
|
|
2426
2724
|
this.codeScroller.classList.add( 'with-vscrollbar' );
|
|
2427
2725
|
this.vScrollbar.root.classList.remove( 'scrollbar-unused' );
|
|
2428
|
-
this.vScrollbar.thumb.size = (
|
|
2726
|
+
this.vScrollbar.thumb.size = (totalLinesInViewport / this.code.lines.length);
|
|
2429
2727
|
this.vScrollbar.thumb.style.height = (this.vScrollbar.thumb.size * 100.0) + "%";
|
|
2430
2728
|
}
|
|
2431
2729
|
|
|
2432
2730
|
const numViewportChars = Math.floor( this.codeScroller.clientWidth / this.charWidth );
|
|
2433
|
-
const
|
|
2434
|
-
const maxLineLength = Math.max(...line_lengths);
|
|
2731
|
+
const maxLineLength = this._lastMaxLineLength;
|
|
2435
2732
|
|
|
2436
2733
|
if( numViewportChars > maxLineLength )
|
|
2437
2734
|
{
|
|
@@ -2625,11 +2922,11 @@ class CodeEditor {
|
|
|
2625
2922
|
|
|
2626
2923
|
// Add language special keys...
|
|
2627
2924
|
suggestions = suggestions.concat(
|
|
2628
|
-
Object.keys( this.builtin[ this.highlight ]
|
|
2629
|
-
Object.keys( this.keywords[ this.highlight ]
|
|
2630
|
-
Object.keys( this.statementsAndDeclarations[ this.highlight ]
|
|
2631
|
-
Object.keys( this.types[ this.highlight ]
|
|
2632
|
-
Object.keys( this.utils[ this.highlight ]
|
|
2925
|
+
Object.keys( this.builtin[ this.highlight ] ?? {} ),
|
|
2926
|
+
Object.keys( this.keywords[ this.highlight ] ?? {} ),
|
|
2927
|
+
Object.keys( this.statementsAndDeclarations[ this.highlight ] ?? {} ),
|
|
2928
|
+
Object.keys( this.types[ this.highlight ] ?? {} ),
|
|
2929
|
+
Object.keys( this.utils[ this.highlight ] ?? {} )
|
|
2633
2930
|
);
|
|
2634
2931
|
|
|
2635
2932
|
// Add words in current tab plus remove current word
|