lexgui 0.1.16 → 0.1.18
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 +919 -727
- package/build/lexgui.css +65 -51
- package/build/lexgui.js +14 -2
- package/build/lexgui.module.js +14 -2
- package/demo.js +1 -1
- package/examples/code_editor.html +0 -12
- package/package.json +1 -1
|
@@ -13,23 +13,23 @@ function flushCss(element) {
|
|
|
13
13
|
element.offsetHeight;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
function swapElements
|
|
16
|
+
function swapElements( obj, a, b ) {
|
|
17
17
|
[obj[a], obj[b]] = [obj[b], obj[a]];
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
function swapArrayElements
|
|
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
|
|
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 =
|
|
80
|
-
domEl.style.top =
|
|
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(" +
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
area.
|
|
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,141 +208,134 @@ 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 = "
|
|
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 = " ";
|
|
191
229
|
cursor._left = 0;
|
|
192
230
|
cursor.style.left = this.xPadding;
|
|
193
|
-
cursor._top =
|
|
194
|
-
cursor.style.top = "
|
|
231
|
+
cursor._top = 0;
|
|
232
|
+
cursor.style.top = cursor._top + "px";
|
|
195
233
|
cursor.position = 0;
|
|
196
|
-
cursor.
|
|
197
|
-
cursor.print = (function() { console.log( this.line, this.position ) }).bind(cursor);
|
|
198
|
-
|
|
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
|
-
//
|
|
245
|
+
// Scroll stuff
|
|
202
246
|
{
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
this.
|
|
206
|
-
|
|
247
|
+
this.codeScroller = this.tabs.area.root;
|
|
248
|
+
this.firstLineInViewport = 0;
|
|
249
|
+
this.lineScrollMargin = new LX.vec2( 20, 20 ); // [ mUp, mDown ]
|
|
250
|
+
window.scroller = this.codeScroller;
|
|
207
251
|
|
|
208
|
-
|
|
209
|
-
this.
|
|
210
|
-
this.scrollbarThumb._top = 0;
|
|
211
|
-
scrollbar.appendChild(scrollbarThumb);
|
|
252
|
+
let lastScrollTopValue = -1;
|
|
253
|
+
this.codeScroller.addEventListener( 'scroll', e => {
|
|
212
254
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
228
|
-
{
|
|
229
|
-
var dt = (last_pos - e.y);
|
|
230
|
-
|
|
231
|
-
that.applyVerticalScrollFromScrollBar( that.scrollbarThumb._top - dt )
|
|
261
|
+
this.setScrollBarValue( 'vertical' );
|
|
232
262
|
|
|
233
|
-
|
|
234
|
-
e.stopPropagation();
|
|
235
|
-
e.preventDefault();
|
|
236
|
-
}
|
|
263
|
+
const scrollTop = this.getScrollTop();
|
|
237
264
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
265
|
+
// Scroll down...
|
|
266
|
+
if( scrollTop > lastScrollTopValue )
|
|
267
|
+
{
|
|
268
|
+
if( this.visibleLinesViewport.y < (this.code.lines.length - 1) )
|
|
269
|
+
{
|
|
270
|
+
const totalLinesInViewport = ((this.codeScroller.offsetHeight - 36) / this.lineHeight)|0;
|
|
271
|
+
const scrollDownBoundary =
|
|
272
|
+
( Math.max( this.visibleLinesViewport.y - totalLinesInViewport, 0 ) - 1 ) * this.lineHeight;
|
|
273
|
+
|
|
274
|
+
if( scrollTop >= scrollDownBoundary )
|
|
275
|
+
this.processLines( CodeEditor.UPDATE_VISIBLE_LINES );
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
// Scroll up...
|
|
279
|
+
else
|
|
280
|
+
{
|
|
281
|
+
const scrollUpBoundary = parseInt( this.code.style.top );
|
|
282
|
+
if( scrollTop < scrollUpBoundary )
|
|
283
|
+
this.processLines( CodeEditor.UPDATE_VISIBLE_LINES );
|
|
284
|
+
}
|
|
252
285
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
this.hScrollbarThumb._left = 0;
|
|
256
|
-
hScrollbar.appendChild(hScrollbarThumb);
|
|
286
|
+
lastScrollTopValue = scrollTop;
|
|
287
|
+
});
|
|
257
288
|
|
|
258
|
-
this.
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
}
|
|
289
|
+
this.codeScroller.addEventListener( 'wheel', e => {
|
|
290
|
+
const dX = ( e.deltaY > 0.0 ? 10.0 : -10.0 ) * ( e.shiftKey ? 1.0 : 0.0 );
|
|
291
|
+
if( dX != 0.0 ) this.setScrollBarValue( 'horizontal', dX );
|
|
292
|
+
});
|
|
293
|
+
}
|
|
271
294
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
295
|
+
// This is only the container, line numbers are in the same line div
|
|
296
|
+
{
|
|
297
|
+
this.gutter = document.createElement( 'div' );
|
|
298
|
+
this.gutter.className = "lexcodegutter";
|
|
299
|
+
area.attach( this.gutter );
|
|
300
|
+
}
|
|
277
301
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
302
|
+
// Add custom vertical scroll bar
|
|
303
|
+
{
|
|
304
|
+
this.vScrollbar = new ScrollBar( this, ScrollBar.SCROLLBAR_VERTICAL );
|
|
305
|
+
area.attach( this.vScrollbar.root );
|
|
306
|
+
}
|
|
282
307
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
doc.removeEventListener("mouseup", inner_mouseup);
|
|
288
|
-
}
|
|
308
|
+
// Add custom horizontal scroll bar
|
|
309
|
+
{
|
|
310
|
+
this.hScrollbar = new ScrollBar( this, ScrollBar.SCROLLBAR_HORIZONTAL );
|
|
311
|
+
area.attach( this.hScrollbar.root );
|
|
289
312
|
}
|
|
290
313
|
|
|
291
314
|
// Add autocomplete box
|
|
292
315
|
{
|
|
293
|
-
var box = document.createElement('div');
|
|
316
|
+
var box = document.createElement( 'div' );
|
|
294
317
|
box.className = "autocomplete";
|
|
295
318
|
this.autocomplete = box;
|
|
296
|
-
this.tabs.area.attach(box);
|
|
319
|
+
this.tabs.area.attach( box );
|
|
297
320
|
|
|
298
321
|
this.isAutoCompleteActive = false;
|
|
299
322
|
}
|
|
300
323
|
|
|
324
|
+
// Add code-sizer
|
|
325
|
+
{
|
|
326
|
+
this.codeSizer = document.createElement( 'div' );
|
|
327
|
+
this.codeSizer.className = "code-sizer";
|
|
328
|
+
|
|
329
|
+
// Append all childs
|
|
330
|
+
while( this.codeScroller.firstChild )
|
|
331
|
+
this.codeSizer.appendChild( this.codeScroller.firstChild );
|
|
332
|
+
|
|
333
|
+
this.codeScroller.appendChild( this.codeSizer );
|
|
334
|
+
}
|
|
335
|
+
|
|
301
336
|
// State
|
|
302
337
|
|
|
303
338
|
this.state = {
|
|
304
|
-
overwrite: false,
|
|
305
339
|
focused: false,
|
|
306
340
|
selectingText: false
|
|
307
341
|
}
|
|
@@ -335,7 +369,7 @@ class CodeEditor {
|
|
|
335
369
|
};
|
|
336
370
|
|
|
337
371
|
// Scan tokens..
|
|
338
|
-
setInterval( this.scanWordSuggestions.bind(this), 2000 );
|
|
372
|
+
// setInterval( this.scanWordSuggestions.bind( this ), 2000 );
|
|
339
373
|
|
|
340
374
|
this.languages = {
|
|
341
375
|
'Plain Text': { },
|
|
@@ -346,7 +380,8 @@ class CodeEditor {
|
|
|
346
380
|
'WGSL': { },
|
|
347
381
|
'JSON': { },
|
|
348
382
|
'XML': { },
|
|
349
|
-
'Python': { },
|
|
383
|
+
'Python': { singleLineCommentToken: '#' },
|
|
384
|
+
'HTML': { },
|
|
350
385
|
'Batch': { blockComments: false, singleLineCommentToken: '::' }
|
|
351
386
|
};
|
|
352
387
|
|
|
@@ -370,7 +405,8 @@ class CodeEditor {
|
|
|
370
405
|
'texture_storage_2d_array', 'texture_storage_3d'],
|
|
371
406
|
'Python': ['False', 'def', 'None', 'True', 'in', 'is', 'and', 'lambda', 'nonlocal', 'not', 'or'],
|
|
372
407
|
'Batch': ['set', 'SET', 'echo', 'ECHO', 'off', 'OFF', 'del', 'DEL', 'defined', 'DEFINED', 'setlocal', 'SETLOCAL', 'enabledelayedexpansion', 'ENABLEDELAYEDEXPANSION', 'driverquery',
|
|
373
|
-
'DRIVERQUERY', 'print', 'PRINT']
|
|
408
|
+
'DRIVERQUERY', 'print', 'PRINT'],
|
|
409
|
+
'HTML': ['html', 'meta', 'title', 'link', 'script', 'body', 'DOCTYPE', 'head'],
|
|
374
410
|
};
|
|
375
411
|
this.utils = { // These ones don't have hightlight, used as suggestions to autocomplete only...
|
|
376
412
|
'JavaScript': ['querySelector', 'body', 'addEventListener', 'removeEventListener', 'remove', 'sort', 'keys', 'filter', 'isNaN', 'parseFloat', 'parseInt', 'EPSILON', 'isFinite',
|
|
@@ -387,13 +423,14 @@ class CodeEditor {
|
|
|
387
423
|
'Python': ['int', 'type', 'float', 'map', 'list', 'ArithmeticError', 'AssertionError', 'AttributeError', 'Exception', 'EOFError', 'FloatingPointError', 'GeneratorExit',
|
|
388
424
|
'ImportError', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'NotImplementedError', 'OSError',
|
|
389
425
|
'OverflowError', 'ReferenceError', 'RuntimeError', 'StopIteration', 'SyntaxError', 'TabError', 'SystemError', 'SystemExit', 'TypeError', 'UnboundLocalError',
|
|
390
|
-
'UnicodeError', 'UnicodeEncodeError', 'UnicodeDecodeError', 'UnicodeTranslateError', 'ValueError', 'ZeroDivisionError'
|
|
426
|
+
'UnicodeError', 'UnicodeEncodeError', 'UnicodeDecodeError', 'UnicodeTranslateError', 'ValueError', 'ZeroDivisionError'],
|
|
391
427
|
'C++': ['uint8_t', 'uint16_t', 'uint32_t']
|
|
392
428
|
};
|
|
393
429
|
this.builtin = {
|
|
394
430
|
'JavaScript': ['document', 'console', 'window', 'navigator', 'performance'],
|
|
395
431
|
'CSS': ['*', '!important'],
|
|
396
|
-
'C++': ['vector', 'list', 'map']
|
|
432
|
+
'C++': ['vector', 'list', 'map'],
|
|
433
|
+
'HTML': ['type', 'xmlns', 'PUBLIC', 'http-equiv', 'src', 'lang', 'href', 'rel', 'content', 'xml'], // attributes
|
|
397
434
|
};
|
|
398
435
|
this.statementsAndDeclarations = {
|
|
399
436
|
'JavaScript': ['for', 'if', 'else', 'case', 'switch', 'return', 'while', 'continue', 'break', 'do', 'import', 'from', 'throw', 'async', 'try', 'catch', 'await'],
|
|
@@ -413,6 +450,7 @@ class CodeEditor {
|
|
|
413
450
|
'CSS': ['{', '}', '(', ')', '*'],
|
|
414
451
|
'Python': ['<', '>', '[', ']', '(', ')', '='],
|
|
415
452
|
'Batch': ['[', ']', '(', ')', '%'],
|
|
453
|
+
'HTML': ['<', '>', '/']
|
|
416
454
|
};
|
|
417
455
|
|
|
418
456
|
// Convert reserved word arrays to maps so we can search tokens faster
|
|
@@ -426,64 +464,67 @@ class CodeEditor {
|
|
|
426
464
|
|
|
427
465
|
// Action keys
|
|
428
466
|
|
|
429
|
-
this.action('Escape', false, ( ln, cursor, e ) => {
|
|
467
|
+
this.action( 'Escape', false, ( ln, cursor, e ) => {
|
|
430
468
|
this.hideAutoCompleteBox();
|
|
431
469
|
});
|
|
432
470
|
|
|
433
|
-
this.action('Backspace', false, ( ln, cursor, e ) => {
|
|
471
|
+
this.action( 'Backspace', false, ( ln, cursor, e ) => {
|
|
434
472
|
|
|
435
|
-
this._addUndoStep(cursor);
|
|
473
|
+
this._addUndoStep( cursor );
|
|
436
474
|
|
|
437
|
-
if(this.selection) {
|
|
438
|
-
this.deleteSelection(cursor);
|
|
475
|
+
if( this.selection ) {
|
|
476
|
+
this.deleteSelection( cursor );
|
|
439
477
|
// Remove entire line when selecting with triple click
|
|
440
|
-
if(this.code.lines[ln] && !this.code.lines[ln].length)
|
|
441
|
-
|
|
478
|
+
if(this.code.lines[ ln ] != undefined && !this.code.lines[ ln ].length)
|
|
479
|
+
{
|
|
480
|
+
this.actions['Backspace'].callback( ln, cursor, e );
|
|
481
|
+
this.lineDown( cursor, true );
|
|
482
|
+
}
|
|
442
483
|
}
|
|
443
484
|
else {
|
|
444
485
|
var letter = this.getCharAtPos( cursor, -1 );
|
|
445
|
-
if(letter) {
|
|
446
|
-
this.code.lines[ln] = sliceChar( this.code.lines[ln], cursor.position - 1 );
|
|
486
|
+
if( letter ) {
|
|
487
|
+
this.code.lines[ ln ] = sliceChar( this.code.lines[ ln ], cursor.position - 1 );
|
|
447
488
|
this.cursorToLeft( letter );
|
|
448
|
-
this.processLine(ln);
|
|
489
|
+
this.processLine( ln );
|
|
449
490
|
if( this.useAutoComplete )
|
|
450
491
|
this.showAutoCompleteBox( 'foo', cursor );
|
|
451
492
|
}
|
|
452
|
-
else if(this.code.lines[ln - 1] != undefined) {
|
|
493
|
+
else if( this.code.lines[ ln - 1 ] != undefined ) {
|
|
453
494
|
this.lineUp();
|
|
454
|
-
this.actions['End'].callback(cursor.line, cursor, e);
|
|
495
|
+
this.actions[ 'End' ].callback( cursor.line, cursor, e );
|
|
455
496
|
// Move line on top
|
|
456
|
-
this.code.lines[ln - 1] += this.code.lines[ln];
|
|
457
|
-
this.code.lines.splice(ln, 1);
|
|
458
|
-
this.processLines(
|
|
497
|
+
this.code.lines[ ln - 1 ] += this.code.lines[ ln ];
|
|
498
|
+
this.code.lines.splice( ln, 1 );
|
|
499
|
+
this.processLines();
|
|
459
500
|
}
|
|
460
501
|
}
|
|
461
502
|
});
|
|
462
503
|
|
|
463
|
-
this.action('Delete', false, ( ln, cursor, e ) => {
|
|
504
|
+
this.action( 'Delete', false, ( ln, cursor, e ) => {
|
|
464
505
|
|
|
465
506
|
this._addUndoStep( cursor );
|
|
466
507
|
|
|
467
508
|
if(this.selection) {
|
|
468
509
|
// Use 'Backspace' as it's the same callback...
|
|
469
|
-
this.actions['Backspace'].callback(ln, cursor, e);
|
|
510
|
+
this.actions['Backspace'].callback( ln, cursor, e );
|
|
470
511
|
}
|
|
471
512
|
else
|
|
472
513
|
{
|
|
473
514
|
var letter = this.getCharAtPos( cursor );
|
|
474
|
-
if(letter) {
|
|
475
|
-
this.code.lines[ln] = sliceChar( this.code.lines[ln], cursor.position );
|
|
476
|
-
this.processLine(ln);
|
|
515
|
+
if( letter ) {
|
|
516
|
+
this.code.lines[ ln ] = sliceChar( this.code.lines[ ln ], cursor.position );
|
|
517
|
+
this.processLine( ln );
|
|
477
518
|
}
|
|
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(
|
|
519
|
+
else if(this.code.lines[ ln + 1 ] != undefined) {
|
|
520
|
+
this.code.lines[ ln ] += this.code.lines[ ln + 1 ];
|
|
521
|
+
this.code.lines.splice( ln + 1, 1 );
|
|
522
|
+
this.processLines();
|
|
482
523
|
}
|
|
483
524
|
}
|
|
484
525
|
});
|
|
485
526
|
|
|
486
|
-
this.action('Tab', true, ( ln, cursor, e ) => {
|
|
527
|
+
this.action( 'Tab', true, ( ln, cursor, e ) => {
|
|
487
528
|
|
|
488
529
|
if( this.isAutoCompleteActive )
|
|
489
530
|
{
|
|
@@ -494,53 +535,66 @@ class CodeEditor {
|
|
|
494
535
|
}
|
|
495
536
|
});
|
|
496
537
|
|
|
497
|
-
this.action('Home', false, ( ln, cursor, e ) => {
|
|
538
|
+
this.action( 'Home', false, ( ln, cursor, e ) => {
|
|
498
539
|
|
|
499
|
-
let idx = firstNonspaceIndex(this.code.lines[ln]);
|
|
540
|
+
let idx = firstNonspaceIndex( this.code.lines[ ln ] );
|
|
500
541
|
|
|
501
542
|
// We already are in the first non space index...
|
|
502
543
|
if(idx == cursor.position) idx = 0;
|
|
503
544
|
|
|
504
|
-
const prestring = this.code.lines[ln].substring(0, idx);
|
|
505
|
-
let
|
|
545
|
+
const prestring = this.code.lines[ ln ].substring( 0, idx );
|
|
546
|
+
let lastX = cursor.position;
|
|
506
547
|
|
|
507
548
|
this.resetCursorPos( CodeEditor.CURSOR_LEFT );
|
|
508
|
-
if(idx > 0) this.cursorToString(cursor, prestring);
|
|
509
|
-
this._refreshCodeInfo(cursor.line, cursor.position);
|
|
549
|
+
if(idx > 0) this.cursorToString( cursor, prestring );
|
|
550
|
+
this._refreshCodeInfo( cursor.line, cursor.position );
|
|
510
551
|
this.setScrollLeft( 0 );
|
|
511
552
|
|
|
512
553
|
if( e.shiftKey && !e.cancelShift )
|
|
513
554
|
{
|
|
514
555
|
// Get last selection range
|
|
515
|
-
if(this.selection)
|
|
516
|
-
|
|
556
|
+
if( this.selection )
|
|
557
|
+
lastX += this.selection.chars;
|
|
517
558
|
|
|
518
|
-
this.
|
|
519
|
-
|
|
520
|
-
this.
|
|
559
|
+
if( !this.selection )
|
|
560
|
+
this.startSelection( cursor );
|
|
561
|
+
var string = this.code.lines[ ln ].substring( idx, lastX );
|
|
562
|
+
if( this.selection.sameLine() )
|
|
563
|
+
this.selection.selectInline( idx, cursor.line, this.measureString( string ) );
|
|
564
|
+
else
|
|
565
|
+
{
|
|
566
|
+
this.processSelection();
|
|
567
|
+
}
|
|
521
568
|
} else if( !e.keepSelection )
|
|
522
569
|
this.endSelection();
|
|
523
570
|
});
|
|
524
571
|
|
|
525
|
-
this.action('End', false, ( ln, cursor, e ) => {
|
|
572
|
+
this.action( 'End', false, ( ln, cursor, e ) => {
|
|
526
573
|
|
|
527
574
|
if( e.shiftKey || e._shiftKey ) {
|
|
528
575
|
|
|
529
|
-
var string = this.code.lines[ln].substring(cursor.position);
|
|
530
|
-
if(!this.selection)
|
|
531
|
-
this.startSelection(cursor);
|
|
532
|
-
this.selection.
|
|
576
|
+
var string = this.code.lines[ ln ].substring( cursor.position );
|
|
577
|
+
if( !this.selection )
|
|
578
|
+
this.startSelection( cursor );
|
|
579
|
+
if( this.selection.sameLine() )
|
|
580
|
+
this.selection.selectInline(cursor.position, cursor.line, this.measureString( string ));
|
|
581
|
+
else
|
|
582
|
+
{
|
|
583
|
+
this.resetCursorPos( CodeEditor.CURSOR_LEFT );
|
|
584
|
+
this.cursorToString( cursor, this.code.lines[ ln ] );
|
|
585
|
+
this.processSelection();
|
|
586
|
+
}
|
|
533
587
|
} else
|
|
534
588
|
this.endSelection();
|
|
535
589
|
|
|
536
590
|
this.resetCursorPos( CodeEditor.CURSOR_LEFT );
|
|
537
|
-
this.cursorToString( cursor, this.code.lines[ln] );
|
|
591
|
+
this.cursorToString( cursor, this.code.lines[ ln ] );
|
|
538
592
|
|
|
539
|
-
const last_char = (this.code.clientWidth / this.charWidth)|0;
|
|
540
|
-
this.setScrollLeft( cursor.position >= last_char ? (cursor.position - last_char) * this.charWidth : 0 );
|
|
593
|
+
const last_char = ( this.code.clientWidth / this.charWidth )|0;
|
|
594
|
+
this.setScrollLeft( cursor.position >= last_char ? ( cursor.position - last_char ) * this.charWidth : 0 );
|
|
541
595
|
});
|
|
542
596
|
|
|
543
|
-
this.action('Enter', true, ( ln, cursor, e ) => {
|
|
597
|
+
this.action( 'Enter', true, ( ln, cursor, e ) => {
|
|
544
598
|
|
|
545
599
|
// Add word
|
|
546
600
|
if( this.isAutoCompleteActive )
|
|
@@ -549,63 +603,63 @@ class CodeEditor {
|
|
|
549
603
|
return;
|
|
550
604
|
}
|
|
551
605
|
|
|
552
|
-
if(e.ctrlKey)
|
|
606
|
+
if( e.ctrlKey )
|
|
553
607
|
{
|
|
554
608
|
this.onrun( this.getText() );
|
|
555
609
|
return;
|
|
556
610
|
}
|
|
557
611
|
|
|
558
|
-
this._addUndoStep(cursor);
|
|
612
|
+
this._addUndoStep( cursor );
|
|
559
613
|
|
|
560
614
|
var _c0 = this.getCharAtPos( cursor, -1 );
|
|
561
615
|
var _c1 = this.getCharAtPos( cursor );
|
|
562
616
|
|
|
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);
|
|
617
|
+
this.code.lines.splice( cursor.line + 1, 0, "" );
|
|
618
|
+
this.code.lines[cursor.line + 1] = this.code.lines[ ln ].substr( cursor.position ); // new line (below)
|
|
619
|
+
this.code.lines[ ln ] = this.code.lines[ ln ].substr( 0, cursor.position ); // line above
|
|
620
|
+
this.lineDown( cursor, true );
|
|
567
621
|
|
|
568
622
|
// Check indentation
|
|
569
|
-
var spaces = firstNonspaceIndex(this.code.lines[ln]);
|
|
623
|
+
var spaces = firstNonspaceIndex( this.code.lines[ ln ]);
|
|
570
624
|
var tabs = Math.floor( spaces / this.tabSpaces );
|
|
571
625
|
|
|
572
626
|
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];
|
|
627
|
+
this.code.lines.splice( cursor.line, 0, "" );
|
|
628
|
+
this.addSpaceTabs( tabs + 1 );
|
|
629
|
+
this.code.lines[ cursor.line + 1 ] = " ".repeat(spaces) + this.code.lines[ cursor.line + 1 ];
|
|
576
630
|
} else {
|
|
577
|
-
this.addSpaceTabs(tabs);
|
|
631
|
+
this.addSpaceTabs( tabs );
|
|
578
632
|
}
|
|
579
633
|
|
|
580
|
-
this.processLines(
|
|
634
|
+
this.processLines();
|
|
581
635
|
});
|
|
582
636
|
|
|
583
|
-
this.action('ArrowUp', false, ( ln, cursor, e ) => {
|
|
637
|
+
this.action( 'ArrowUp', false, ( ln, cursor, e ) => {
|
|
584
638
|
|
|
585
639
|
// Move cursor..
|
|
586
640
|
if( !this.isAutoCompleteActive )
|
|
587
641
|
{
|
|
588
642
|
if( e.shiftKey ) {
|
|
589
|
-
if(!this.selection)
|
|
590
|
-
this.startSelection(cursor);
|
|
643
|
+
if( !this.selection )
|
|
644
|
+
this.startSelection( cursor );
|
|
591
645
|
|
|
592
|
-
this.selection.toY = (this.selection.toY > 0) ? (this.selection.toY - 1) : 0;
|
|
593
|
-
this.cursorToLine(cursor, this.selection.toY);
|
|
646
|
+
this.selection.toY = ( this.selection.toY > 0 ) ? ( this.selection.toY - 1 ) : 0;
|
|
647
|
+
this.cursorToLine( cursor, this.selection.toY );
|
|
594
648
|
|
|
595
649
|
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);
|
|
650
|
+
if( !letter ) {
|
|
651
|
+
this.selection.toX = this.code.lines[ cursor.line ].length;
|
|
652
|
+
this.cursorToPosition( cursor, this.selection.toX );
|
|
599
653
|
}
|
|
600
654
|
|
|
601
|
-
this.processSelection(null, true);
|
|
655
|
+
this.processSelection( null, true );
|
|
602
656
|
|
|
603
657
|
} else {
|
|
604
658
|
this.endSelection();
|
|
605
659
|
this.lineUp();
|
|
606
660
|
// Go to end of line if out of line
|
|
607
661
|
var letter = this.getCharAtPos( cursor );
|
|
608
|
-
if(!letter) this.actions['End'].callback(cursor.line, cursor, e);
|
|
662
|
+
if( !letter ) this.actions['End'].callback( cursor.line, cursor, e );
|
|
609
663
|
}
|
|
610
664
|
}
|
|
611
665
|
// Move up autocomplete selection
|
|
@@ -615,34 +669,34 @@ class CodeEditor {
|
|
|
615
669
|
}
|
|
616
670
|
});
|
|
617
671
|
|
|
618
|
-
this.action('ArrowDown', false, ( ln, cursor, e ) => {
|
|
672
|
+
this.action( 'ArrowDown', false, ( ln, cursor, e ) => {
|
|
619
673
|
|
|
620
674
|
// Move cursor..
|
|
621
675
|
if( !this.isAutoCompleteActive )
|
|
622
676
|
{
|
|
623
677
|
if( e.shiftKey ) {
|
|
624
|
-
if(!this.selection)
|
|
625
|
-
this.startSelection(cursor);
|
|
678
|
+
if( !this.selection )
|
|
679
|
+
this.startSelection( cursor );
|
|
626
680
|
|
|
627
681
|
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);
|
|
682
|
+
this.cursorToLine( cursor, this.selection.toY );
|
|
629
683
|
|
|
630
684
|
var letter = this.getCharAtPos( cursor );
|
|
631
|
-
if(!letter) {
|
|
632
|
-
this.selection.toX = Math.max(this.code.lines[cursor.line].length - 1, 0);
|
|
685
|
+
if( !letter ) {
|
|
686
|
+
this.selection.toX = Math.max(this.code.lines[ cursor.line ].length - 1, 0);
|
|
633
687
|
this.cursorToPosition(cursor, this.selection.toX);
|
|
634
688
|
}
|
|
635
689
|
|
|
636
|
-
this.processSelection(null, true);
|
|
690
|
+
this.processSelection( null, true );
|
|
637
691
|
} else {
|
|
638
692
|
|
|
639
693
|
if( this.code.lines[ ln + 1 ] == undefined )
|
|
640
694
|
return;
|
|
641
695
|
this.endSelection();
|
|
642
|
-
this.lineDown();
|
|
696
|
+
this.lineDown( cursor );
|
|
643
697
|
// Go to end of line if out of line
|
|
644
698
|
var letter = this.getCharAtPos( cursor );
|
|
645
|
-
if(!letter) this.actions['End'].callback(cursor.line, cursor, e);
|
|
699
|
+
if( !letter ) this.actions['End'].callback(cursor.line, cursor, e);
|
|
646
700
|
}
|
|
647
701
|
}
|
|
648
702
|
// Move down autocomplete selection
|
|
@@ -652,40 +706,41 @@ class CodeEditor {
|
|
|
652
706
|
}
|
|
653
707
|
});
|
|
654
708
|
|
|
655
|
-
this.action('ArrowLeft', false, ( ln, cursor, e ) => {
|
|
709
|
+
this.action( 'ArrowLeft', false, ( ln, cursor, e ) => {
|
|
656
710
|
|
|
657
|
-
if(e.metaKey) { // Apple devices (Command)
|
|
711
|
+
if( e.metaKey ) { // Apple devices (Command)
|
|
658
712
|
e.preventDefault();
|
|
659
713
|
this.actions[ 'Home' ].callback( ln, cursor, e );
|
|
660
714
|
}
|
|
661
|
-
else if(e.ctrlKey) {
|
|
715
|
+
else if( e.ctrlKey ) {
|
|
662
716
|
// Get next word
|
|
663
717
|
const [word, from, to] = this.getWordAtPos( cursor, -1 );
|
|
664
718
|
var diff = Math.max(cursor.position - from, 1);
|
|
665
719
|
var substr = word.substr(0, diff);
|
|
666
720
|
// Selections...
|
|
667
|
-
if( e.shiftKey ) if(!this.selection) this.startSelection(cursor);
|
|
721
|
+
if( e.shiftKey ) { if( !this.selection ) this.startSelection( cursor ); }
|
|
722
|
+
else this.endSelection();
|
|
668
723
|
this.cursorToString(cursor, substr, true);
|
|
669
724
|
if( e.shiftKey ) this.processSelection();
|
|
670
725
|
}
|
|
671
726
|
else {
|
|
672
727
|
var letter = this.getCharAtPos( cursor, -1 );
|
|
673
|
-
if(letter) {
|
|
728
|
+
if( letter ) {
|
|
674
729
|
if( e.shiftKey ) {
|
|
675
|
-
if(!this.selection) this.startSelection(cursor);
|
|
676
|
-
if( ((cursor.position - 1) < this.selection.fromX) && this.selection.sameLine() )
|
|
730
|
+
if( !this.selection ) this.startSelection( cursor );
|
|
731
|
+
if( ( ( cursor.position - 1 ) < this.selection.fromX ) && this.selection.sameLine() )
|
|
677
732
|
this.selection.fromX--;
|
|
678
|
-
else if( (cursor.position - 1) == this.selection.fromX && this.selection.sameLine() ) {
|
|
733
|
+
else if( ( cursor.position - 1 ) == this.selection.fromX && this.selection.sameLine() ) {
|
|
679
734
|
this.cursorToLeft( letter, cursor );
|
|
680
735
|
this.endSelection();
|
|
681
736
|
return;
|
|
682
737
|
}
|
|
683
738
|
else this.selection.toX--;
|
|
684
739
|
this.cursorToLeft( letter, cursor );
|
|
685
|
-
this.processSelection(null, true);
|
|
740
|
+
this.processSelection( null, true );
|
|
686
741
|
}
|
|
687
742
|
else {
|
|
688
|
-
if(!this.selection) {
|
|
743
|
+
if( !this.selection ) {
|
|
689
744
|
this.cursorToLeft( letter, cursor );
|
|
690
745
|
if( this.useAutoComplete && this.isAutoCompleteActive )
|
|
691
746
|
this.showAutoCompleteBox( 'foo', cursor );
|
|
@@ -693,8 +748,8 @@ class CodeEditor {
|
|
|
693
748
|
else {
|
|
694
749
|
this.selection.invertIfNecessary();
|
|
695
750
|
this.resetCursorPos( CodeEditor.CURSOR_LEFT | CodeEditor.CURSOR_TOP );
|
|
696
|
-
this.cursorToLine(cursor, this.selection.fromY, true);
|
|
697
|
-
this.cursorToPosition(cursor, this.selection.fromX);
|
|
751
|
+
this.cursorToLine( cursor, this.selection.fromY, true );
|
|
752
|
+
this.cursorToPosition( cursor, this.selection.fromX );
|
|
698
753
|
this.endSelection();
|
|
699
754
|
}
|
|
700
755
|
}
|
|
@@ -702,7 +757,7 @@ class CodeEditor {
|
|
|
702
757
|
else if( cursor.line > 0 ) {
|
|
703
758
|
|
|
704
759
|
if( e.shiftKey ) {
|
|
705
|
-
if(!this.selection) this.startSelection(cursor);
|
|
760
|
+
if( !this.selection ) this.startSelection( cursor );
|
|
706
761
|
}
|
|
707
762
|
|
|
708
763
|
this.lineUp( cursor );
|
|
@@ -711,34 +766,35 @@ class CodeEditor {
|
|
|
711
766
|
if( e.shiftKey ) {
|
|
712
767
|
this.selection.toX = cursor.position;
|
|
713
768
|
this.selection.toY--;
|
|
714
|
-
this.processSelection(null, true);
|
|
769
|
+
this.processSelection( null, true );
|
|
715
770
|
}
|
|
716
771
|
}
|
|
717
772
|
}
|
|
718
773
|
});
|
|
719
774
|
|
|
720
|
-
this.action('ArrowRight', false, ( ln, cursor, e ) => {
|
|
775
|
+
this.action( 'ArrowRight', false, ( ln, cursor, e ) => {
|
|
721
776
|
|
|
722
|
-
if(e.metaKey) { // Apple devices (Command)
|
|
777
|
+
if( e.metaKey ) { // Apple devices (Command)
|
|
723
778
|
e.preventDefault();
|
|
724
779
|
this.actions[ 'End' ].callback( ln, cursor );
|
|
725
|
-
} else if(e.ctrlKey) {
|
|
780
|
+
} else if( e.ctrlKey ) {
|
|
726
781
|
// Get next word
|
|
727
|
-
const [word, from, to] = this.getWordAtPos( cursor );
|
|
782
|
+
const [ word, from, to ] = this.getWordAtPos( cursor );
|
|
728
783
|
var diff = cursor.position - from;
|
|
729
|
-
var substr = word.substr(diff);
|
|
784
|
+
var substr = word.substr( diff );
|
|
730
785
|
// Selections...
|
|
731
|
-
if( e.shiftKey ) if(!this.selection) this.startSelection(cursor);
|
|
732
|
-
this.
|
|
786
|
+
if( e.shiftKey ) { if( !this.selection ) this.startSelection( cursor ); }
|
|
787
|
+
else this.endSelection();
|
|
788
|
+
this.cursorToString( cursor, substr);
|
|
733
789
|
if( e.shiftKey ) this.processSelection();
|
|
734
790
|
} else {
|
|
735
791
|
var letter = this.getCharAtPos( cursor );
|
|
736
|
-
if(letter) {
|
|
792
|
+
if( letter ) {
|
|
737
793
|
if( e.shiftKey ) {
|
|
738
|
-
if(!this.selection) this.startSelection(cursor);
|
|
794
|
+
if( !this.selection ) this.startSelection( cursor );
|
|
739
795
|
var keep_range = false;
|
|
740
796
|
if( cursor.position == this.selection.fromX ) {
|
|
741
|
-
if( (cursor.position + 1) == this.selection.toX && this.selection.sameLine() ) {
|
|
797
|
+
if( ( cursor.position + 1 ) == this.selection.toX && this.selection.sameLine() ) {
|
|
742
798
|
this.cursorToRight( letter, cursor );
|
|
743
799
|
this.endSelection();
|
|
744
800
|
return;
|
|
@@ -748,9 +804,9 @@ class CodeEditor {
|
|
|
748
804
|
} else this.selection.toX++;
|
|
749
805
|
}
|
|
750
806
|
this.cursorToRight( letter, cursor );
|
|
751
|
-
this.processSelection(null, keep_range);
|
|
807
|
+
this.processSelection( null, keep_range );
|
|
752
808
|
}else{
|
|
753
|
-
if(!this.selection) {
|
|
809
|
+
if( !this.selection ) {
|
|
754
810
|
this.cursorToRight( letter, cursor );
|
|
755
811
|
if( this.useAutoComplete && this.isAutoCompleteActive )
|
|
756
812
|
this.showAutoCompleteBox( 'foo', cursor );
|
|
@@ -759,8 +815,8 @@ class CodeEditor {
|
|
|
759
815
|
{
|
|
760
816
|
this.selection.invertIfNecessary();
|
|
761
817
|
this.resetCursorPos( CodeEditor.CURSOR_LEFT | CodeEditor.CURSOR_TOP );
|
|
762
|
-
this.cursorToLine(cursor, this.selection.toY);
|
|
763
|
-
this.cursorToPosition(cursor, this.selection.toX);
|
|
818
|
+
this.cursorToLine( cursor, this.selection.toY );
|
|
819
|
+
this.cursorToPosition( cursor, this.selection.toX );
|
|
764
820
|
this.endSelection();
|
|
765
821
|
}
|
|
766
822
|
}
|
|
@@ -768,18 +824,18 @@ class CodeEditor {
|
|
|
768
824
|
else if( this.code.lines[ cursor.line + 1 ] !== undefined ) {
|
|
769
825
|
|
|
770
826
|
if( e.shiftKey ) {
|
|
771
|
-
if(!this.selection) this.startSelection(cursor);
|
|
827
|
+
if( !this.selection ) this.startSelection( cursor );
|
|
772
828
|
e.cancelShift = true;
|
|
773
829
|
e.keepSelection = true;
|
|
774
830
|
}
|
|
775
831
|
|
|
776
832
|
this.lineDown( cursor );
|
|
777
|
-
this.actions['Home'].callback(cursor.line, cursor, e);
|
|
833
|
+
this.actions['Home'].callback( cursor.line, cursor, e );
|
|
778
834
|
|
|
779
835
|
if( e.shiftKey ) {
|
|
780
836
|
this.selection.toX = cursor.position;
|
|
781
837
|
this.selection.toY++;
|
|
782
|
-
this.processSelection(null, true);
|
|
838
|
+
this.processSelection( null, true );
|
|
783
839
|
}
|
|
784
840
|
|
|
785
841
|
this.hideAutoCompleteBox();
|
|
@@ -813,14 +869,14 @@ class CodeEditor {
|
|
|
813
869
|
// This can be used to empty all text...
|
|
814
870
|
setText( text = "", lang ) {
|
|
815
871
|
|
|
816
|
-
let new_lines = text.split('\n');
|
|
817
|
-
this.code.lines = [].concat(new_lines);
|
|
872
|
+
let new_lines = text.split( '\n' );
|
|
873
|
+
this.code.lines = [].concat( new_lines );
|
|
818
874
|
|
|
819
|
-
let cursor = this.cursors.children[0];
|
|
875
|
+
let cursor = this.cursors.children[ 0 ];
|
|
820
876
|
let lastLine = new_lines.pop();
|
|
821
877
|
|
|
822
|
-
this.cursorToLine(cursor, new_lines.length); // Already substracted 1
|
|
823
|
-
this.cursorToPosition(cursor, lastLine.length);
|
|
878
|
+
this.cursorToLine( cursor, new_lines.length ); // Already substracted 1
|
|
879
|
+
this.cursorToPosition( cursor, lastLine.length );
|
|
824
880
|
this.processLines();
|
|
825
881
|
|
|
826
882
|
if( lang )
|
|
@@ -831,64 +887,64 @@ class CodeEditor {
|
|
|
831
887
|
|
|
832
888
|
appendText( text ) {
|
|
833
889
|
|
|
834
|
-
let cursor = this.cursors.children[0];
|
|
890
|
+
let cursor = this.cursors.children[ 0 ];
|
|
835
891
|
let lidx = cursor.line;
|
|
836
892
|
|
|
837
893
|
if( this.selection ) {
|
|
838
|
-
this.deleteSelection(cursor);
|
|
894
|
+
this.deleteSelection( cursor );
|
|
839
895
|
lidx = cursor.line;
|
|
840
896
|
}
|
|
841
897
|
|
|
842
898
|
this.endSelection();
|
|
843
899
|
|
|
844
|
-
const new_lines = text.split('\n');
|
|
900
|
+
const new_lines = text.split( '\n' );
|
|
845
901
|
|
|
846
902
|
// Pasting Multiline...
|
|
847
|
-
if(new_lines.length != 1)
|
|
903
|
+
if( new_lines.length != 1 )
|
|
848
904
|
{
|
|
849
905
|
let num_lines = new_lines.length;
|
|
850
|
-
console.assert(num_lines > 0);
|
|
906
|
+
console.assert( num_lines > 0 );
|
|
851
907
|
const first_line = new_lines.shift();
|
|
852
908
|
num_lines--;
|
|
853
909
|
|
|
854
|
-
const remaining = this.code.lines[lidx].slice(cursor.position);
|
|
910
|
+
const remaining = this.code.lines[ lidx ].slice( cursor.position );
|
|
855
911
|
|
|
856
912
|
// Add first line
|
|
857
|
-
this.code.lines[lidx] = [
|
|
858
|
-
this.code.lines[lidx].slice(0, cursor.position),
|
|
913
|
+
this.code.lines[ lidx ] = [
|
|
914
|
+
this.code.lines[ lidx ].slice( 0, cursor.position ),
|
|
859
915
|
first_line
|
|
860
916
|
].join('');
|
|
861
917
|
|
|
862
|
-
this.cursorToPosition(cursor, (cursor.position + first_line.length));
|
|
918
|
+
this.cursorToPosition( cursor, ( cursor.position + first_line.length ) );
|
|
863
919
|
|
|
864
920
|
// Enter next lines...
|
|
865
921
|
|
|
866
922
|
let _text = null;
|
|
867
923
|
|
|
868
924
|
for( var i = 0; i < new_lines.length; ++i ) {
|
|
869
|
-
_text = new_lines[i];
|
|
870
|
-
this.cursorToLine(cursor, cursor.line++, true);
|
|
925
|
+
_text = new_lines[ i ];
|
|
926
|
+
this.cursorToLine( cursor, cursor.line++, true );
|
|
871
927
|
// Add remaining...
|
|
872
928
|
if( i == (new_lines.length - 1) )
|
|
873
929
|
_text += remaining;
|
|
874
|
-
this.code.lines.splice( 1 + lidx + i, 0, _text);
|
|
930
|
+
this.code.lines.splice( 1 + lidx + i, 0, _text );
|
|
875
931
|
}
|
|
876
932
|
|
|
877
|
-
if(_text) this.cursorToPosition(cursor, _text.length);
|
|
878
|
-
this.cursorToLine(cursor, cursor.line + num_lines);
|
|
879
|
-
this.processLines(
|
|
933
|
+
if( _text ) this.cursorToPosition( cursor, _text.length );
|
|
934
|
+
this.cursorToLine( cursor, cursor.line + num_lines );
|
|
935
|
+
this.processLines();
|
|
880
936
|
}
|
|
881
937
|
// Pasting one line...
|
|
882
938
|
else
|
|
883
939
|
{
|
|
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)
|
|
940
|
+
this.code.lines[ lidx ] = [
|
|
941
|
+
this.code.lines[ lidx ].slice( 0, cursor.position ),
|
|
942
|
+
new_lines[ 0 ],
|
|
943
|
+
this.code.lines[ lidx ].slice( cursor.position )
|
|
888
944
|
].join('');
|
|
889
945
|
|
|
890
|
-
this.cursorToPosition(cursor, (cursor.position + new_lines[0].length));
|
|
891
|
-
this.processLine(lidx);
|
|
946
|
+
this.cursorToPosition( cursor, ( cursor.position + new_lines[ 0 ].length ) );
|
|
947
|
+
this.processLine( lidx );
|
|
892
948
|
}
|
|
893
949
|
}
|
|
894
950
|
|
|
@@ -898,18 +954,18 @@ class CodeEditor {
|
|
|
898
954
|
const existing = this.addTab(name, true, title);
|
|
899
955
|
if( !existing )
|
|
900
956
|
{
|
|
901
|
-
text = text.replaceAll('\r', '');
|
|
902
|
-
this.code.lines = text.split('\n');
|
|
903
|
-
this._changeLanguageFromExtension( LX.getExtension(name) );
|
|
957
|
+
text = text.replaceAll( '\r', '' );
|
|
958
|
+
this.code.lines = text.split( '\n' );
|
|
959
|
+
this._changeLanguageFromExtension( LX.getExtension( name ) );
|
|
904
960
|
}
|
|
905
961
|
};
|
|
906
962
|
|
|
907
|
-
if(file.constructor == String)
|
|
963
|
+
if( file.constructor == String )
|
|
908
964
|
{
|
|
909
965
|
let filename = file;
|
|
910
966
|
LX.request({ url: filename, success: text => {
|
|
911
967
|
|
|
912
|
-
const name = filename.substring(filename.lastIndexOf('/') + 1);
|
|
968
|
+
const name = filename.substring(filename.lastIndexOf( '/' ) + 1);
|
|
913
969
|
inner_add_tab( text, name, filename );
|
|
914
970
|
} });
|
|
915
971
|
}
|
|
@@ -926,18 +982,34 @@ class CodeEditor {
|
|
|
926
982
|
|
|
927
983
|
_addUndoStep( cursor ) {
|
|
928
984
|
|
|
929
|
-
|
|
985
|
+
const d = new Date();
|
|
986
|
+
const current = d.getTime();
|
|
987
|
+
|
|
988
|
+
if( !this._lastTime ) {
|
|
989
|
+
this._lastTime = current;
|
|
990
|
+
} else {
|
|
991
|
+
if( ( current - this._lastTime ) > 3000 ){
|
|
992
|
+
this._lastTime = null;
|
|
993
|
+
} else {
|
|
994
|
+
// If time not enough, reset timer
|
|
995
|
+
this._lastTime = current;
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
var cursor = cursor ?? this.cursors.children[ 0 ];
|
|
930
1001
|
|
|
931
1002
|
this.code.undoSteps.push( {
|
|
932
|
-
lines: LX.deepCopy(this.code.lines),
|
|
933
|
-
cursor: this.saveCursor(cursor),
|
|
934
|
-
line: cursor.line
|
|
1003
|
+
lines: LX.deepCopy( this.code.lines ),
|
|
1004
|
+
cursor: this.saveCursor( cursor ),
|
|
1005
|
+
line: cursor.line,
|
|
1006
|
+
position: cursor.position
|
|
935
1007
|
} );
|
|
936
1008
|
}
|
|
937
1009
|
|
|
938
1010
|
_changeLanguage( lang ) {
|
|
939
1011
|
|
|
940
|
-
this.code.
|
|
1012
|
+
this.code.language = lang;
|
|
941
1013
|
this.highlight = lang;
|
|
942
1014
|
this._refreshCodeInfo();
|
|
943
1015
|
this.processLines();
|
|
@@ -946,23 +1018,24 @@ class CodeEditor {
|
|
|
946
1018
|
_changeLanguageFromExtension( ext ) {
|
|
947
1019
|
|
|
948
1020
|
if( !ext )
|
|
949
|
-
return this._changeLanguage( this.code.
|
|
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');
|
|
1021
|
+
return this._changeLanguage( this.code.language );
|
|
1022
|
+
|
|
1023
|
+
switch( ext.toLowerCase() )
|
|
1024
|
+
{
|
|
1025
|
+
case 'js': return this._changeLanguage( 'JavaScript' );
|
|
1026
|
+
case 'cpp': return this._changeLanguage( 'C++' );
|
|
1027
|
+
case 'h': return this._changeLanguage( 'C++' );
|
|
1028
|
+
case 'glsl': return this._changeLanguage( 'GLSL' );
|
|
1029
|
+
case 'css': return this._changeLanguage( 'CSS' );
|
|
1030
|
+
case 'json': return this._changeLanguage( 'JSON' );
|
|
1031
|
+
case 'xml': return this._changeLanguage( 'XML' );
|
|
1032
|
+
case 'wgsl': return this._changeLanguage( 'WGSL' );
|
|
1033
|
+
case 'py': return this._changeLanguage( 'Python' );
|
|
1034
|
+
case 'bat': return this._changeLanguage( 'Batch' );
|
|
1035
|
+
case 'html': return this._changeLanguage( 'HTML' );
|
|
963
1036
|
case 'txt':
|
|
964
1037
|
default:
|
|
965
|
-
this._changeLanguage('Plain Text');
|
|
1038
|
+
this._changeLanguage( 'Plain Text' );
|
|
966
1039
|
}
|
|
967
1040
|
}
|
|
968
1041
|
|
|
@@ -974,15 +1047,15 @@ class CodeEditor {
|
|
|
974
1047
|
panel.ln = 0;
|
|
975
1048
|
panel.col = 0;
|
|
976
1049
|
|
|
977
|
-
this._refreshCodeInfo = (ln = panel.ln, col = panel.col) => {
|
|
1050
|
+
this._refreshCodeInfo = ( ln = panel.ln, col = panel.col ) => {
|
|
978
1051
|
panel.ln = ln + 1;
|
|
979
1052
|
panel.col = col + 1;
|
|
980
1053
|
panel.clear();
|
|
981
1054
|
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) => {
|
|
1055
|
+
panel.addLabel( this.code.title, { float: 'right' });
|
|
1056
|
+
panel.addLabel( "Ln " + panel.ln, { width: "64px" });
|
|
1057
|
+
panel.addLabel( "Col " + panel.col, { width: "64px" });
|
|
1058
|
+
panel.addButton( "<b>{ }</b>", this.highlight, ( value, event ) => {
|
|
986
1059
|
LX.addContextMenu( "Language", event, m => {
|
|
987
1060
|
for( const lang of Object.keys(this.languages) )
|
|
988
1061
|
m.add( lang, this._changeLanguage.bind(this) );
|
|
@@ -1003,8 +1076,8 @@ class CodeEditor {
|
|
|
1003
1076
|
|
|
1004
1077
|
// Change css a little bit...
|
|
1005
1078
|
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)";
|
|
1079
|
+
this.root.querySelectorAll( '.code' ).forEach( e => e.style.height = "calc(100% - 6px)" );
|
|
1080
|
+
this.root.querySelector( '.lexareatabscontent' ).style.height = "calc(100% - 23px)";
|
|
1008
1081
|
|
|
1009
1082
|
}, 100);
|
|
1010
1083
|
}
|
|
@@ -1015,102 +1088,98 @@ class CodeEditor {
|
|
|
1015
1088
|
this.processFocus(false);
|
|
1016
1089
|
|
|
1017
1090
|
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) );
|
|
1091
|
+
m.add( "Create", this.addTab.bind( this, "unnamed.js", true ) );
|
|
1092
|
+
m.add( "Load", this.loadTab.bind( this, "unnamed.js", true ) );
|
|
1020
1093
|
});
|
|
1021
1094
|
}
|
|
1022
1095
|
|
|
1023
|
-
addTab(name, selected, title) {
|
|
1096
|
+
addTab( name, selected, title ) {
|
|
1024
1097
|
|
|
1025
|
-
if(this.openedTabs[name])
|
|
1098
|
+
if(this.openedTabs[ name ])
|
|
1026
1099
|
{
|
|
1027
1100
|
this.tabs.select( this.code.tabName );
|
|
1028
1101
|
return true;
|
|
1029
1102
|
}
|
|
1030
1103
|
|
|
1031
1104
|
// Create code content
|
|
1032
|
-
let code = document.createElement('div');
|
|
1105
|
+
let code = document.createElement( 'div' );
|
|
1033
1106
|
code.className = 'code';
|
|
1034
|
-
code.lines = [""];
|
|
1035
|
-
code.
|
|
1107
|
+
code.lines = [ "" ];
|
|
1108
|
+
code.language = "Plain Text";
|
|
1036
1109
|
code.cursorState = {};
|
|
1037
1110
|
code.undoSteps = [];
|
|
1038
1111
|
code.tabName = name;
|
|
1039
1112
|
code.title = title ?? name;
|
|
1040
1113
|
code.tokens = {};
|
|
1041
|
-
code.
|
|
1114
|
+
code.style.left = "0px";
|
|
1115
|
+
code.style.top = "0px";
|
|
1042
1116
|
|
|
1043
|
-
code.addEventListener('dragenter', function(e) {
|
|
1117
|
+
code.addEventListener( 'dragenter', function(e) {
|
|
1044
1118
|
e.preventDefault();
|
|
1045
|
-
this.parentElement.classList.add('dragging');
|
|
1119
|
+
this.parentElement.classList.add( 'dragging' );
|
|
1046
1120
|
});
|
|
1047
|
-
code.addEventListener('dragleave', function(e) {
|
|
1121
|
+
code.addEventListener( 'dragleave', function(e) {
|
|
1048
1122
|
e.preventDefault();
|
|
1049
|
-
this.parentElement.remove('dragging');
|
|
1123
|
+
this.parentElement.remove( 'dragging' );
|
|
1050
1124
|
});
|
|
1051
|
-
code.addEventListener('drop',
|
|
1125
|
+
code.addEventListener( 'drop', e => {
|
|
1052
1126
|
e.preventDefault();
|
|
1053
|
-
code.parentElement.classList.remove('dragging');
|
|
1127
|
+
code.parentElement.classList.remove( 'dragging' );
|
|
1054
1128
|
for( let i = 0; i < e.dataTransfer.files.length; ++i )
|
|
1055
|
-
this.loadFile( e.dataTransfer.files[i] );
|
|
1129
|
+
this.loadFile( e.dataTransfer.files[ i ] );
|
|
1056
1130
|
});
|
|
1057
|
-
code.addEventListener('wheel', (e) => {
|
|
1058
1131
|
|
|
1059
|
-
|
|
1132
|
+
this.openedTabs[ name ] = code;
|
|
1060
1133
|
|
|
1061
|
-
|
|
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 );
|
|
1070
|
-
});
|
|
1134
|
+
const ext = LX.getExtension( name );
|
|
1071
1135
|
|
|
1072
|
-
this.
|
|
1136
|
+
this.tabs.add(name, code, {
|
|
1137
|
+
selected: selected,
|
|
1138
|
+
fixed: (name === '+') ,
|
|
1139
|
+
title: code.title,
|
|
1140
|
+
icon: ext == 'html' ? "fa-solid fa-code orange" :
|
|
1141
|
+
ext == 'js' ? "images/js.png" :
|
|
1142
|
+
ext == 'py' ? "images/py.png" : undefined,
|
|
1143
|
+
onSelect: (e, tabname) => {
|
|
1073
1144
|
|
|
1074
|
-
|
|
1145
|
+
if(tabname == '+')
|
|
1146
|
+
{
|
|
1147
|
+
this._onNewTab( e );
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1075
1150
|
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
this.
|
|
1079
|
-
|
|
1151
|
+
var cursor = cursor ?? this.cursors.children[ 0 ];
|
|
1152
|
+
this.saveCursor( cursor, this.code.cursorState );
|
|
1153
|
+
this.code = this.openedTabs[ tabname ];
|
|
1154
|
+
this.restoreCursor( cursor, this.code.cursorState );
|
|
1155
|
+
this.endSelection();
|
|
1156
|
+
this._changeLanguageFromExtension( LX.getExtension( tabname ) );
|
|
1157
|
+
this._refreshCodeInfo( cursor.line, cursor.position );
|
|
1080
1158
|
}
|
|
1159
|
+
});
|
|
1081
1160
|
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
this.code = this.openedTabs[tabname];
|
|
1085
|
-
this.restoreCursor(cursor, this.code.cursorState);
|
|
1086
|
-
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();
|
|
1093
|
-
}});
|
|
1161
|
+
// Move into the sizer..
|
|
1162
|
+
this.codeSizer.appendChild( code );
|
|
1094
1163
|
|
|
1095
1164
|
this.endSelection();
|
|
1096
1165
|
|
|
1097
1166
|
if( selected )
|
|
1098
1167
|
{
|
|
1099
1168
|
this.code = code;
|
|
1100
|
-
this.resetCursorPos(CodeEditor.CURSOR_LEFT | CodeEditor.CURSOR_TOP);
|
|
1169
|
+
this.resetCursorPos( CodeEditor.CURSOR_LEFT | CodeEditor.CURSOR_TOP );
|
|
1101
1170
|
this.processLines();
|
|
1102
|
-
doAsync( () => this._refreshCodeInfo(0, 0), 50 );
|
|
1171
|
+
doAsync( () => this._refreshCodeInfo( 0, 0 ), 50 );
|
|
1103
1172
|
}
|
|
1104
1173
|
}
|
|
1105
1174
|
|
|
1106
1175
|
loadTab() {
|
|
1107
|
-
const input = document.createElement('input');
|
|
1176
|
+
const input = document.createElement( 'input' );
|
|
1108
1177
|
input.type = 'file';
|
|
1109
|
-
document.body.appendChild(input);
|
|
1178
|
+
document.body.appendChild( input );
|
|
1110
1179
|
input.click();
|
|
1111
|
-
input.addEventListener('change',
|
|
1112
|
-
if (e.target.files[0]) {
|
|
1113
|
-
this.loadFile( e.target.files[0] );
|
|
1180
|
+
input.addEventListener('change', e => {
|
|
1181
|
+
if (e.target.files[ 0 ]) {
|
|
1182
|
+
this.loadFile( e.target.files[ 0 ] );
|
|
1114
1183
|
}
|
|
1115
1184
|
input.remove();
|
|
1116
1185
|
});
|
|
@@ -1131,7 +1200,7 @@ class CodeEditor {
|
|
|
1131
1200
|
if( !e.target.classList.contains('code') ) return;
|
|
1132
1201
|
if( !this.code ) return;
|
|
1133
1202
|
|
|
1134
|
-
var cursor = this.cursors.children[0];
|
|
1203
|
+
var cursor = this.cursors.children[ 0 ];
|
|
1135
1204
|
var code_rect = this.code.getBoundingClientRect();
|
|
1136
1205
|
var mouse_pos = [(e.clientX - code_rect.x), (e.clientY - code_rect.y)];
|
|
1137
1206
|
|
|
@@ -1139,7 +1208,7 @@ class CodeEditor {
|
|
|
1139
1208
|
if( e.type != 'contextmenu' )
|
|
1140
1209
|
{
|
|
1141
1210
|
var ln = (mouse_pos[1] / this.lineHeight)|0;
|
|
1142
|
-
if(this.code.lines[ln] == undefined) return;
|
|
1211
|
+
if(this.code.lines[ ln ] == undefined) return;
|
|
1143
1212
|
}
|
|
1144
1213
|
|
|
1145
1214
|
if( e.type == 'mousedown' )
|
|
@@ -1193,7 +1262,7 @@ class CodeEditor {
|
|
|
1193
1262
|
this.resetCursorPos( CodeEditor.CURSOR_LEFT );
|
|
1194
1263
|
this.cursorToPosition( cursor, from );
|
|
1195
1264
|
this.startSelection( cursor );
|
|
1196
|
-
this.selection.selectInline(from, cursor.line, this.measureString(word));
|
|
1265
|
+
this.selection.selectInline( from, cursor.line, this.measureString( word ) );
|
|
1197
1266
|
this.cursorToString( cursor, word ); // Go to the end of the word
|
|
1198
1267
|
break;
|
|
1199
1268
|
// Select entire line
|
|
@@ -1220,8 +1289,8 @@ class CodeEditor {
|
|
|
1220
1289
|
m.add( "Paste", () => { this._pasteContent(); } );
|
|
1221
1290
|
m.add( "" );
|
|
1222
1291
|
m.add( "Format/JSON", () => {
|
|
1223
|
-
let json = this.toJSONFormat(this.getText());
|
|
1224
|
-
this.code.lines = json.split("\n");
|
|
1292
|
+
let json = this.toJSONFormat( this.getText() );
|
|
1293
|
+
this.code.lines = json.split( "\n" );
|
|
1225
1294
|
this.processLines();
|
|
1226
1295
|
} );
|
|
1227
1296
|
}
|
|
@@ -1231,36 +1300,35 @@ class CodeEditor {
|
|
|
1231
1300
|
}
|
|
1232
1301
|
}
|
|
1233
1302
|
|
|
1234
|
-
processClick(e, skip_refresh = false) {
|
|
1303
|
+
processClick( e, skip_refresh = false ) {
|
|
1235
1304
|
|
|
1236
|
-
var
|
|
1237
|
-
var
|
|
1238
|
-
var
|
|
1305
|
+
var cursor = this.cursors.children[ 0 ];
|
|
1306
|
+
var code_rect = this.codeScroller.getBoundingClientRect();
|
|
1307
|
+
var position = [( e.clientX - code_rect.x ) + this.getScrollLeft(), (e.clientY - code_rect.y) + this.getScrollTop()];
|
|
1308
|
+
var ln = (position[ 1 ] / this.lineHeight)|0;
|
|
1239
1309
|
|
|
1240
|
-
if(this.code.lines[ln] == undefined)
|
|
1310
|
+
if( this.code.lines[ ln ] == undefined )
|
|
1311
|
+
return;
|
|
1241
1312
|
|
|
1242
|
-
|
|
1243
|
-
cursor.line = ln;
|
|
1244
|
-
|
|
1245
|
-
this.cursorToLine(cursor, ln, true);
|
|
1313
|
+
this.cursorToLine( cursor, ln, true );
|
|
1246
1314
|
|
|
1247
|
-
var ch = (position[0] / this.charWidth)|0;
|
|
1248
|
-
var string = this.code.lines[ln].slice(0, ch);
|
|
1249
|
-
this.cursorToPosition(cursor, string.length);
|
|
1315
|
+
var ch = ( ( position[ 0 ] - parseInt( this.xPadding ) + 3) / this.charWidth )|0;
|
|
1316
|
+
var string = this.code.lines[ ln ].slice( 0, ch );
|
|
1317
|
+
this.cursorToPosition( cursor, string.length );
|
|
1250
1318
|
|
|
1251
1319
|
this.hideAutoCompleteBox();
|
|
1252
1320
|
|
|
1253
|
-
if(!skip_refresh)
|
|
1321
|
+
if( !skip_refresh )
|
|
1254
1322
|
this._refreshCodeInfo( ln, cursor.position );
|
|
1255
1323
|
}
|
|
1256
1324
|
|
|
1257
1325
|
processSelection( e, keep_range ) {
|
|
1258
1326
|
|
|
1259
|
-
var cursor = this.cursors.children[0];
|
|
1327
|
+
var cursor = this.cursors.children[ 0 ];
|
|
1260
1328
|
|
|
1261
|
-
if(e) this.processClick(e, true);
|
|
1329
|
+
if( e ) this.processClick( e, true );
|
|
1262
1330
|
if( !this.selection )
|
|
1263
|
-
this.startSelection(cursor);
|
|
1331
|
+
this.startSelection( cursor );
|
|
1264
1332
|
|
|
1265
1333
|
// Update selection
|
|
1266
1334
|
if(!keep_range)
|
|
@@ -1280,20 +1348,25 @@ class CodeEditor {
|
|
|
1280
1348
|
// Selection goes down...
|
|
1281
1349
|
if( deltaY >= 0 )
|
|
1282
1350
|
{
|
|
1283
|
-
while( deltaY < (this.selections.childElementCount - 1) )
|
|
1351
|
+
while( deltaY < ( this.selections.childElementCount - 1 ) )
|
|
1284
1352
|
deleteElement( this.selections.lastChild );
|
|
1285
1353
|
|
|
1286
1354
|
for(let i = fromY; i <= toY; i++){
|
|
1287
1355
|
|
|
1288
1356
|
const sId = i - fromY;
|
|
1357
|
+
const isVisible = i >= this.visibleLinesViewport.x && i <= this.visibleLinesViewport.y;
|
|
1358
|
+
let domEl = null;
|
|
1289
1359
|
|
|
1290
|
-
|
|
1291
|
-
let domEl = this.selections.childNodes[sId];
|
|
1292
|
-
if(!domEl)
|
|
1360
|
+
if( isVisible )
|
|
1293
1361
|
{
|
|
1294
|
-
|
|
1295
|
-
domEl
|
|
1296
|
-
|
|
1362
|
+
// Make sure that the line selection is generated...
|
|
1363
|
+
domEl = this.selections.childNodes[ sId ];
|
|
1364
|
+
if(!domEl)
|
|
1365
|
+
{
|
|
1366
|
+
domEl = document.createElement( 'div' );
|
|
1367
|
+
domEl.className = "lexcodeselection";
|
|
1368
|
+
this.selections.appendChild( domEl );
|
|
1369
|
+
}
|
|
1297
1370
|
}
|
|
1298
1371
|
|
|
1299
1372
|
// Compute new width and selection margins
|
|
@@ -1302,68 +1375,80 @@ class CodeEditor {
|
|
|
1302
1375
|
if(sId == 0) // First line 2 cases (single line, multiline)
|
|
1303
1376
|
{
|
|
1304
1377
|
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 = (
|
|
1308
|
-
domEl.style.left = "calc(" + pixels + "px + " + this.xPadding + ")";
|
|
1378
|
+
if(deltaY == 0) string = !reverse ? this.code.lines[ i ].substring( fromX, toX ) : this.code.lines[ i ].substring(toX, fromX);
|
|
1379
|
+
else string = this.code.lines[ i ].substr( fromX );
|
|
1380
|
+
const pixels = (reverse && deltaY == 0 ? toX : fromX) * this.charWidth;
|
|
1381
|
+
if( isVisible ) domEl.style.left = "calc(" + pixels + "px + " + this.xPadding + ")";
|
|
1309
1382
|
}
|
|
1310
1383
|
else
|
|
1311
1384
|
{
|
|
1312
|
-
string = (i == toY) ? this.code.lines[i].substring(0, toX) : this.code.lines[i]; // Last line, any multiple line...
|
|
1313
|
-
|
|
1314
|
-
domEl.style.left = "calc(" + pixels + "px + " + this.xPadding + ")";
|
|
1385
|
+
string = (i == toY) ? this.code.lines[ i ].substring( 0, toX ) : this.code.lines[ i ]; // Last line, any multiple line...
|
|
1386
|
+
if( isVisible ) domEl.style.left = this.xPadding;
|
|
1315
1387
|
}
|
|
1316
1388
|
|
|
1317
|
-
const stringWidth = this.measureString(string);
|
|
1318
|
-
domEl.style.width = (stringWidth || 8) + "px";
|
|
1319
|
-
domEl._top = 4 + i * this.lineHeight;
|
|
1320
|
-
domEl.style.top = (domEl._top - this.getScrollTop()) + "px";
|
|
1389
|
+
const stringWidth = this.measureString( string );
|
|
1321
1390
|
this.selection.chars += stringWidth / this.charWidth;
|
|
1391
|
+
|
|
1392
|
+
if( isVisible )
|
|
1393
|
+
{
|
|
1394
|
+
domEl.style.width = (stringWidth || 8) + "px";
|
|
1395
|
+
domEl._top = i * this.lineHeight;
|
|
1396
|
+
domEl.style.top = domEl._top + "px";
|
|
1397
|
+
}
|
|
1322
1398
|
}
|
|
1323
1399
|
}
|
|
1324
1400
|
else // Selection goes up...
|
|
1325
1401
|
{
|
|
1326
|
-
while( Math.abs(deltaY) < (this.selections.childElementCount - 1) )
|
|
1402
|
+
while( Math.abs( deltaY ) < ( this.selections.childElementCount - 1 ) )
|
|
1327
1403
|
deleteElement( this.selections.firstChild );
|
|
1328
1404
|
|
|
1329
|
-
for(let i = toY; i <= fromY; i++){
|
|
1405
|
+
for( let i = toY; i <= fromY; i++ ){
|
|
1330
1406
|
|
|
1331
1407
|
const sId = i - toY;
|
|
1408
|
+
const isVisible = i >= this.visibleLinesViewport.x && i <= this.visibleLinesViewport.y;
|
|
1409
|
+
let domEl = null;
|
|
1332
1410
|
|
|
1333
|
-
|
|
1334
|
-
let domEl = this.selections.childNodes[sId];
|
|
1335
|
-
if(!domEl)
|
|
1411
|
+
if( isVisible )
|
|
1336
1412
|
{
|
|
1337
|
-
|
|
1338
|
-
domEl
|
|
1339
|
-
|
|
1413
|
+
// Make sure that the line selection is generated...
|
|
1414
|
+
domEl = this.selections.childNodes[ sId ];
|
|
1415
|
+
if(!domEl)
|
|
1416
|
+
{
|
|
1417
|
+
domEl = document.createElement( 'div' );
|
|
1418
|
+
domEl.className = "lexcodeselection";
|
|
1419
|
+
this.selections.appendChild( domEl );
|
|
1420
|
+
}
|
|
1340
1421
|
}
|
|
1341
1422
|
|
|
1342
1423
|
// Compute new width and selection margins
|
|
1343
1424
|
let string;
|
|
1344
1425
|
|
|
1345
|
-
if(sId == 0)
|
|
1426
|
+
if( sId == 0 )
|
|
1346
1427
|
{
|
|
1347
|
-
string = this.code.lines[i].substr(toX);
|
|
1348
|
-
const pixels =
|
|
1349
|
-
domEl.style.left = "calc(" + pixels + "px + " + this.xPadding + ")";
|
|
1428
|
+
string = this.code.lines[ i ].substr(toX);
|
|
1429
|
+
const pixels = toX * this.charWidth;
|
|
1430
|
+
if( isVisible ) domEl.style.left = "calc(" + pixels + "px + " + this.xPadding + ")";
|
|
1350
1431
|
}
|
|
1351
1432
|
else
|
|
1352
1433
|
{
|
|
1353
|
-
string = (i == fromY) ? this.code.lines[i].substring(0, fromX) : this.code.lines[i]; // Last line, any multiple line...
|
|
1354
|
-
domEl.style.left =
|
|
1434
|
+
string = (i == fromY) ? this.code.lines[ i ].substring(0, fromX) : this.code.lines[ i ]; // Last line, any multiple line...
|
|
1435
|
+
if( isVisible ) domEl.style.left = this.xPadding;
|
|
1355
1436
|
}
|
|
1356
1437
|
|
|
1357
|
-
const stringWidth = this.measureString(string);
|
|
1358
|
-
domEl.style.width = (stringWidth || 8) + "px";
|
|
1359
|
-
domEl._top = 4 + i * this.lineHeight;
|
|
1360
|
-
domEl.style.top = (domEl._top - this.getScrollTop()) + "px";
|
|
1438
|
+
const stringWidth = this.measureString( string );
|
|
1361
1439
|
this.selection.chars += stringWidth / this.charWidth;
|
|
1440
|
+
|
|
1441
|
+
if( isVisible )
|
|
1442
|
+
{
|
|
1443
|
+
domEl.style.width = (stringWidth || 8) + "px";
|
|
1444
|
+
domEl._top = i * this.lineHeight;
|
|
1445
|
+
domEl.style.top = domEl._top + "px";
|
|
1446
|
+
}
|
|
1362
1447
|
}
|
|
1363
1448
|
}
|
|
1364
1449
|
}
|
|
1365
1450
|
|
|
1366
|
-
async processKey(e) {
|
|
1451
|
+
async processKey( e ) {
|
|
1367
1452
|
|
|
1368
1453
|
if( !this.code )
|
|
1369
1454
|
return;
|
|
@@ -1373,12 +1458,12 @@ class CodeEditor {
|
|
|
1373
1458
|
const skip_undo = e.detail.skip_undo ?? false;
|
|
1374
1459
|
|
|
1375
1460
|
// keys with length > 1 are probably special keys
|
|
1376
|
-
if( key.length > 1 && this.specialKeys.indexOf(key) == -1 )
|
|
1461
|
+
if( key.length > 1 && this.specialKeys.indexOf( key ) == -1 )
|
|
1377
1462
|
return;
|
|
1378
1463
|
|
|
1379
|
-
let cursor = this.cursors.children[0];
|
|
1464
|
+
let cursor = this.cursors.children[ 0 ];
|
|
1380
1465
|
let lidx = cursor.line;
|
|
1381
|
-
this.code.lines[lidx] = this.code.lines[lidx] ?? "";
|
|
1466
|
+
this.code.lines[ lidx ] = this.code.lines[ lidx ] ?? "";
|
|
1382
1467
|
|
|
1383
1468
|
// Check combinations
|
|
1384
1469
|
|
|
@@ -1388,22 +1473,22 @@ class CodeEditor {
|
|
|
1388
1473
|
case 'a': // select all
|
|
1389
1474
|
e.preventDefault();
|
|
1390
1475
|
this.resetCursorPos( CodeEditor.CURSOR_LEFT | CodeEditor.CURSOR_TOP );
|
|
1391
|
-
this.startSelection(cursor);
|
|
1476
|
+
this.startSelection( cursor );
|
|
1392
1477
|
const nlines = this.code.lines.length - 1;
|
|
1393
|
-
this.selection.toX = this.code.lines[nlines].length;
|
|
1478
|
+
this.selection.toX = this.code.lines[ nlines ].length;
|
|
1394
1479
|
this.selection.toY = nlines;
|
|
1395
|
-
this.processSelection(null, true);
|
|
1396
|
-
this.cursorToPosition(cursor, this.selection.toX);
|
|
1397
|
-
this.cursorToLine(cursor, this.selection.toY);
|
|
1480
|
+
this.processSelection( null, true );
|
|
1481
|
+
this.cursorToPosition( cursor, this.selection.toX );
|
|
1482
|
+
this.cursorToLine( cursor, this.selection.toY );
|
|
1398
1483
|
break;
|
|
1399
1484
|
case 'c': // copy
|
|
1400
1485
|
this._copyContent();
|
|
1401
1486
|
return;
|
|
1402
1487
|
case 'd': // duplicate line
|
|
1403
1488
|
e.preventDefault();
|
|
1404
|
-
this.code.lines.splice(lidx, 0, this.code.lines[lidx]);
|
|
1489
|
+
this.code.lines.splice( lidx, 0, this.code.lines[ lidx ] );
|
|
1405
1490
|
this.lineDown( cursor );
|
|
1406
|
-
this.processLines(
|
|
1491
|
+
this.processLines();
|
|
1407
1492
|
return;
|
|
1408
1493
|
case 's': // save
|
|
1409
1494
|
e.preventDefault();
|
|
@@ -1420,12 +1505,9 @@ class CodeEditor {
|
|
|
1420
1505
|
return;
|
|
1421
1506
|
const step = this.code.undoSteps.pop();
|
|
1422
1507
|
this.code.lines = step.lines;
|
|
1423
|
-
cursor.line = step.line;
|
|
1424
1508
|
this.restoreCursor( cursor, step.cursor );
|
|
1425
1509
|
this.processLines();
|
|
1426
1510
|
return;
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
1511
|
}
|
|
1430
1512
|
}
|
|
1431
1513
|
|
|
@@ -1469,23 +1551,9 @@ class CodeEditor {
|
|
|
1469
1551
|
|
|
1470
1552
|
// Add undo steps
|
|
1471
1553
|
|
|
1472
|
-
|
|
1473
|
-
const current = d.getTime();
|
|
1474
|
-
|
|
1475
|
-
if( !skip_undo )
|
|
1554
|
+
if( !skip_undo && this.code.lines.length )
|
|
1476
1555
|
{
|
|
1477
|
-
|
|
1478
|
-
this._lastTime = current;
|
|
1479
|
-
this._addUndoStep( cursor );
|
|
1480
|
-
} else {
|
|
1481
|
-
if( (current - this._lastTime) > 3000 && this.code.lines.length){
|
|
1482
|
-
this._lastTime = null;
|
|
1483
|
-
this._addUndoStep( cursor );
|
|
1484
|
-
}else{
|
|
1485
|
-
// If time not enough, reset timer
|
|
1486
|
-
this._lastTime = current;
|
|
1487
|
-
}
|
|
1488
|
-
}
|
|
1556
|
+
this._addUndoStep( cursor );
|
|
1489
1557
|
}
|
|
1490
1558
|
|
|
1491
1559
|
// Some custom cases for word enclosing (), {}, "", '', ...
|
|
@@ -1493,7 +1561,7 @@ class CodeEditor {
|
|
|
1493
1561
|
const enclosableKeys = ["\"", "'", "(", "{"];
|
|
1494
1562
|
if( enclosableKeys.indexOf( key ) > -1 )
|
|
1495
1563
|
{
|
|
1496
|
-
if( this.
|
|
1564
|
+
if( this._encloseSelectedWordWithKey(key, lidx, cursor) )
|
|
1497
1565
|
return;
|
|
1498
1566
|
}
|
|
1499
1567
|
|
|
@@ -1509,14 +1577,14 @@ class CodeEditor {
|
|
|
1509
1577
|
// Append key
|
|
1510
1578
|
|
|
1511
1579
|
const isPairKey = (Object.values( this.pairKeys ).indexOf( key ) > -1) && !this.wasKeyPaired;
|
|
1512
|
-
const sameKeyNext = isPairKey && (this.code.lines[lidx][cursor.position] === key);
|
|
1580
|
+
const sameKeyNext = isPairKey && (this.code.lines[ lidx ][cursor.position] === key);
|
|
1513
1581
|
|
|
1514
1582
|
if( !sameKeyNext )
|
|
1515
1583
|
{
|
|
1516
|
-
this.code.lines[lidx] = [
|
|
1517
|
-
this.code.lines[lidx].slice(0, cursor.position),
|
|
1584
|
+
this.code.lines[ lidx ] = [
|
|
1585
|
+
this.code.lines[ lidx ].slice(0, cursor.position),
|
|
1518
1586
|
key,
|
|
1519
|
-
this.code.lines[lidx].slice(cursor.position)
|
|
1587
|
+
this.code.lines[ lidx ].slice(cursor.position)
|
|
1520
1588
|
].join('');
|
|
1521
1589
|
}
|
|
1522
1590
|
|
|
@@ -1543,6 +1611,14 @@ class CodeEditor {
|
|
|
1543
1611
|
// Update only the current line, since it's only an appended key
|
|
1544
1612
|
this.processLine( lidx );
|
|
1545
1613
|
|
|
1614
|
+
// We are out of the viewport and max length is different? Resize scrollbars...
|
|
1615
|
+
const maxLineLength = this.getMaxLineLength();
|
|
1616
|
+
const numViewportChars = Math.floor( this.codeScroller.clientWidth / this.charWidth );
|
|
1617
|
+
if( maxLineLength >= numViewportChars && maxLineLength != this._lastMaxLineLength )
|
|
1618
|
+
{
|
|
1619
|
+
this.resize( maxLineLength );
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1546
1622
|
// Manage autocomplete
|
|
1547
1623
|
|
|
1548
1624
|
if( this.useAutoComplete )
|
|
@@ -1556,13 +1632,17 @@ class CodeEditor {
|
|
|
1556
1632
|
|
|
1557
1633
|
async _copyContent() {
|
|
1558
1634
|
|
|
1559
|
-
let cursor = this.cursors.children[0];
|
|
1635
|
+
let cursor = this.cursors.children[ 0 ];
|
|
1560
1636
|
let text_to_copy = "";
|
|
1561
1637
|
|
|
1562
1638
|
if( !this.selection ) {
|
|
1563
|
-
text_to_copy = "\n" + this.code.lines[cursor.line];
|
|
1639
|
+
text_to_copy = "\n" + this.code.lines[ cursor.line ];
|
|
1564
1640
|
}
|
|
1565
1641
|
else {
|
|
1642
|
+
|
|
1643
|
+
// Some selections don't depend on mouse up..
|
|
1644
|
+
if( this.selection ) this.selection.invertIfNecessary();
|
|
1645
|
+
|
|
1566
1646
|
const separator = "_NEWLINE_";
|
|
1567
1647
|
let code = this.code.lines.join(separator);
|
|
1568
1648
|
|
|
@@ -1570,7 +1650,7 @@ class CodeEditor {
|
|
|
1570
1650
|
let index = 0;
|
|
1571
1651
|
|
|
1572
1652
|
for(let i = 0; i <= this.selection.fromY; i++)
|
|
1573
|
-
index += (i == this.selection.fromY ? this.selection.fromX : this.code.lines[i].length);
|
|
1653
|
+
index += (i == this.selection.fromY ? this.selection.fromX : this.code.lines[ i ].length);
|
|
1574
1654
|
|
|
1575
1655
|
index += this.selection.fromY * separator.length;
|
|
1576
1656
|
const num_chars = this.selection.chars + (this.selection.toY - this.selection.fromY) * separator.length;
|
|
@@ -1579,22 +1659,26 @@ class CodeEditor {
|
|
|
1579
1659
|
text_to_copy = lines.join('\n');
|
|
1580
1660
|
}
|
|
1581
1661
|
|
|
1582
|
-
navigator.clipboard.writeText(text_to_copy).then(() => console.log("Successfully copied"), (err) => console.error("Error"));
|
|
1662
|
+
navigator.clipboard.writeText( text_to_copy ).then(() => console.log("Successfully copied"), (err) => console.error("Error"));
|
|
1583
1663
|
}
|
|
1584
1664
|
|
|
1585
1665
|
async _cutContent() {
|
|
1586
1666
|
|
|
1587
|
-
let cursor = this.cursors.children[0];
|
|
1667
|
+
let cursor = this.cursors.children[ 0 ];
|
|
1588
1668
|
let lidx = cursor.line;
|
|
1589
1669
|
let text_to_cut = "";
|
|
1590
1670
|
|
|
1591
1671
|
if( !this.selection ) {
|
|
1592
|
-
text_to_cut = "\n" + this.code.lines[cursor.line];
|
|
1593
|
-
this.code.lines.splice(lidx, 1);
|
|
1594
|
-
this.processLines(
|
|
1672
|
+
text_to_cut = "\n" + this.code.lines[ cursor.line ];
|
|
1673
|
+
this.code.lines.splice( lidx, 1 );
|
|
1674
|
+
this.processLines();
|
|
1595
1675
|
this.resetCursorPos( CodeEditor.CURSOR_LEFT );
|
|
1596
1676
|
}
|
|
1597
1677
|
else {
|
|
1678
|
+
|
|
1679
|
+
// Some selections don't depend on mouse up..
|
|
1680
|
+
if( this.selection ) this.selection.invertIfNecessary();
|
|
1681
|
+
|
|
1598
1682
|
const separator = "_NEWLINE_";
|
|
1599
1683
|
let code = this.code.lines.join(separator);
|
|
1600
1684
|
|
|
@@ -1602,7 +1686,7 @@ class CodeEditor {
|
|
|
1602
1686
|
let index = 0;
|
|
1603
1687
|
|
|
1604
1688
|
for(let i = 0; i <= this.selection.fromY; i++)
|
|
1605
|
-
index += (i == this.selection.fromY ? this.selection.fromX : this.code.lines[i].length);
|
|
1689
|
+
index += (i == this.selection.fromY ? this.selection.fromX : this.code.lines[ i ].length);
|
|
1606
1690
|
|
|
1607
1691
|
index += this.selection.fromY * separator.length;
|
|
1608
1692
|
const num_chars = this.selection.chars + (this.selection.toY - this.selection.fromY) * separator.length;
|
|
@@ -1613,7 +1697,7 @@ class CodeEditor {
|
|
|
1613
1697
|
this.deleteSelection( cursor );
|
|
1614
1698
|
}
|
|
1615
1699
|
|
|
1616
|
-
navigator.clipboard.writeText(text_to_cut).then(() => console.log("Successfully cut"), (err) => console.error("Error"));
|
|
1700
|
+
navigator.clipboard.writeText( text_to_cut ).then(() => console.log("Successfully cut"), (err) => console.error("Error"));
|
|
1617
1701
|
}
|
|
1618
1702
|
|
|
1619
1703
|
action( key, deleteSelection, fn ) {
|
|
@@ -1631,70 +1715,112 @@ class CodeEditor {
|
|
|
1631
1715
|
for( let i = 0; i < this.code.lines.length; ++i )
|
|
1632
1716
|
{
|
|
1633
1717
|
const linestring = this.code.lines[ i ];
|
|
1634
|
-
const tokens = this.
|
|
1718
|
+
const tokens = this._getTokensFromLine( linestring, true );
|
|
1635
1719
|
tokens.forEach( t => this.code.tokens[ t ] = 1 );
|
|
1636
1720
|
}
|
|
1637
1721
|
}
|
|
1638
1722
|
|
|
1639
|
-
|
|
1723
|
+
toLocalLine( line ) {
|
|
1724
|
+
|
|
1725
|
+
const d = Math.max( this.firstLineInViewport - this.lineScrollMargin.x, 0 );
|
|
1726
|
+
return Math.min( Math.max( line - d, 0 ), this.code.lines.length - 1 );
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
getMaxLineLength() {
|
|
1730
|
+
|
|
1731
|
+
return Math.max(...this.code.lines.map( v => v.length ));
|
|
1732
|
+
}
|
|
1640
1733
|
|
|
1734
|
+
processLines( mode ) {
|
|
1735
|
+
|
|
1641
1736
|
const start = performance.now();
|
|
1642
1737
|
|
|
1643
1738
|
var gutter_html = "";
|
|
1644
1739
|
var code_html = "";
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1740
|
+
|
|
1741
|
+
// Reset all lines content
|
|
1648
1742
|
this.code.innerHTML = "";
|
|
1649
|
-
|
|
1650
|
-
|
|
1743
|
+
|
|
1651
1744
|
// Get info about lines in viewport
|
|
1652
|
-
const
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
const
|
|
1656
|
-
|
|
1657
|
-
Math.
|
|
1745
|
+
const lastScrollTop = this.getScrollTop();
|
|
1746
|
+
this.firstLineInViewport = ( mode ?? CodeEditor.KEEP_VISIBLE_LINES ) & CodeEditor.UPDATE_VISIBLE_LINES ?
|
|
1747
|
+
( (lastScrollTop / this.lineHeight)|0 ) : this.firstLineInViewport;
|
|
1748
|
+
const totalLinesInViewport = ((this.codeScroller.offsetHeight - 36) / this.lineHeight)|0;
|
|
1749
|
+
this.visibleLinesViewport = new LX.vec2(
|
|
1750
|
+
Math.max( this.firstLineInViewport - this.lineScrollMargin.x, 0 ),
|
|
1751
|
+
Math.min( this.firstLineInViewport + totalLinesInViewport + this.lineScrollMargin.y, this.code.lines.length )
|
|
1658
1752
|
);
|
|
1659
1753
|
|
|
1660
|
-
|
|
1754
|
+
// Add remaining lines if we are near the end of the scroll
|
|
1755
|
+
{
|
|
1756
|
+
const diff = Math.max( this.code.lines.length - this.visibleLinesViewport.y, 0 );
|
|
1757
|
+
if( diff <= this.lineScrollMargin.y )
|
|
1758
|
+
this.visibleLinesViewport.y += diff;
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
// Process visible lines
|
|
1762
|
+
for( let i = this.visibleLinesViewport.x; i < this.visibleLinesViewport.y; ++i )
|
|
1661
1763
|
{
|
|
1662
1764
|
gutter_html += "<span>" + (i + 1) + "</span>";
|
|
1663
1765
|
code_html += this.processLine( i, true );
|
|
1664
1766
|
}
|
|
1665
|
-
|
|
1666
|
-
this.code.innerHTML = code_html;
|
|
1667
|
-
this.gutter.innerHTML = gutter_html;
|
|
1668
1767
|
|
|
1669
|
-
|
|
1768
|
+
this.code.innerHTML = code_html;
|
|
1769
|
+
|
|
1770
|
+
// console.log("RANGE:", this.visibleLinesViewport);
|
|
1771
|
+
// console.log( "Num lines processed:", (this.visibleLinesViewport.y - this.visibleLinesViewport.x), performance.now() - start );
|
|
1772
|
+
// console.log("--------------------------------------------");
|
|
1773
|
+
|
|
1774
|
+
// Update scroll data
|
|
1775
|
+
this.codeScroller.scrollTop = lastScrollTop;
|
|
1776
|
+
this.code.style.top = ( this.visibleLinesViewport.x * this.lineHeight ) + "px";
|
|
1777
|
+
|
|
1778
|
+
// Update selections
|
|
1779
|
+
if( this.selection )
|
|
1780
|
+
this.processSelection( null, true );
|
|
1781
|
+
|
|
1782
|
+
// Clear tmp vars
|
|
1783
|
+
delete this._buildingString;
|
|
1784
|
+
delete this._pendingString;
|
|
1785
|
+
|
|
1786
|
+
this.resize();
|
|
1670
1787
|
}
|
|
1671
1788
|
|
|
1672
1789
|
processLine( linenum, force ) {
|
|
1673
1790
|
|
|
1674
|
-
|
|
1791
|
+
const local_line_num = this.toLocalLine( linenum );
|
|
1792
|
+
const gutter_line = "<span class='line-gutter'>" + (linenum + 1) + "</span>";
|
|
1793
|
+
|
|
1794
|
+
const UPDATE_LINE = ( html ) => {
|
|
1795
|
+
if( !force ) // Single line update
|
|
1796
|
+
this.code.childNodes[ local_line_num ].innerHTML = gutter_line + html;
|
|
1797
|
+
else // Update all lines at once
|
|
1798
|
+
return "<pre>" + ( gutter_line + html ) + "</pre>";
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
// multi-line strings not supported by now
|
|
1802
|
+
delete this._buildingString;
|
|
1803
|
+
delete this._pendingString;
|
|
1675
1804
|
|
|
1676
|
-
// It's allowed to process only 1 line to optimize
|
|
1677
1805
|
let linestring = this.code.lines[ linenum ];
|
|
1678
1806
|
|
|
1807
|
+
// Single line
|
|
1679
1808
|
if( !force )
|
|
1680
1809
|
{
|
|
1681
|
-
|
|
1810
|
+
deleteElement( this.code.childNodes[ local_line_num ] );
|
|
1811
|
+
this.code.insertChildAtIndex( document.createElement( 'pre' ), local_line_num );
|
|
1812
|
+
}
|
|
1682
1813
|
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
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 );
|
|
1814
|
+
// Early out check for no highlighting languages
|
|
1815
|
+
if( this.highlight == 'Plain Text' )
|
|
1816
|
+
{
|
|
1817
|
+
return UPDATE_LINE( linestring );
|
|
1692
1818
|
}
|
|
1693
1819
|
|
|
1694
|
-
const tokensToEvaluate = this.
|
|
1820
|
+
const tokensToEvaluate = this._getTokensFromLine( linestring );
|
|
1695
1821
|
|
|
1696
1822
|
if( !tokensToEvaluate.length )
|
|
1697
|
-
return "<pre></pre>";
|
|
1823
|
+
return "<pre><span class='line-gutter'>" + linenum + "</span></pre>";
|
|
1698
1824
|
|
|
1699
1825
|
var line_inner_html = "";
|
|
1700
1826
|
|
|
@@ -1702,58 +1828,49 @@ class CodeEditor {
|
|
|
1702
1828
|
for( var i = 0; i < tokensToEvaluate.length; ++i )
|
|
1703
1829
|
{
|
|
1704
1830
|
let it = i - 1;
|
|
1705
|
-
let prev = tokensToEvaluate[it];
|
|
1831
|
+
let prev = tokensToEvaluate[ it ];
|
|
1706
1832
|
while( prev == ' ' ) {
|
|
1707
1833
|
it--;
|
|
1708
|
-
prev = tokensToEvaluate[it];
|
|
1834
|
+
prev = tokensToEvaluate[ it ];
|
|
1709
1835
|
}
|
|
1710
1836
|
|
|
1711
1837
|
it = i + 1;
|
|
1712
|
-
let next = tokensToEvaluate[it];
|
|
1838
|
+
let next = tokensToEvaluate[ it ];
|
|
1713
1839
|
while( next == ' ' || next == '"' ) {
|
|
1714
1840
|
it++;
|
|
1715
|
-
next = tokensToEvaluate[it];
|
|
1841
|
+
next = tokensToEvaluate[ it ];
|
|
1716
1842
|
}
|
|
1717
1843
|
|
|
1718
|
-
const token = tokensToEvaluate[i];
|
|
1844
|
+
const token = tokensToEvaluate[ i ];
|
|
1719
1845
|
|
|
1720
1846
|
if( this.languages[ this.highlight ].blockComments ?? true )
|
|
1721
1847
|
{
|
|
1722
|
-
if( token.substr(0, 2) == '/*' )
|
|
1848
|
+
if( token.substr( 0, 2 ) == '/*' )
|
|
1723
1849
|
this._buildingBlockComment = true;
|
|
1724
|
-
if( token.substr(token.length - 2) == '*/' )
|
|
1850
|
+
if( token.substr( token.length - 2 ) == '*/' )
|
|
1725
1851
|
delete this._buildingBlockComment;
|
|
1726
1852
|
}
|
|
1727
1853
|
|
|
1728
|
-
line_inner_html += this.
|
|
1854
|
+
line_inner_html += this._evaluateToken( token, prev, next, (i == tokensToEvaluate.length - 1) );
|
|
1729
1855
|
}
|
|
1730
1856
|
|
|
1731
|
-
|
|
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
|
-
}
|
|
1857
|
+
return UPDATE_LINE( line_inner_html );
|
|
1741
1858
|
}
|
|
1742
1859
|
|
|
1743
1860
|
_processTokens( tokens, offset = 0 ) {
|
|
1744
1861
|
|
|
1745
1862
|
if( this.highlight == 'C++' )
|
|
1746
1863
|
{
|
|
1747
|
-
var idx = tokens.slice(offset).findIndex( (value, index) => this.isNumber(value) );
|
|
1864
|
+
var idx = tokens.slice( offset ).findIndex( ( value, index ) => this.isNumber( value ) );
|
|
1748
1865
|
if( idx > -1 )
|
|
1749
1866
|
{
|
|
1750
1867
|
idx += offset; // Add offset to compute within the whole array of tokens
|
|
1751
|
-
let data = tokens[idx] + tokens[++idx];
|
|
1868
|
+
let data = tokens[ idx ] + tokens[ ++idx ];
|
|
1752
1869
|
while( this.isNumber( data ) )
|
|
1753
1870
|
{
|
|
1754
1871
|
tokens[ idx - 1 ] += tokens[ idx ];
|
|
1755
1872
|
tokens.splice( idx, 1 );
|
|
1756
|
-
data += tokens[idx];
|
|
1873
|
+
data += tokens[ idx ];
|
|
1757
1874
|
}
|
|
1758
1875
|
// Scan for numbers again
|
|
1759
1876
|
return this._processTokens( tokens, idx );
|
|
@@ -1763,16 +1880,43 @@ class CodeEditor {
|
|
|
1763
1880
|
return tokens;
|
|
1764
1881
|
}
|
|
1765
1882
|
|
|
1766
|
-
|
|
1883
|
+
_lineHasComment( linestring ) {
|
|
1767
1884
|
|
|
1768
|
-
// Check if line comment
|
|
1769
1885
|
const singleLineCommentToken = this.languages[ this.highlight ].singleLineCommentToken ?? this.defaultSingleLineCommentToken;
|
|
1770
|
-
const
|
|
1771
|
-
|
|
1772
|
-
|
|
1886
|
+
const idx = linestring.indexOf( singleLineCommentToken );
|
|
1887
|
+
|
|
1888
|
+
if( idx > -1 )
|
|
1889
|
+
{
|
|
1890
|
+
const stringKeys = Object.values( this.stringKeys );
|
|
1891
|
+
// Count times we started a string BEFORE the comment
|
|
1892
|
+
var err = false;
|
|
1893
|
+
err |= stringKeys.some( function(v) {
|
|
1894
|
+
var re = new RegExp( v, "g" );
|
|
1895
|
+
var matches = (linestring.substring( 0, idx ).match( re ) || []);
|
|
1896
|
+
return (matches.length % 2) !== 0;
|
|
1897
|
+
} );
|
|
1898
|
+
err |= stringKeys.some( function(v) {
|
|
1899
|
+
var re = new RegExp( v, "g" );
|
|
1900
|
+
var matches = (linestring.substring( idx ).match( re ) || []);
|
|
1901
|
+
return (matches.length % 2) !== 0;
|
|
1902
|
+
} );
|
|
1903
|
+
return err ? undefined : idx;
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
_getTokensFromLine( linestring, skipNonWords ) {
|
|
1908
|
+
|
|
1909
|
+
// Check if line comment
|
|
1910
|
+
const ogLine = linestring;
|
|
1911
|
+
const hasCommentIdx = this._lineHasComment( linestring );
|
|
1912
|
+
|
|
1913
|
+
if( hasCommentIdx != undefined )
|
|
1914
|
+
{
|
|
1915
|
+
linestring = ogLine.substring( 0, hasCommentIdx );
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
// const usesBlockComments = this.languages[ this.highlight ].blockComments ?? true;
|
|
1773
1919
|
|
|
1774
|
-
// const tokens = linestring.split(' ').join('¬ ¬').split('¬'); // trick to split without losing spaces
|
|
1775
|
-
|
|
1776
1920
|
let tokensToEvaluate = []; // store in a temp array so we know prev and next tokens...
|
|
1777
1921
|
|
|
1778
1922
|
const pushToken = function( t ) {
|
|
@@ -1781,7 +1925,7 @@ class CodeEditor {
|
|
|
1781
1925
|
tokensToEvaluate.push( t );
|
|
1782
1926
|
};
|
|
1783
1927
|
|
|
1784
|
-
let iter = linestring.matchAll(/(::|[\[\](){}<>.,;:*"'
|
|
1928
|
+
let iter = linestring.matchAll(/(::|[\[\](){}<>.,;:*"'%@!/= ])/g);
|
|
1785
1929
|
let subtokens = iter.next();
|
|
1786
1930
|
if( subtokens.value )
|
|
1787
1931
|
{
|
|
@@ -1790,8 +1934,8 @@ class CodeEditor {
|
|
|
1790
1934
|
{
|
|
1791
1935
|
const _pt = linestring.substring(idx, subtokens.value.index);
|
|
1792
1936
|
if( _pt.length ) pushToken( _pt );
|
|
1793
|
-
pushToken( subtokens.value[0] );
|
|
1794
|
-
idx = subtokens.value.index + subtokens.value[0].length;
|
|
1937
|
+
pushToken( subtokens.value[ 0 ] );
|
|
1938
|
+
idx = subtokens.value.index + subtokens.value[ 0 ].length;
|
|
1795
1939
|
subtokens = iter.next();
|
|
1796
1940
|
if(!subtokens.value) {
|
|
1797
1941
|
const _at = linestring.substring(idx);
|
|
@@ -1821,10 +1965,10 @@ class CodeEditor {
|
|
|
1821
1965
|
// if( block ) continue;
|
|
1822
1966
|
// }
|
|
1823
1967
|
|
|
1824
|
-
if(
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1968
|
+
if( hasCommentIdx != undefined )
|
|
1969
|
+
{
|
|
1970
|
+
pushToken( ogLine.substring( hasCommentIdx ) );
|
|
1971
|
+
}
|
|
1828
1972
|
|
|
1829
1973
|
return this._processTokens( tokensToEvaluate );
|
|
1830
1974
|
}
|
|
@@ -1834,45 +1978,51 @@ class CodeEditor {
|
|
|
1834
1978
|
return kindArray[this.highlight] && kindArray[this.highlight][token] != undefined;
|
|
1835
1979
|
}
|
|
1836
1980
|
|
|
1837
|
-
|
|
1981
|
+
_evaluateToken( token, prev, next, isLastToken ) {
|
|
1982
|
+
|
|
1983
|
+
const highlight = this.highlight.replace(/\s/g, '').replaceAll("+", "p").toLowerCase();
|
|
1984
|
+
const customStringKeys = Object.assign( {}, this.stringKeys );
|
|
1985
|
+
|
|
1986
|
+
if( highlight == 'cpp' && prev && prev.includes('#') ) // preprocessor code..
|
|
1987
|
+
{
|
|
1988
|
+
customStringKeys['@<'] = '>';
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
// Manage strings
|
|
1992
|
+
this._stringEnded = false;
|
|
1993
|
+
if( this._buildingString != undefined )
|
|
1994
|
+
{
|
|
1995
|
+
const idx = Object.values(customStringKeys).indexOf( token );
|
|
1996
|
+
this._stringEnded = (idx > -1) && (idx == Object.values(customStringKeys).indexOf( customStringKeys[ '@' + this._buildingString ] ));
|
|
1997
|
+
}
|
|
1998
|
+
else if( customStringKeys[ '@' + token ] )
|
|
1999
|
+
{
|
|
2000
|
+
// Start new string
|
|
2001
|
+
this._buildingString = token;
|
|
2002
|
+
}
|
|
1838
2003
|
|
|
1839
2004
|
if(token == ' ')
|
|
1840
2005
|
{
|
|
2006
|
+
if( this._buildingString != undefined )
|
|
2007
|
+
{
|
|
2008
|
+
this._appendStringToken( token );
|
|
2009
|
+
return "";
|
|
2010
|
+
}
|
|
1841
2011
|
return token;
|
|
1842
2012
|
}
|
|
1843
2013
|
else
|
|
1844
2014
|
{
|
|
1845
|
-
let stringEnded = false;
|
|
1846
|
-
let highlight = this.highlight.replace(/\s/g, '').replaceAll("+", "p").toLowerCase();
|
|
1847
|
-
|
|
1848
2015
|
const singleLineCommentToken = this.languages[ this.highlight ].singleLineCommentToken ?? this.defaultSingleLineCommentToken;
|
|
1849
2016
|
const usesBlockComments = this.languages[ this.highlight ].blockComments ?? true;
|
|
1850
|
-
|
|
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
|
-
|
|
2017
|
+
|
|
1869
2018
|
let token_classname = "";
|
|
2019
|
+
let discardToken = false;
|
|
1870
2020
|
|
|
1871
2021
|
if( this._buildingBlockComment != undefined )
|
|
1872
2022
|
token_classname = "cm-com";
|
|
1873
2023
|
|
|
1874
2024
|
else if( this._buildingString != undefined )
|
|
1875
|
-
|
|
2025
|
+
discardToken = this._appendStringToken( token );
|
|
1876
2026
|
|
|
1877
2027
|
else if( this._mustHightlightWord( token, this.keywords ) )
|
|
1878
2028
|
token_classname = "cm-kwd";
|
|
@@ -1886,28 +2036,28 @@ class CodeEditor {
|
|
|
1886
2036
|
else if( this._mustHightlightWord( token, this.symbols ) )
|
|
1887
2037
|
token_classname = "cm-sym";
|
|
1888
2038
|
|
|
1889
|
-
else if( token.substr(0,
|
|
2039
|
+
else if( token.substr( 0, singleLineCommentToken.length ) == singleLineCommentToken )
|
|
1890
2040
|
token_classname = "cm-com";
|
|
1891
2041
|
|
|
1892
|
-
else if( usesBlockComments && token.substr(0, 2) == '/*' )
|
|
2042
|
+
else if( usesBlockComments && token.substr( 0, 2 ) == '/*' )
|
|
1893
2043
|
token_classname = "cm-com";
|
|
1894
2044
|
|
|
1895
|
-
else if( usesBlockComments && token.substr(token.length - 2) == '*/' )
|
|
2045
|
+
else if( usesBlockComments && token.substr( token.length - 2 ) == '*/' )
|
|
1896
2046
|
token_classname = "cm-com";
|
|
1897
2047
|
|
|
1898
|
-
else if( this.isNumber(token) || this.isNumber( token.replace(/[px]|[em]|%/g,'') ) )
|
|
2048
|
+
else if( this.isNumber( token ) || this.isNumber( token.replace(/[px]|[em]|%/g,'') ) )
|
|
1899
2049
|
token_classname = "cm-dec";
|
|
1900
2050
|
|
|
1901
|
-
else if( this.
|
|
2051
|
+
else if( this._isCSSClass( token, prev, next ) )
|
|
1902
2052
|
token_classname = "cm-kwd";
|
|
1903
2053
|
|
|
1904
|
-
else if ( this.
|
|
2054
|
+
else if ( this._isType( token, prev, next ) )
|
|
1905
2055
|
token_classname = "cm-typ";
|
|
1906
2056
|
|
|
1907
|
-
else if ( highlight == 'batch' && (token == '@' || prev == ':' || prev == '@') )
|
|
2057
|
+
else if ( highlight == 'batch' && ( token == '@' || prev == ':' || prev == '@' ) )
|
|
1908
2058
|
token_classname = "cm-kwd";
|
|
1909
2059
|
|
|
1910
|
-
else if ( highlight == 'cpp' && token.includes('#') ) // C++ preprocessor
|
|
2060
|
+
else if ( highlight == 'cpp' && token.includes( '#' ) ) // C++ preprocessor
|
|
1911
2061
|
token_classname = "cm-ppc";
|
|
1912
2062
|
|
|
1913
2063
|
else if ( highlight == 'cpp' && prev == '<' && (next == '>' || next == '*') ) // Defining template type in C++
|
|
@@ -1922,16 +2072,53 @@ class CodeEditor {
|
|
|
1922
2072
|
else if ( highlight == 'css' && prev == undefined && next == ':' ) // CSS attribute
|
|
1923
2073
|
token_classname = "cm-typ";
|
|
1924
2074
|
|
|
1925
|
-
else if ( token[0] != '@' && next == '(' )
|
|
2075
|
+
else if ( token[ 0 ] != '@' && token[ 0 ] != ',' && next == '(' )
|
|
1926
2076
|
token_classname = "cm-mtd";
|
|
1927
2077
|
|
|
1928
|
-
|
|
2078
|
+
|
|
2079
|
+
// We finished constructing a string
|
|
2080
|
+
if( this._buildingString && ( this._stringEnded || isLastToken ) )
|
|
2081
|
+
{
|
|
2082
|
+
token = this._getCurrentString();
|
|
2083
|
+
token_classname = "cm-str";
|
|
2084
|
+
discardToken = false;
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
// Update state
|
|
2088
|
+
this._buildingString = this._stringEnded ? undefined : this._buildingString;
|
|
2089
|
+
|
|
2090
|
+
if( discardToken )
|
|
2091
|
+
return "";
|
|
2092
|
+
|
|
2093
|
+
token = token.replace( "<", "<" );
|
|
2094
|
+
token = token.replace( ">", ">" );
|
|
2095
|
+
|
|
2096
|
+
// No highlighting, no need to put it inside another span..
|
|
2097
|
+
if( !token_classname.length )
|
|
2098
|
+
return token;
|
|
1929
2099
|
|
|
1930
2100
|
return "<span class='" + highlight + " " + token_classname + "'>" + token + "</span>";
|
|
1931
2101
|
}
|
|
1932
2102
|
}
|
|
1933
2103
|
|
|
1934
|
-
|
|
2104
|
+
_appendStringToken( token ) {
|
|
2105
|
+
|
|
2106
|
+
if( !this._pendingString )
|
|
2107
|
+
this._pendingString = "";
|
|
2108
|
+
|
|
2109
|
+
this._pendingString += token;
|
|
2110
|
+
|
|
2111
|
+
return true;
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
_getCurrentString() {
|
|
2115
|
+
|
|
2116
|
+
const chars = this._pendingString;
|
|
2117
|
+
delete this._pendingString;
|
|
2118
|
+
return chars;
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
_isCSSClass( token, prev, next ) {
|
|
1935
2122
|
return this.highlight == 'CSS' && prev == '.';
|
|
1936
2123
|
}
|
|
1937
2124
|
|
|
@@ -1948,7 +2135,7 @@ class CodeEditor {
|
|
|
1948
2135
|
return token.length && token != ' ' && !Number.isNaN(+token);
|
|
1949
2136
|
}
|
|
1950
2137
|
|
|
1951
|
-
|
|
2138
|
+
_isType( token, prev, next ) {
|
|
1952
2139
|
|
|
1953
2140
|
// Common case
|
|
1954
2141
|
if( this._mustHightlightWord( token, this.types ) )
|
|
@@ -1972,7 +2159,7 @@ class CodeEditor {
|
|
|
1972
2159
|
}
|
|
1973
2160
|
}
|
|
1974
2161
|
|
|
1975
|
-
|
|
2162
|
+
_encloseSelectedWordWithKey( key, lidx, cursor ) {
|
|
1976
2163
|
|
|
1977
2164
|
if( !this.selection || (this.selection.fromY != this.selection.toY) )
|
|
1978
2165
|
return false;
|
|
@@ -1980,10 +2167,10 @@ class CodeEditor {
|
|
|
1980
2167
|
this.selection.invertIfNecessary();
|
|
1981
2168
|
|
|
1982
2169
|
// Insert first..
|
|
1983
|
-
this.code.lines[lidx] = [
|
|
1984
|
-
this.code.lines[lidx].slice(0, this.selection.fromX),
|
|
2170
|
+
this.code.lines[ lidx ] = [
|
|
2171
|
+
this.code.lines[ lidx ].slice(0, this.selection.fromX),
|
|
1985
2172
|
key,
|
|
1986
|
-
this.code.lines[lidx].slice(this.selection.fromX)
|
|
2173
|
+
this.code.lines[ lidx ].slice(this.selection.fromX)
|
|
1987
2174
|
].join('');
|
|
1988
2175
|
|
|
1989
2176
|
// Go to the end of the word
|
|
@@ -2000,10 +2187,10 @@ class CodeEditor {
|
|
|
2000
2187
|
}
|
|
2001
2188
|
|
|
2002
2189
|
// Insert the other
|
|
2003
|
-
this.code.lines[lidx] = [
|
|
2004
|
-
this.code.lines[lidx].slice(0, cursor.position),
|
|
2190
|
+
this.code.lines[ lidx ] = [
|
|
2191
|
+
this.code.lines[ lidx ].slice(0, cursor.position),
|
|
2005
2192
|
key,
|
|
2006
|
-
this.code.lines[lidx].slice(cursor.position)
|
|
2193
|
+
this.code.lines[ lidx ].slice(cursor.position)
|
|
2007
2194
|
].join('');
|
|
2008
2195
|
|
|
2009
2196
|
// Recompute and reposition current selection
|
|
@@ -2018,19 +2205,19 @@ class CodeEditor {
|
|
|
2018
2205
|
return true;
|
|
2019
2206
|
}
|
|
2020
2207
|
|
|
2021
|
-
lineUp(cursor, resetLeft) {
|
|
2208
|
+
lineUp( cursor, resetLeft ) {
|
|
2022
2209
|
|
|
2023
|
-
cursor = cursor ?? this.cursors.children[0];
|
|
2210
|
+
cursor = cursor ?? this.cursors.children[ 0 ];
|
|
2024
2211
|
cursor.line--;
|
|
2025
|
-
cursor.line = Math.max(0, cursor.line);
|
|
2026
|
-
this.cursorToTop(cursor, resetLeft);
|
|
2212
|
+
cursor.line = Math.max( 0, cursor.line );
|
|
2213
|
+
this.cursorToTop( cursor, resetLeft );
|
|
2027
2214
|
}
|
|
2028
2215
|
|
|
2029
|
-
lineDown(cursor, resetLeft) {
|
|
2216
|
+
lineDown( cursor, resetLeft ) {
|
|
2030
2217
|
|
|
2031
|
-
cursor = cursor ?? this.cursors.children[0];
|
|
2218
|
+
cursor = cursor ?? this.cursors.children[ 0 ];
|
|
2032
2219
|
cursor.line++;
|
|
2033
|
-
this.cursorToBottom(cursor, resetLeft);
|
|
2220
|
+
this.cursorToBottom( cursor, resetLeft );
|
|
2034
2221
|
}
|
|
2035
2222
|
|
|
2036
2223
|
restartBlink() {
|
|
@@ -2057,40 +2244,41 @@ class CodeEditor {
|
|
|
2057
2244
|
this.selections.classList.add('show');
|
|
2058
2245
|
|
|
2059
2246
|
// Create new selection instance
|
|
2060
|
-
this.selection = new
|
|
2247
|
+
this.selection = new CodeSelection(this, cursor.position, cursor.line);
|
|
2061
2248
|
}
|
|
2062
2249
|
|
|
2063
2250
|
deleteSelection( cursor ) {
|
|
2064
2251
|
|
|
2065
2252
|
// I think it's not necessary but...
|
|
2066
|
-
if(this.disableEdition)
|
|
2253
|
+
if( this.disableEdition )
|
|
2067
2254
|
return;
|
|
2068
2255
|
|
|
2069
2256
|
// Some selections don't depend on mouse up..
|
|
2070
|
-
if(this.selection) this.selection.invertIfNecessary();
|
|
2257
|
+
if( this.selection ) this.selection.invertIfNecessary();
|
|
2071
2258
|
|
|
2072
2259
|
const separator = "_NEWLINE_";
|
|
2073
|
-
let code = this.code.lines.join(separator);
|
|
2260
|
+
let code = this.code.lines.join( separator );
|
|
2074
2261
|
|
|
2075
2262
|
// Get linear start index
|
|
2076
2263
|
let index = 0;
|
|
2077
2264
|
for(let i = 0; i <= this.selection.fromY; i++)
|
|
2078
|
-
index += (i == this.selection.fromY ? this.selection.fromX : this.code.lines[i].length);
|
|
2265
|
+
index += (i == this.selection.fromY ? this.selection.fromX : this.code.lines[ i ].length);
|
|
2079
2266
|
|
|
2080
2267
|
index += this.selection.fromY * separator.length;
|
|
2081
2268
|
|
|
2082
2269
|
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);
|
|
2085
|
-
|
|
2086
|
-
this.code.lines = (pre + post).split(separator);
|
|
2087
|
-
this.processLines(this.selection.fromY);
|
|
2270
|
+
const pre = code.slice( 0, index );
|
|
2271
|
+
const post = code.slice( index + num_chars );
|
|
2088
2272
|
|
|
2089
|
-
this.
|
|
2090
|
-
|
|
2273
|
+
this.code.lines = ( pre + post ).split( separator );
|
|
2274
|
+
|
|
2275
|
+
this.cursorToLine( cursor, this.selection.fromY, true );
|
|
2276
|
+
this.cursorToPosition( cursor, this.selection.fromX );
|
|
2277
|
+
|
|
2091
2278
|
this.endSelection();
|
|
2092
2279
|
|
|
2093
|
-
this.
|
|
2280
|
+
this.processLines();
|
|
2281
|
+
this._refreshCodeInfo( cursor.line, cursor.position );
|
|
2094
2282
|
}
|
|
2095
2283
|
|
|
2096
2284
|
endSelection() {
|
|
@@ -2102,19 +2290,20 @@ class CodeEditor {
|
|
|
2102
2290
|
|
|
2103
2291
|
cursorToRight( key, cursor ) {
|
|
2104
2292
|
|
|
2105
|
-
if(!key) return;
|
|
2106
|
-
cursor = cursor ?? this.cursors.children[0];
|
|
2293
|
+
if( !key ) return;
|
|
2294
|
+
cursor = cursor ?? this.cursors.children[ 0 ];
|
|
2107
2295
|
cursor._left += this.charWidth;
|
|
2108
|
-
cursor.style.left = "calc(" +
|
|
2296
|
+
cursor.style.left = "calc( " + cursor._left + "px + " + this.xPadding + " )";
|
|
2109
2297
|
cursor.position++;
|
|
2298
|
+
|
|
2110
2299
|
this.restartBlink();
|
|
2111
2300
|
this._refreshCodeInfo( cursor.line, cursor.position );
|
|
2112
2301
|
|
|
2113
2302
|
// Add horizontal scroll
|
|
2114
2303
|
|
|
2115
2304
|
doAsync(() => {
|
|
2116
|
-
var
|
|
2117
|
-
if( cursor.position >=
|
|
2305
|
+
var viewportSizeX = ( this.codeScroller.clientWidth + this.getScrollLeft() ) - 48; // Gutter offset
|
|
2306
|
+
if( (cursor.position * this.charWidth) >= viewportSizeX )
|
|
2118
2307
|
this.setScrollLeft( this.getScrollLeft() + this.charWidth );
|
|
2119
2308
|
});
|
|
2120
2309
|
}
|
|
@@ -2122,30 +2311,30 @@ class CodeEditor {
|
|
|
2122
2311
|
cursorToLeft( key, cursor ) {
|
|
2123
2312
|
|
|
2124
2313
|
if(!key) return;
|
|
2125
|
-
cursor = cursor ?? this.cursors.children[0];
|
|
2314
|
+
cursor = cursor ?? this.cursors.children[ 0 ];
|
|
2126
2315
|
cursor._left -= this.charWidth;
|
|
2127
|
-
cursor._left = Math.max(cursor._left, 0);
|
|
2128
|
-
cursor.style.left = "calc(" +
|
|
2316
|
+
cursor._left = Math.max( cursor._left, 0 );
|
|
2317
|
+
cursor.style.left = "calc( " + cursor._left + "px + " + this.xPadding + " )";
|
|
2129
2318
|
cursor.position--;
|
|
2130
|
-
cursor.position = Math.max(cursor.position, 0);
|
|
2319
|
+
cursor.position = Math.max( cursor.position, 0 );
|
|
2131
2320
|
this.restartBlink();
|
|
2132
2321
|
this._refreshCodeInfo( cursor.line, cursor.position );
|
|
2133
2322
|
|
|
2134
2323
|
// Add horizontal scroll
|
|
2135
2324
|
|
|
2136
2325
|
doAsync(() => {
|
|
2137
|
-
var
|
|
2138
|
-
if( (cursor.position - 1) <
|
|
2326
|
+
var viewportSizeX = this.getScrollLeft(); // Gutter offset
|
|
2327
|
+
if( ( ( cursor.position - 1 ) * this.charWidth ) < viewportSizeX )
|
|
2139
2328
|
this.setScrollLeft( this.getScrollLeft() - this.charWidth );
|
|
2140
2329
|
});
|
|
2141
2330
|
}
|
|
2142
2331
|
|
|
2143
2332
|
cursorToTop( cursor, resetLeft = false ) {
|
|
2144
2333
|
|
|
2145
|
-
cursor = cursor ?? this.cursors.children[0];
|
|
2334
|
+
cursor = cursor ?? this.cursors.children[ 0 ];
|
|
2146
2335
|
cursor._top -= this.lineHeight;
|
|
2147
|
-
cursor._top = Math.max(cursor._top,
|
|
2148
|
-
cursor.style.top = "calc(" +
|
|
2336
|
+
cursor._top = Math.max(cursor._top, 0);
|
|
2337
|
+
cursor.style.top = "calc(" + cursor._top + "px)";
|
|
2149
2338
|
this.restartBlink();
|
|
2150
2339
|
|
|
2151
2340
|
if(resetLeft)
|
|
@@ -2154,7 +2343,7 @@ class CodeEditor {
|
|
|
2154
2343
|
this._refreshCodeInfo( cursor.line, cursor.position );
|
|
2155
2344
|
|
|
2156
2345
|
doAsync(() => {
|
|
2157
|
-
var first_line = (this.getScrollTop() / this.lineHeight)|0;
|
|
2346
|
+
var first_line = ( this.getScrollTop() / this.lineHeight )|0;
|
|
2158
2347
|
if( (cursor.line - 1) < first_line )
|
|
2159
2348
|
this.setScrollTop( this.getScrollTop() - this.lineHeight );
|
|
2160
2349
|
});
|
|
@@ -2162,9 +2351,9 @@ class CodeEditor {
|
|
|
2162
2351
|
|
|
2163
2352
|
cursorToBottom( cursor, resetLeft = false ) {
|
|
2164
2353
|
|
|
2165
|
-
cursor = cursor ?? this.cursors.children[0];
|
|
2354
|
+
cursor = cursor ?? this.cursors.children[ 0 ];
|
|
2166
2355
|
cursor._top += this.lineHeight;
|
|
2167
|
-
cursor.style.top = "calc(" +
|
|
2356
|
+
cursor.style.top = "calc(" + cursor._top + "px)";
|
|
2168
2357
|
this.restartBlink();
|
|
2169
2358
|
|
|
2170
2359
|
if(resetLeft)
|
|
@@ -2173,7 +2362,7 @@ class CodeEditor {
|
|
|
2173
2362
|
this._refreshCodeInfo( cursor.line, cursor.position );
|
|
2174
2363
|
|
|
2175
2364
|
doAsync(() => {
|
|
2176
|
-
var last_line = ((this.
|
|
2365
|
+
var last_line = ( ( this.codeScroller.offsetHeight + this.getScrollTop() ) / this.lineHeight )|0;
|
|
2177
2366
|
if( cursor.line >= last_line )
|
|
2178
2367
|
this.setScrollTop( this.getScrollTop() + this.lineHeight );
|
|
2179
2368
|
});
|
|
@@ -2181,7 +2370,7 @@ class CodeEditor {
|
|
|
2181
2370
|
|
|
2182
2371
|
cursorToString( cursor, text, reverse ) {
|
|
2183
2372
|
|
|
2184
|
-
cursor = cursor ?? this.cursors.children[0];
|
|
2373
|
+
cursor = cursor ?? this.cursors.children[ 0 ];
|
|
2185
2374
|
for( let char of text )
|
|
2186
2375
|
reverse ? this.cursorToLeft(char) : this.cursorToRight(char);
|
|
2187
2376
|
}
|
|
@@ -2190,20 +2379,20 @@ class CodeEditor {
|
|
|
2190
2379
|
|
|
2191
2380
|
cursor.position = position;
|
|
2192
2381
|
cursor._left = position * this.charWidth;
|
|
2193
|
-
cursor.style.left = "calc(" +
|
|
2382
|
+
cursor.style.left = "calc(" + cursor._left + "px + " + this.xPadding + ")";
|
|
2194
2383
|
}
|
|
2195
2384
|
|
|
2196
2385
|
cursorToLine( cursor, line, resetLeft = false ) {
|
|
2197
2386
|
|
|
2198
2387
|
cursor.line = line;
|
|
2199
|
-
cursor._top =
|
|
2200
|
-
cursor.style.top =
|
|
2388
|
+
cursor._top = this.lineHeight * line;
|
|
2389
|
+
cursor.style.top = cursor._top + "px";
|
|
2201
2390
|
if(resetLeft) this.resetCursorPos( CodeEditor.CURSOR_LEFT, cursor );
|
|
2202
2391
|
}
|
|
2203
2392
|
|
|
2204
2393
|
saveCursor( cursor, state = {} ) {
|
|
2205
2394
|
|
|
2206
|
-
var cursor = cursor ?? this.cursors.children[0];
|
|
2395
|
+
var cursor = cursor ?? this.cursors.children[ 0 ];
|
|
2207
2396
|
state.top = cursor._top;
|
|
2208
2397
|
state.left = cursor._left;
|
|
2209
2398
|
state.line = cursor.line;
|
|
@@ -2213,19 +2402,19 @@ class CodeEditor {
|
|
|
2213
2402
|
|
|
2214
2403
|
restoreCursor( cursor, state ) {
|
|
2215
2404
|
|
|
2216
|
-
cursor = cursor ?? this.cursors.children[0];
|
|
2405
|
+
cursor = cursor ?? this.cursors.children[ 0 ];
|
|
2217
2406
|
cursor.line = state.line ?? 0;
|
|
2218
|
-
cursor.position = state.
|
|
2407
|
+
cursor.position = state.position ?? 0;
|
|
2219
2408
|
|
|
2220
2409
|
cursor._left = state.left ?? 0;
|
|
2221
2410
|
cursor.style.left = "calc(" + (cursor._left - this.getScrollLeft()) + "px + " + this.xPadding + ")";
|
|
2222
|
-
cursor._top = state.top ??
|
|
2411
|
+
cursor._top = state.top ?? 0;
|
|
2223
2412
|
cursor.style.top = "calc(" + (cursor._top - this.getScrollTop()) + "px)";
|
|
2224
2413
|
}
|
|
2225
2414
|
|
|
2226
2415
|
resetCursorPos( flag, cursor ) {
|
|
2227
2416
|
|
|
2228
|
-
cursor = cursor ?? this.cursors.children[0];
|
|
2417
|
+
cursor = cursor ?? this.cursors.children[ 0 ];
|
|
2229
2418
|
|
|
2230
2419
|
if( flag & CodeEditor.CURSOR_LEFT )
|
|
2231
2420
|
{
|
|
@@ -2236,7 +2425,7 @@ class CodeEditor {
|
|
|
2236
2425
|
|
|
2237
2426
|
if( flag & CodeEditor.CURSOR_TOP )
|
|
2238
2427
|
{
|
|
2239
|
-
cursor._top =
|
|
2428
|
+
cursor._top = 0;
|
|
2240
2429
|
cursor.style.top = (cursor._top - this.getScrollTop()) + "px";
|
|
2241
2430
|
cursor.line = 0;
|
|
2242
2431
|
}
|
|
@@ -2261,160 +2450,163 @@ class CodeEditor {
|
|
|
2261
2450
|
|
|
2262
2451
|
getScrollLeft() {
|
|
2263
2452
|
|
|
2264
|
-
if(!this.
|
|
2265
|
-
return this.
|
|
2453
|
+
if(!this.codeScroller) return 0;
|
|
2454
|
+
return this.codeScroller.scrollLeft;
|
|
2266
2455
|
}
|
|
2267
2456
|
|
|
2268
2457
|
getScrollTop() {
|
|
2269
2458
|
|
|
2270
|
-
if(!this.
|
|
2271
|
-
return this.
|
|
2459
|
+
if(!this.codeScroller) return 0;
|
|
2460
|
+
return this.codeScroller.scrollTop;
|
|
2272
2461
|
}
|
|
2273
2462
|
|
|
2274
|
-
setScrollLeft( value
|
|
2463
|
+
setScrollLeft( value ) {
|
|
2275
2464
|
|
|
2276
|
-
if(!this.
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
value = LX.UTILS.clamp( value, 0, maxWidth );
|
|
2282
|
-
|
|
2283
|
-
this.code.style.marginLeft = (-value) + "px";
|
|
2465
|
+
if(!this.codeScroller) return;
|
|
2466
|
+
this.codeScroller.scrollLeft = value;
|
|
2467
|
+
this.setScrollBarValue( 'horizontal', 0 );
|
|
2468
|
+
}
|
|
2284
2469
|
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2470
|
+
setScrollTop( value ) {
|
|
2471
|
+
|
|
2472
|
+
if(!this.codeScroller) return;
|
|
2473
|
+
this.codeScroller.scrollTop = value;
|
|
2474
|
+
this.setScrollBarValue( 'vertical' );
|
|
2475
|
+
}
|
|
2291
2476
|
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2477
|
+
resize( pMaxLength ) {
|
|
2478
|
+
|
|
2479
|
+
setTimeout( () => {
|
|
2295
2480
|
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2481
|
+
// Update max viewport
|
|
2482
|
+
const maxLineLength = pMaxLength ?? this.getMaxLineLength();
|
|
2483
|
+
const scrollWidth = maxLineLength * this.charWidth;
|
|
2484
|
+
const scrollHeight = this.code.lines.length * this.lineHeight;
|
|
2300
2485
|
|
|
2301
|
-
|
|
2302
|
-
}
|
|
2486
|
+
this._lastMaxLineLength = maxLineLength;
|
|
2303
2487
|
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
if(!this.code) return;
|
|
2488
|
+
this.codeSizer.style.minWidth = scrollWidth + "px";
|
|
2489
|
+
this.codeSizer.style.minHeight = scrollHeight + "px";
|
|
2307
2490
|
|
|
2308
|
-
|
|
2309
|
-
const maxHeight = Math.max( this.code.scrollHeight - realClientHeight, 0 );
|
|
2310
|
-
|
|
2311
|
-
value = LX.UTILS.clamp( value, 0, maxHeight );
|
|
2312
|
-
|
|
2313
|
-
this.gutter.scrollTop = value;
|
|
2314
|
-
|
|
2315
|
-
this.code.style.marginTop = (-value) + "px";
|
|
2491
|
+
this.resizeScrollBars();
|
|
2316
2492
|
|
|
2317
|
-
|
|
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
|
-
}
|
|
2493
|
+
// console.warn("Resize editor viewport");
|
|
2325
2494
|
|
|
2326
|
-
|
|
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;
|
|
2495
|
+
}, 10 );
|
|
2336
2496
|
}
|
|
2337
2497
|
|
|
2338
2498
|
resizeScrollBars() {
|
|
2339
2499
|
|
|
2340
|
-
const
|
|
2500
|
+
const totalLinesInViewport = ((this.codeScroller.offsetHeight - 36) / this.lineHeight)|0;
|
|
2341
2501
|
|
|
2342
|
-
if(
|
|
2502
|
+
if( totalLinesInViewport > this.code.lines.length )
|
|
2343
2503
|
{
|
|
2344
|
-
this.
|
|
2345
|
-
this.
|
|
2504
|
+
this.codeScroller.classList.remove( 'with-vscrollbar' );
|
|
2505
|
+
this.vScrollbar.root.classList.add( 'scrollbar-unused' );
|
|
2346
2506
|
}
|
|
2347
2507
|
else
|
|
2348
2508
|
{
|
|
2349
|
-
this.
|
|
2350
|
-
this.
|
|
2351
|
-
this.
|
|
2352
|
-
this.
|
|
2509
|
+
this.codeScroller.classList.add( 'with-vscrollbar' );
|
|
2510
|
+
this.vScrollbar.root.classList.remove( 'scrollbar-unused' );
|
|
2511
|
+
this.vScrollbar.thumb.size = (totalLinesInViewport / this.code.lines.length);
|
|
2512
|
+
this.vScrollbar.thumb.style.height = (this.vScrollbar.thumb.size * 100.0) + "%";
|
|
2353
2513
|
}
|
|
2354
2514
|
|
|
2355
|
-
const numViewportChars = Math.floor( this.
|
|
2356
|
-
const
|
|
2357
|
-
const maxLineLength = Math.max(...line_lengths);
|
|
2515
|
+
const numViewportChars = Math.floor( this.codeScroller.clientWidth / this.charWidth );
|
|
2516
|
+
const maxLineLength = this._lastMaxLineLength;
|
|
2358
2517
|
|
|
2359
2518
|
if( numViewportChars > maxLineLength )
|
|
2360
2519
|
{
|
|
2361
|
-
this.
|
|
2362
|
-
this.
|
|
2520
|
+
this.codeScroller.classList.remove( 'with-hscrollbar' );
|
|
2521
|
+
this.hScrollbar.root.classList.add( 'scrollbar-unused' );
|
|
2363
2522
|
}
|
|
2364
2523
|
else
|
|
2365
2524
|
{
|
|
2366
|
-
this.
|
|
2367
|
-
this.
|
|
2368
|
-
this.
|
|
2369
|
-
this.
|
|
2525
|
+
this.codeScroller.classList.add( 'with-hscrollbar' );
|
|
2526
|
+
this.hScrollbar.root.classList.remove( 'scrollbar-unused' );
|
|
2527
|
+
this.hScrollbar.thumb.size = (numViewportChars / maxLineLength);
|
|
2528
|
+
this.hScrollbar.thumb.style.width = (this.hScrollbar.thumb.size * 100.0) + "%";
|
|
2370
2529
|
}
|
|
2371
2530
|
}
|
|
2372
2531
|
|
|
2373
|
-
setScrollBarValue(
|
|
2532
|
+
setScrollBarValue( type = 'vertical', value ) {
|
|
2374
2533
|
|
|
2375
2534
|
if( type == 'vertical' )
|
|
2376
2535
|
{
|
|
2377
|
-
const
|
|
2378
|
-
const
|
|
2536
|
+
const scrollBarHeight = this.vScrollbar.thumb.parentElement.offsetHeight;
|
|
2537
|
+
const scrollThumbHeight = this.vScrollbar.thumb.offsetHeight;
|
|
2379
2538
|
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
this.
|
|
2539
|
+
const scrollHeight = this.codeScroller.scrollHeight - this.codeScroller.clientHeight;
|
|
2540
|
+
const currentScroll = this.codeScroller.scrollTop;
|
|
2541
|
+
|
|
2542
|
+
this.vScrollbar.thumb._top = ( currentScroll / scrollHeight ) * ( scrollBarHeight - scrollThumbHeight );
|
|
2543
|
+
this.vScrollbar.thumb.style.top = this.vScrollbar.thumb._top + "px";
|
|
2384
2544
|
}
|
|
2385
2545
|
else
|
|
2386
2546
|
{
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
this.
|
|
2393
|
-
|
|
2547
|
+
this.codeScroller.scrollLeft += value;
|
|
2548
|
+
|
|
2549
|
+
const scrollBarWidth = this.hScrollbar.thumb.parentElement.offsetWidth;
|
|
2550
|
+
const scrollThumbWidth = this.hScrollbar.thumb.offsetWidth;
|
|
2551
|
+
|
|
2552
|
+
const scrollWidth = this.codeScroller.scrollWidth - this.codeScroller.clientWidth;
|
|
2553
|
+
const currentScroll = this.codeScroller.scrollLeft;
|
|
2554
|
+
|
|
2555
|
+
this.hScrollbar.thumb._left = ( currentScroll / scrollWidth ) * ( scrollBarWidth - scrollThumbWidth );
|
|
2556
|
+
this.hScrollbar.thumb.style.left = this.hScrollbar.thumb._left + "px";
|
|
2394
2557
|
}
|
|
2395
2558
|
}
|
|
2396
2559
|
|
|
2397
|
-
|
|
2560
|
+
updateHorizontalScrollFromScrollBar( value ) {
|
|
2561
|
+
|
|
2562
|
+
value = this.hScrollbar.thumb._left - value;
|
|
2563
|
+
|
|
2564
|
+
// Move scrollbar thumb
|
|
2565
|
+
|
|
2566
|
+
const scrollBarWidth = this.hScrollbar.thumb.parentElement.offsetWidth;
|
|
2567
|
+
const scrollThumbWidth = this.hScrollbar.thumb.offsetWidth;
|
|
2398
2568
|
|
|
2399
|
-
this.
|
|
2400
|
-
this.
|
|
2569
|
+
this.hScrollbar.thumb._left = LX.UTILS.clamp( value, 0, ( scrollBarWidth - scrollThumbWidth ) );
|
|
2570
|
+
this.hScrollbar.thumb.style.left = this.hScrollbar.thumb._left + "px";
|
|
2571
|
+
|
|
2572
|
+
// Scroll code
|
|
2573
|
+
|
|
2574
|
+
const scrollWidth = this.codeScroller.scrollWidth - this.codeScroller.clientWidth;
|
|
2575
|
+
const currentScroll = (this.hScrollbar.thumb._left * scrollWidth) / ( scrollBarWidth - scrollThumbWidth );
|
|
2576
|
+
this.codeScroller.scrollLeft = currentScroll;
|
|
2577
|
+
|
|
2578
|
+
|
|
2579
|
+
this._discardScroll = true;
|
|
2401
2580
|
}
|
|
2402
2581
|
|
|
2403
|
-
|
|
2582
|
+
updateVerticalScrollFromScrollBar( value ) {
|
|
2583
|
+
|
|
2584
|
+
value = this.vScrollbar.thumb._top - value;
|
|
2585
|
+
|
|
2586
|
+
// Move scrollbar thumb
|
|
2587
|
+
|
|
2588
|
+
const scrollBarHeight = this.vScrollbar.thumb.parentElement.offsetHeight;
|
|
2589
|
+
const scrollThumbHeight = this.vScrollbar.thumb.offsetHeight;
|
|
2590
|
+
|
|
2591
|
+
this.vScrollbar.thumb._top = LX.UTILS.clamp( value, 0, ( scrollBarHeight - scrollThumbHeight ) );
|
|
2592
|
+
this.vScrollbar.thumb.style.top = this.vScrollbar.thumb._top + "px";
|
|
2593
|
+
|
|
2594
|
+
// Scroll code
|
|
2404
2595
|
|
|
2405
|
-
this.
|
|
2406
|
-
|
|
2596
|
+
const scrollHeight = this.codeScroller.scrollHeight - this.codeScroller.clientHeight;
|
|
2597
|
+
const currentScroll = (this.vScrollbar.thumb._top * scrollHeight) / ( scrollBarHeight - scrollThumbHeight );
|
|
2598
|
+
this.codeScroller.scrollTop = currentScroll;
|
|
2407
2599
|
}
|
|
2408
2600
|
|
|
2409
2601
|
getCharAtPos( cursor, offset = 0 ) {
|
|
2410
2602
|
|
|
2411
|
-
cursor = cursor ?? this.cursors.children[0];
|
|
2412
|
-
return this.code.lines[cursor.line][cursor.position + offset];
|
|
2603
|
+
cursor = cursor ?? this.cursors.children[ 0 ];
|
|
2604
|
+
return this.code.lines[ cursor.line ][cursor.position + offset];
|
|
2413
2605
|
}
|
|
2414
2606
|
|
|
2415
2607
|
getWordAtPos( cursor, offset = 0 ) {
|
|
2416
2608
|
|
|
2417
|
-
cursor = cursor ?? this.cursors.children[0];
|
|
2609
|
+
cursor = cursor ?? this.cursors.children[ 0 ];
|
|
2418
2610
|
const col = cursor.line;
|
|
2419
2611
|
const words = this.code.lines[col];
|
|
2420
2612
|
|
|
@@ -2449,7 +2641,7 @@ class CodeEditor {
|
|
|
2449
2641
|
var rect = test.getBoundingClientRect();
|
|
2450
2642
|
deleteElement( test );
|
|
2451
2643
|
const bb = [Math.floor(rect.width), Math.floor(rect.height)];
|
|
2452
|
-
return get_bb ? bb : bb[0];
|
|
2644
|
+
return get_bb ? bb : bb[ 0 ];
|
|
2453
2645
|
}
|
|
2454
2646
|
|
|
2455
2647
|
measureString( str ) {
|
|
@@ -2462,10 +2654,10 @@ class CodeEditor {
|
|
|
2462
2654
|
var script = document.createElement('script');
|
|
2463
2655
|
script.type = 'module';
|
|
2464
2656
|
script.innerHTML = code;
|
|
2465
|
-
// script.src = url[i] + ( version ? "?version=" + version : "" );
|
|
2657
|
+
// script.src = url[ i ] + ( version ? "?version=" + version : "" );
|
|
2466
2658
|
script.async = false;
|
|
2467
2659
|
// script.onload = function(e) { };
|
|
2468
|
-
document.getElementsByTagName('head')[0].appendChild(script);
|
|
2660
|
+
document.getElementsByTagName('head')[ 0 ].appendChild(script);
|
|
2469
2661
|
}
|
|
2470
2662
|
|
|
2471
2663
|
toJSONFormat( text ) {
|
|
@@ -2473,19 +2665,19 @@ class CodeEditor {
|
|
|
2473
2665
|
let params = text.split(":");
|
|
2474
2666
|
|
|
2475
2667
|
for(let i = 0; i < params.length; i++) {
|
|
2476
|
-
let key = params[i].split(',');
|
|
2668
|
+
let key = params[ i ].split(',');
|
|
2477
2669
|
if(key.length > 1) {
|
|
2478
2670
|
if(key[key.length-1].includes("]"))
|
|
2479
2671
|
continue;
|
|
2480
2672
|
key = key[key.length-1];
|
|
2481
2673
|
}
|
|
2482
|
-
else if(key[0].includes("}"))
|
|
2674
|
+
else if(key[ 0 ].includes("}"))
|
|
2483
2675
|
continue;
|
|
2484
2676
|
else
|
|
2485
|
-
key = key[0];
|
|
2677
|
+
key = key[ 0 ];
|
|
2486
2678
|
key = key.replaceAll(/[{}\n\r]/g,"").replaceAll(" ","")
|
|
2487
|
-
if(key[0] != '"' && key[key.length - 1] != '"') {
|
|
2488
|
-
params[i] = params[i].replace(key, '"' + key + '"');
|
|
2679
|
+
if(key[ 0 ] != '"' && key[key.length - 1] != '"') {
|
|
2680
|
+
params[ i ] = params[ i ].replace(key, '"' + key + '"');
|
|
2489
2681
|
}
|
|
2490
2682
|
}
|
|
2491
2683
|
|
|
@@ -2515,11 +2707,11 @@ class CodeEditor {
|
|
|
2515
2707
|
|
|
2516
2708
|
// Add language special keys...
|
|
2517
2709
|
suggestions = suggestions.concat(
|
|
2518
|
-
Object.keys( this.builtin[ this.highlight ]
|
|
2519
|
-
Object.keys( this.keywords[ this.highlight ]
|
|
2520
|
-
Object.keys( this.statementsAndDeclarations[ this.highlight ]
|
|
2521
|
-
Object.keys( this.types[ this.highlight ]
|
|
2522
|
-
Object.keys( this.utils[ this.highlight ]
|
|
2710
|
+
Object.keys( this.builtin[ this.highlight ] ?? {} ),
|
|
2711
|
+
Object.keys( this.keywords[ this.highlight ] ?? {} ),
|
|
2712
|
+
Object.keys( this.statementsAndDeclarations[ this.highlight ] ?? {} ),
|
|
2713
|
+
Object.keys( this.types[ this.highlight ] ?? {} ),
|
|
2714
|
+
Object.keys( this.utils[ this.highlight ] ?? {} )
|
|
2523
2715
|
);
|
|
2524
2716
|
|
|
2525
2717
|
// Add words in current tab plus remove current word
|
|
@@ -2622,7 +2814,7 @@ class CodeEditor {
|
|
|
2622
2814
|
|
|
2623
2815
|
for( let i = 0; i < this.autocomplete.childElementCount; ++i )
|
|
2624
2816
|
{
|
|
2625
|
-
const child = this.autocomplete.childNodes[i];
|
|
2817
|
+
const child = this.autocomplete.childNodes[ i ];
|
|
2626
2818
|
if( child.classList.contains('selected') )
|
|
2627
2819
|
{
|
|
2628
2820
|
var word = "";
|