lexgui 0.1.45 → 0.2.0
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 +129 -99
- package/build/lexgui.css +402 -144
- package/build/lexgui.js +8278 -7629
- package/build/lexgui.min.css +1 -0
- package/build/lexgui.min.js +1 -0
- package/build/lexgui.module.js +903 -274
- package/build/lexgui.module.min.js +1 -0
- package/changelog.md +29 -2
- package/demo.js +89 -839
- package/package.json +1 -1
|
@@ -237,6 +237,8 @@ class CodeEditor {
|
|
|
237
237
|
static CODE_MIN_FONT_SIZE = 9;
|
|
238
238
|
static CODE_MAX_FONT_SIZE = 22;
|
|
239
239
|
|
|
240
|
+
static LINE_GUTTER_WIDTH = 48;
|
|
241
|
+
|
|
240
242
|
/**
|
|
241
243
|
* @param {*} options
|
|
242
244
|
* name:
|
|
@@ -313,6 +315,9 @@ class CodeEditor {
|
|
|
313
315
|
this.base_area = area;
|
|
314
316
|
this.area = new LX.Area( { className: "lexcodeeditor", height: "100%", skipAppend: true } );
|
|
315
317
|
|
|
318
|
+
this.skipCodeInfo = options.skipInfo ?? false;
|
|
319
|
+
this.disableEdition = options.disableEdition ?? false;
|
|
320
|
+
|
|
316
321
|
this.tabs = this.area.addTabs( { onclose: (name) => {
|
|
317
322
|
delete this.openedTabs[ name ];
|
|
318
323
|
if( Object.keys( this.openedTabs ).length < 2 )
|
|
@@ -322,12 +327,15 @@ class CodeEditor {
|
|
|
322
327
|
}
|
|
323
328
|
} } );
|
|
324
329
|
|
|
325
|
-
this.
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
330
|
+
if( !this.disableEdition )
|
|
331
|
+
{
|
|
332
|
+
this.tabs.root.addEventListener( 'dblclick', (e) => {
|
|
333
|
+
if( options.allowAddScripts ?? true ) {
|
|
334
|
+
e.preventDefault();
|
|
335
|
+
this.addTab("unnamed.js", true);
|
|
336
|
+
}
|
|
337
|
+
} );
|
|
338
|
+
}
|
|
331
339
|
|
|
332
340
|
// Full editor
|
|
333
341
|
area.root.classList.add('codebasearea');
|
|
@@ -339,15 +347,16 @@ class CodeEditor {
|
|
|
339
347
|
this.root.tabIndex = -1;
|
|
340
348
|
area.attach( this.root );
|
|
341
349
|
|
|
342
|
-
this.skipCodeInfo = options.skipInfo ?? false;
|
|
343
|
-
this.disableEdition = options.disableEdition ?? false;
|
|
344
|
-
|
|
345
350
|
if( !this.disableEdition )
|
|
346
351
|
{
|
|
347
352
|
this.root.addEventListener( 'keydown', this.processKey.bind( this) );
|
|
348
353
|
this.root.addEventListener( 'focus', this.processFocus.bind( this, true ) );
|
|
349
354
|
this.root.addEventListener( 'focusout', this.processFocus.bind( this, false ) );
|
|
350
355
|
}
|
|
356
|
+
else
|
|
357
|
+
{
|
|
358
|
+
this.root.classList.add( "disabled" );
|
|
359
|
+
}
|
|
351
360
|
|
|
352
361
|
this.root.addEventListener( 'mousedown', this.processMouse.bind(this) );
|
|
353
362
|
this.root.addEventListener( 'mouseup', this.processMouse.bind(this) );
|
|
@@ -373,7 +382,7 @@ class CodeEditor {
|
|
|
373
382
|
this.selections = {};
|
|
374
383
|
|
|
375
384
|
// Css char synchronization
|
|
376
|
-
this.xPadding = "
|
|
385
|
+
this.xPadding = CodeEditor.LINE_GUTTER_WIDTH + "px";
|
|
377
386
|
|
|
378
387
|
// Add main cursor
|
|
379
388
|
this._addCursor( 0, 0, true, true );
|
|
@@ -383,57 +392,60 @@ class CodeEditor {
|
|
|
383
392
|
this.codeScroller = this.tabs.area.root;
|
|
384
393
|
this.firstLineInViewport = 0;
|
|
385
394
|
this.lineScrollMargin = new LX.vec2( 20, 20 ); // [ mUp, mDown ]
|
|
386
|
-
window.scroller = this.codeScroller;
|
|
387
395
|
|
|
388
396
|
let lastScrollTopValue = -1;
|
|
389
|
-
this.codeScroller.addEventListener( 'scroll', e => {
|
|
390
397
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
398
|
+
if( !this.disableEdition )
|
|
399
|
+
{
|
|
400
|
+
this.codeScroller.addEventListener( 'scroll', e => {
|
|
401
|
+
|
|
402
|
+
if( this._discardScroll )
|
|
403
|
+
{
|
|
404
|
+
this._discardScroll = false;
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
396
407
|
|
|
397
|
-
|
|
408
|
+
this.setScrollBarValue( 'vertical' );
|
|
398
409
|
|
|
399
|
-
|
|
410
|
+
const scrollTop = this.getScrollTop();
|
|
400
411
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
{
|
|
404
|
-
if( this.visibleLinesViewport.y < (this.code.lines.length - 1) )
|
|
412
|
+
// Scroll down...
|
|
413
|
+
if( scrollTop > lastScrollTopValue )
|
|
405
414
|
{
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
415
|
+
if( this.visibleLinesViewport.y < (this.code.lines.length - 1) )
|
|
416
|
+
{
|
|
417
|
+
const totalLinesInViewport = ((this.codeScroller.offsetHeight - 36) / this.lineHeight)|0;
|
|
418
|
+
const scrollDownBoundary =
|
|
419
|
+
( Math.max( this.visibleLinesViewport.y - totalLinesInViewport, 0 ) - 1 ) * this.lineHeight;
|
|
409
420
|
|
|
410
|
-
|
|
421
|
+
if( scrollTop >= scrollDownBoundary )
|
|
422
|
+
this.processLines( CodeEditor.UPDATE_VISIBLE_LINES );
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
// Scroll up...
|
|
426
|
+
else
|
|
427
|
+
{
|
|
428
|
+
const scrollUpBoundary = parseInt( this.code.style.top );
|
|
429
|
+
if( scrollTop < scrollUpBoundary )
|
|
411
430
|
this.processLines( CodeEditor.UPDATE_VISIBLE_LINES );
|
|
412
431
|
}
|
|
413
|
-
}
|
|
414
|
-
// Scroll up...
|
|
415
|
-
else
|
|
416
|
-
{
|
|
417
|
-
const scrollUpBoundary = parseInt( this.code.style.top );
|
|
418
|
-
if( scrollTop < scrollUpBoundary )
|
|
419
|
-
this.processLines( CodeEditor.UPDATE_VISIBLE_LINES );
|
|
420
|
-
}
|
|
421
432
|
|
|
422
|
-
|
|
423
|
-
|
|
433
|
+
lastScrollTopValue = scrollTop;
|
|
434
|
+
});
|
|
424
435
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
436
|
+
this.codeScroller.addEventListener( 'wheel', e => {
|
|
437
|
+
if( e.ctrlKey )
|
|
438
|
+
{
|
|
439
|
+
e.preventDefault();
|
|
440
|
+
( e.deltaY > 0.0 ? this._decreaseFontSize() : this._increaseFontSize() );
|
|
441
|
+
}
|
|
442
|
+
else
|
|
443
|
+
{
|
|
444
|
+
const dX = ( e.deltaY > 0.0 ? 10.0 : -10.0 ) * ( e.shiftKey ? 1.0 : 0.0 );
|
|
445
|
+
if( dX != 0.0 ) this.setScrollBarValue( 'horizontal', dX );
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
}
|
|
437
449
|
}
|
|
438
450
|
|
|
439
451
|
// This is only the container, line numbers are in the same line div
|
|
@@ -455,59 +467,62 @@ class CodeEditor {
|
|
|
455
467
|
area.attach( this.hScrollbar.root );
|
|
456
468
|
}
|
|
457
469
|
|
|
458
|
-
|
|
470
|
+
if( !this.disableEdition )
|
|
459
471
|
{
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
472
|
+
// Add autocomplete box
|
|
473
|
+
{
|
|
474
|
+
var box = document.createElement( 'div' );
|
|
475
|
+
box.className = "autocomplete";
|
|
476
|
+
this.autocomplete = box;
|
|
477
|
+
this.tabs.area.attach( box );
|
|
464
478
|
|
|
465
|
-
|
|
466
|
-
|
|
479
|
+
this.isAutoCompleteActive = false;
|
|
480
|
+
}
|
|
467
481
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
482
|
+
// Add search box
|
|
483
|
+
{
|
|
484
|
+
var box = document.createElement( 'div' );
|
|
485
|
+
box.className = "searchbox";
|
|
472
486
|
|
|
473
|
-
|
|
474
|
-
|
|
487
|
+
var searchPanel = new LX.Panel();
|
|
488
|
+
box.appendChild( searchPanel.root );
|
|
475
489
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
490
|
+
searchPanel.sameLine( 4 );
|
|
491
|
+
searchPanel.addText( null, "", null, { placeholder: "Find" } );
|
|
492
|
+
searchPanel.addButton( null, "up", () => this.search( null, true ), { className: 'micro', icon: "fa fa-arrow-up" } );
|
|
493
|
+
searchPanel.addButton( null, "down", () => this.search(), { className: 'micro', icon: "fa fa-arrow-down" } );
|
|
494
|
+
searchPanel.addButton( null, "x", this.hideSearchBox.bind( this ), { className: 'micro', icon: "fa fa-xmark" } );
|
|
481
495
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
496
|
+
box.querySelector( 'input' ).addEventListener( 'keyup', e => {
|
|
497
|
+
if( e.key == 'Escape' ) this.hideSearchBox();
|
|
498
|
+
else if( e.key == 'Enter' ) this.search( e.target.value, !!e.shiftKey );
|
|
499
|
+
} );
|
|
486
500
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
501
|
+
this.searchbox = box;
|
|
502
|
+
this.tabs.area.attach( box );
|
|
503
|
+
}
|
|
490
504
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
505
|
+
// Add search LINE box
|
|
506
|
+
{
|
|
507
|
+
var box = document.createElement( 'div' );
|
|
508
|
+
box.className = "searchbox gotoline";
|
|
495
509
|
|
|
496
|
-
|
|
497
|
-
|
|
510
|
+
var searchPanel = new LX.Panel();
|
|
511
|
+
box.appendChild( searchPanel.root );
|
|
498
512
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
513
|
+
searchPanel.addText( null, "", ( value, event ) => {
|
|
514
|
+
input.value = ":" + value.replaceAll( ':', '' );
|
|
515
|
+
this.goToLine( input.value.slice( 1 ) );
|
|
516
|
+
}, { placeholder: "Go to line", trigger: "input" } );
|
|
503
517
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
518
|
+
let input = box.querySelector( 'input' );
|
|
519
|
+
input.addEventListener( 'keyup', e => {
|
|
520
|
+
if( e.key == 'Escape' ) this.hideSearchLineBox();
|
|
521
|
+
} );
|
|
508
522
|
|
|
509
|
-
|
|
510
|
-
|
|
523
|
+
this.searchlinebox = box;
|
|
524
|
+
this.tabs.area.attach( box );
|
|
525
|
+
}
|
|
511
526
|
}
|
|
512
527
|
|
|
513
528
|
// Add code-sizer
|
|
@@ -1059,7 +1074,7 @@ class CodeEditor {
|
|
|
1059
1074
|
this.addTab("+", false, "New File");
|
|
1060
1075
|
}
|
|
1061
1076
|
|
|
1062
|
-
this.addTab( options.name || "untitled", true, options.title, { language: "
|
|
1077
|
+
this.addTab( options.name || "untitled", true, options.title, { language: "Plain Text" } );
|
|
1063
1078
|
|
|
1064
1079
|
// Create inspector panel
|
|
1065
1080
|
let panel = this._createPanelInfo();
|
|
@@ -1609,6 +1624,11 @@ class CodeEditor {
|
|
|
1609
1624
|
|
|
1610
1625
|
_onSelectTab( isNewTabButton, event, name ) {
|
|
1611
1626
|
|
|
1627
|
+
if( this.disableEdition )
|
|
1628
|
+
{
|
|
1629
|
+
return;
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1612
1632
|
if( isNewTabButton )
|
|
1613
1633
|
{
|
|
1614
1634
|
this._onNewTab( event );
|
|
@@ -1657,7 +1677,9 @@ class CodeEditor {
|
|
|
1657
1677
|
}, 0 );
|
|
1658
1678
|
|
|
1659
1679
|
if( repeats > 0 )
|
|
1680
|
+
{
|
|
1660
1681
|
name = name.split( '.' ).join( '_' + repeats + '.' );
|
|
1682
|
+
}
|
|
1661
1683
|
|
|
1662
1684
|
const isNewTabButton = ( name === '+' );
|
|
1663
1685
|
|
|
@@ -2435,10 +2457,11 @@ class CodeEditor {
|
|
|
2435
2457
|
|
|
2436
2458
|
// We are out of the viewport and max length is different? Resize scrollbars...
|
|
2437
2459
|
const maxLineLength = this.getMaxLineLength();
|
|
2438
|
-
const numViewportChars = Math.floor( this.codeScroller.clientWidth / this.charWidth );
|
|
2460
|
+
const numViewportChars = Math.floor( ( this.codeScroller.clientWidth - CodeEditor.LINE_GUTTER_WIDTH ) / this.charWidth );
|
|
2439
2461
|
if( maxLineLength >= numViewportChars && maxLineLength != this._lastMaxLineLength )
|
|
2440
2462
|
{
|
|
2441
2463
|
this.resize( maxLineLength );
|
|
2464
|
+
this.setScrollLeft( 1e4 );
|
|
2442
2465
|
}
|
|
2443
2466
|
|
|
2444
2467
|
// Manage autocomplete
|
|
@@ -3369,7 +3392,9 @@ class CodeEditor {
|
|
|
3369
3392
|
|
|
3370
3393
|
// I think it's not necessary but...
|
|
3371
3394
|
if( this.disableEdition )
|
|
3395
|
+
{
|
|
3372
3396
|
return;
|
|
3397
|
+
}
|
|
3373
3398
|
|
|
3374
3399
|
// Some selections don't depend on mouse up..
|
|
3375
3400
|
if( cursor.selection ) cursor.selection.invertIfNecessary();
|
|
@@ -3455,7 +3480,7 @@ class CodeEditor {
|
|
|
3455
3480
|
// Add horizontal scroll
|
|
3456
3481
|
|
|
3457
3482
|
doAsync(() => {
|
|
3458
|
-
var viewportSizeX = ( this.codeScroller.clientWidth + this.getScrollLeft() ) -
|
|
3483
|
+
var viewportSizeX = ( this.codeScroller.clientWidth + this.getScrollLeft() ) - CodeEditor.LINE_GUTTER_WIDTH; // Gutter offset
|
|
3459
3484
|
if( (cursor.position * this.charWidth) >= viewportSizeX )
|
|
3460
3485
|
this.setScrollLeft( this.getScrollLeft() + this.charWidth );
|
|
3461
3486
|
});
|
|
@@ -3712,7 +3737,7 @@ class CodeEditor {
|
|
|
3712
3737
|
|
|
3713
3738
|
// Update max viewport
|
|
3714
3739
|
const maxLineLength = pMaxLength ?? this.getMaxLineLength();
|
|
3715
|
-
const scrollWidth = maxLineLength * this.charWidth;
|
|
3740
|
+
const scrollWidth = maxLineLength * this.charWidth + CodeEditor.LINE_GUTTER_WIDTH;
|
|
3716
3741
|
const scrollHeight = this.code.lines.length * this.lineHeight;
|
|
3717
3742
|
|
|
3718
3743
|
this._lastMaxLineLength = maxLineLength;
|
|
@@ -3727,9 +3752,9 @@ class CodeEditor {
|
|
|
3727
3752
|
|
|
3728
3753
|
resizeScrollBars() {
|
|
3729
3754
|
|
|
3730
|
-
const totalLinesInViewport = ((this.codeScroller.offsetHeight
|
|
3755
|
+
const totalLinesInViewport = ((this.codeScroller.offsetHeight) / this.lineHeight)|0;
|
|
3731
3756
|
|
|
3732
|
-
if( totalLinesInViewport
|
|
3757
|
+
if( totalLinesInViewport >= this.code.lines.length )
|
|
3733
3758
|
{
|
|
3734
3759
|
this.codeScroller.classList.remove( 'with-vscrollbar' );
|
|
3735
3760
|
this.vScrollbar.root.classList.add( 'scrollbar-unused' );
|
|
@@ -3742,10 +3767,10 @@ class CodeEditor {
|
|
|
3742
3767
|
this.vScrollbar.thumb.style.height = (this.vScrollbar.thumb.size * 100.0) + "%";
|
|
3743
3768
|
}
|
|
3744
3769
|
|
|
3745
|
-
const numViewportChars = Math.floor( this.codeScroller.clientWidth / this.charWidth );
|
|
3770
|
+
const numViewportChars = Math.floor( ( this.codeScroller.clientWidth - CodeEditor.LINE_GUTTER_WIDTH ) / this.charWidth );
|
|
3746
3771
|
const maxLineLength = this._lastMaxLineLength;
|
|
3747
3772
|
|
|
3748
|
-
if( numViewportChars
|
|
3773
|
+
if( numViewportChars >= maxLineLength )
|
|
3749
3774
|
{
|
|
3750
3775
|
this.codeScroller.classList.remove( 'with-hscrollbar' );
|
|
3751
3776
|
this.hScrollbar.root.classList.add( 'scrollbar-unused' );
|
|
@@ -4032,7 +4057,7 @@ class CodeEditor {
|
|
|
4032
4057
|
// Show box
|
|
4033
4058
|
this.autocomplete.classList.toggle('show', true);
|
|
4034
4059
|
this.autocomplete.classList.toggle('no-scrollbar', !(this.autocomplete.scrollHeight > this.autocomplete.offsetHeight));
|
|
4035
|
-
this.autocomplete.style.left = (cursor._left +
|
|
4060
|
+
this.autocomplete.style.left = (cursor._left + CodeEditor.LINE_GUTTER_WIDTH - this.getScrollLeft()) + "px";
|
|
4036
4061
|
this.autocomplete.style.top = (cursor._top + 28 + this.lineHeight - this.getScrollTop()) + "px";
|
|
4037
4062
|
|
|
4038
4063
|
this.isAutoCompleteActive = true;
|
|
@@ -4040,6 +4065,11 @@ class CodeEditor {
|
|
|
4040
4065
|
|
|
4041
4066
|
hideAutoCompleteBox() {
|
|
4042
4067
|
|
|
4068
|
+
if( !this.autocomplete )
|
|
4069
|
+
{
|
|
4070
|
+
return;
|
|
4071
|
+
}
|
|
4072
|
+
|
|
4043
4073
|
const isActive = this.isAutoCompleteActive;
|
|
4044
4074
|
this.isAutoCompleteActive = false;
|
|
4045
4075
|
this.autocomplete.classList.remove( 'show' );
|