@finos/legend-application 10.2.11 → 10.2.13

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.
Files changed (78) hide show
  1. package/lib/application/LegendApplication.d.ts.map +1 -1
  2. package/lib/application/LegendApplication.js +1 -2
  3. package/lib/application/LegendApplication.js.map +1 -1
  4. package/lib/components/ActionAlert.d.ts +1 -0
  5. package/lib/components/ActionAlert.d.ts.map +1 -1
  6. package/lib/components/BlockingAlert.d.ts +1 -0
  7. package/lib/components/BlockingAlert.d.ts.map +1 -1
  8. package/lib/components/LegendApplicationComponentFrameworkProvider.d.ts +6 -0
  9. package/lib/components/LegendApplicationComponentFrameworkProvider.d.ts.map +1 -1
  10. package/lib/components/LegendApplicationComponentFrameworkProvider.js +21 -13
  11. package/lib/components/LegendApplicationComponentFrameworkProvider.js.map +1 -1
  12. package/lib/components/NotificationManager.d.ts +1 -0
  13. package/lib/components/NotificationManager.d.ts.map +1 -1
  14. package/lib/components/VirtualAssistant.d.ts +1 -0
  15. package/lib/components/VirtualAssistant.d.ts.map +1 -1
  16. package/lib/components/shared/TabManager.d.ts +1 -0
  17. package/lib/components/shared/TabManager.d.ts.map +1 -1
  18. package/lib/components/shared/TextSearchAdvancedConfigMenu.d.ts +1 -0
  19. package/lib/components/shared/TextSearchAdvancedConfigMenu.d.ts.map +1 -1
  20. package/lib/const.d.ts +1 -1
  21. package/lib/const.d.ts.map +1 -1
  22. package/lib/const.js +1 -1
  23. package/lib/const.js.map +1 -1
  24. package/lib/index.css +2 -2
  25. package/lib/index.css.map +1 -1
  26. package/lib/index.d.ts +1 -0
  27. package/lib/index.d.ts.map +1 -1
  28. package/lib/index.js +1 -0
  29. package/lib/index.js.map +1 -1
  30. package/lib/stores/ApplicationEvent.d.ts +1 -0
  31. package/lib/stores/ApplicationEvent.d.ts.map +1 -1
  32. package/lib/stores/ApplicationEvent.js +1 -0
  33. package/lib/stores/ApplicationEvent.js.map +1 -1
  34. package/lib/stores/ApplicationStore.d.ts +2 -0
  35. package/lib/stores/ApplicationStore.d.ts.map +1 -1
  36. package/lib/stores/ApplicationStore.js +3 -0
  37. package/lib/stores/ApplicationStore.js.map +1 -1
  38. package/lib/stores/CommandCenter.d.ts +1 -0
  39. package/lib/stores/CommandCenter.d.ts.map +1 -1
  40. package/lib/stores/CommandCenter.js.map +1 -1
  41. package/lib/stores/KeyboardShortcutsService.d.ts +4 -10
  42. package/lib/stores/KeyboardShortcutsService.d.ts.map +1 -1
  43. package/lib/stores/KeyboardShortcutsService.js +36 -33
  44. package/lib/stores/KeyboardShortcutsService.js.map +1 -1
  45. package/lib/stores/LegendApplicationDocumentation.d.ts +1 -0
  46. package/lib/stores/LegendApplicationDocumentation.d.ts.map +1 -1
  47. package/lib/stores/LegendApplicationDocumentation.js +1 -0
  48. package/lib/stores/LegendApplicationDocumentation.js.map +1 -1
  49. package/lib/stores/PureLanguageSupport.d.ts.map +1 -1
  50. package/lib/stores/PureLanguageSupport.js +14 -1
  51. package/lib/stores/PureLanguageSupport.js.map +1 -1
  52. package/lib/stores/TerminalService.d.ts +23 -0
  53. package/lib/stores/TerminalService.d.ts.map +1 -0
  54. package/lib/stores/TerminalService.js +25 -0
  55. package/lib/stores/TerminalService.js.map +1 -0
  56. package/lib/stores/terminal/Terminal.d.ts +155 -0
  57. package/lib/stores/terminal/Terminal.d.ts.map +1 -0
  58. package/lib/stores/terminal/Terminal.js +171 -0
  59. package/lib/stores/terminal/Terminal.js.map +1 -0
  60. package/lib/stores/terminal/XTerm.d.ts +91 -0
  61. package/lib/stores/terminal/XTerm.d.ts.map +1 -0
  62. package/lib/stores/terminal/XTerm.js +693 -0
  63. package/lib/stores/terminal/XTerm.js.map +1 -0
  64. package/package.json +19 -13
  65. package/src/application/LegendApplication.tsx +5 -2
  66. package/src/components/LegendApplicationComponentFrameworkProvider.tsx +24 -18
  67. package/src/const.ts +1 -1
  68. package/src/index.ts +1 -0
  69. package/src/stores/ApplicationEvent.ts +1 -0
  70. package/src/stores/ApplicationStore.ts +4 -1
  71. package/src/stores/CommandCenter.ts +1 -0
  72. package/src/stores/KeyboardShortcutsService.ts +43 -48
  73. package/src/stores/LegendApplicationDocumentation.ts +1 -0
  74. package/src/stores/PureLanguageSupport.ts +15 -1
  75. package/src/stores/TerminalService.ts +30 -0
  76. package/src/stores/terminal/Terminal.ts +259 -0
  77. package/src/stores/terminal/XTerm.ts +880 -0
  78. package/tsconfig.json +3 -0
@@ -0,0 +1,693 @@
1
+ /**
2
+ * Copyright (c) 2020-present, Goldman Sachs
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import { Terminal as XTermTerminal, } from 'xterm';
17
+ import { WebLinksAddon as XTermWebLinksAddon } from 'xterm-addon-web-links';
18
+ import { FitAddon as XTermFitAddon } from 'xterm-addon-fit';
19
+ import { SearchAddon as XTermSearchAddon, } from 'xterm-addon-search';
20
+ import { Unicode11Addon as XTermUnicode11Addon } from 'xterm-addon-unicode11';
21
+ import { WebglAddon as XTermWebglAddon } from 'xterm-addon-webgl';
22
+ import { MONOSPACED_FONT_FAMILY, TAB_SIZE } from '../../const.js';
23
+ import { Terminal, DISPLAY_ANSI_ESCAPE, ANSI_moveCursor, } from './Terminal.js';
24
+ import { ActionState, guaranteeNonNullable, IllegalStateError, isMatchingKeyCombination, LogEvent, prettyCONSTName, uniqBy, } from '@finos/legend-shared';
25
+ import { APPLICATION_EVENT } from '../ApplicationEvent.js';
26
+ import { forceDispatchKeyboardEvent } from '../../components/LegendApplicationComponentFrameworkProvider.js';
27
+ const LEGEND_XTERM_THEME = {
28
+ foreground: '#cccccc',
29
+ background: '#1e1e1e',
30
+ cursor: '#cccccc',
31
+ /** The accent color of the cursor (fg color for a block cursor) */
32
+ // cursorAccent?: string;
33
+ /** The selection background color when the terminal does not have focus (can be transparent) */
34
+ // selectionInactiveBackground?: string;
35
+ selectionBackground: '#264f78',
36
+ black: '#000000',
37
+ red: '#cd3131',
38
+ green: '#0dbc79',
39
+ yellow: '#e5e510',
40
+ blue: '#2472c8',
41
+ magenta: '#bc3fbc',
42
+ cyan: '#11a8cd',
43
+ white: '#e5e5e5',
44
+ brightBlack: '#666666',
45
+ brightRed: '#f14c4c',
46
+ brightGreen: '#23d18b',
47
+ brightYellow: '#f5f543',
48
+ brightBlue: '#3b8eea',
49
+ brightMagenta: '#d670d6',
50
+ brightCyan: '#29b8db',
51
+ brightWhite: '#e5e5e5',
52
+ };
53
+ const LEGEND_XTERM_SEARCH_THEME = {
54
+ matchOverviewRuler: '#d186167e',
55
+ activeMatchColorOverviewRuler: '#A0A0A0CC',
56
+ matchBackground: '#62331c',
57
+ activeMatchBackground: '#515C6A',
58
+ };
59
+ // robot acsii art
60
+ // See https://asciiartist.com/ascii-art-micro-robot/
61
+ const getHelpCommandContent = (commandRegistry) => `
62
+ ${DISPLAY_ANSI_ESCAPE.BRIGHT_BLACK}+-------------------------------------------------------+${DISPLAY_ANSI_ESCAPE.RESET}
63
+ ${DISPLAY_ANSI_ESCAPE.BRIGHT_BLACK}|${DISPLAY_ANSI_ESCAPE.RESET} ${DISPLAY_ANSI_ESCAPE.BRIGHT_GREEN}[@@]${DISPLAY_ANSI_ESCAPE.RESET} "Hi! Welcome to the HELP menu of Pure IDE" ${DISPLAY_ANSI_ESCAPE.BRIGHT_BLACK}|${DISPLAY_ANSI_ESCAPE.RESET}
64
+ ${DISPLAY_ANSI_ESCAPE.BRIGHT_BLACK}|${DISPLAY_ANSI_ESCAPE.RESET} ${DISPLAY_ANSI_ESCAPE.BRIGHT_GREEN}/|__|\\${DISPLAY_ANSI_ESCAPE.RESET} ${DISPLAY_ANSI_ESCAPE.BRIGHT_BLACK}|${DISPLAY_ANSI_ESCAPE.RESET}
65
+ ${DISPLAY_ANSI_ESCAPE.BRIGHT_BLACK}+--${DISPLAY_ANSI_ESCAPE.RESET} ${DISPLAY_ANSI_ESCAPE.BRIGHT_GREEN}d b${DISPLAY_ANSI_ESCAPE.RESET} ${DISPLAY_ANSI_ESCAPE.BRIGHT_BLACK}-----------------------------------------------+${DISPLAY_ANSI_ESCAPE.RESET}
66
+
67
+ Following is the list of supported commands:
68
+
69
+ ${uniqBy(Array.from(commandRegistry.values()), (config) => config.command)
70
+ .map((config) => `${DISPLAY_ANSI_ESCAPE.BRIGHT_GREEN}${config.command.padEnd(30)}${DISPLAY_ANSI_ESCAPE.RESET}${config.description}${config.aliases?.length
71
+ ? `\n${''.padEnd(30)}Aliases: ${config.aliases.join(', ')}`
72
+ : ''}\n${''.padEnd(30)}Usage: ${DISPLAY_ANSI_ESCAPE.DIM}${config.usage}${DISPLAY_ANSI_ESCAPE.RESET}`)
73
+ .join('\n')}`;
74
+ const getCommonANSIEscapeSequencesForStyling = () => `
75
+ Common ANSI Escape Sequences for Styling:
76
+
77
+ ${Object.entries(DISPLAY_ANSI_ESCAPE)
78
+ .map(([key, value]) => `${value}${prettyCONSTName(key).padEnd(20)}${DISPLAY_ANSI_ESCAPE.RESET
79
+ // NOTE: since these are recommended ANSI escape sequences which can be used
80
+ // by users in strings input in Pure IDE, they have to be Unicode escape, if we send
81
+ // the original hexadecimal escape as part of the string, some string escape handling
82
+ // in Pure seems to escape the leading slash of the ANSI escape sequence \x1B; however
83
+ // this is not the case of the escape sequence for Unicode, \u001b hence our logic here
84
+ } ${value.replace('\x1b', '\\u001b')}`)
85
+ .join('\n')}`;
86
+ const DEFAULT_USER = 'purist';
87
+ const DEFAULT_COMMAND_HEADER = `
88
+ ${DISPLAY_ANSI_ESCAPE.BOLD}${DISPLAY_ANSI_ESCAPE.BRIGHT_BLUE}$${DEFAULT_USER}${DISPLAY_ANSI_ESCAPE.RESET}
89
+ ${DISPLAY_ANSI_ESCAPE.BOLD}${DISPLAY_ANSI_ESCAPE.MAGENTA}\u276f${DISPLAY_ANSI_ESCAPE.RESET} `;
90
+ const COMMAND_START = '\u276f ';
91
+ export class XTerm extends Terminal {
92
+ instance;
93
+ resizer;
94
+ renderer;
95
+ searcher;
96
+ webLinkProvider;
97
+ _TEMPORARY__onKeyListener;
98
+ _TEMPORARY__onDataListener;
99
+ command = '';
100
+ commandHistory = [];
101
+ currentCommandSearchString = '';
102
+ commandHistoryNavigationIdx = undefined;
103
+ isRunningCommand = false;
104
+ setupState = ActionState.create();
105
+ constructor(applicationStore) {
106
+ super(applicationStore);
107
+ this.instance = new XTermTerminal({
108
+ allowProposedApi: true,
109
+ fontSize: 12,
110
+ letterSpacing: 2,
111
+ fontWeight: 400,
112
+ fontWeightBold: 700,
113
+ fontFamily: `"${MONOSPACED_FONT_FAMILY}", Menlo, Consolas, monospace`,
114
+ tabStopWidth: TAB_SIZE,
115
+ theme: LEGEND_XTERM_THEME,
116
+ overviewRulerWidth: 14,
117
+ scrollback: 10000,
118
+ convertEol: true,
119
+ // this is needed so we can control the cursor programmatically using escape sequences
120
+ scrollOnUserInput: false,
121
+ });
122
+ this.resizer = new XTermFitAddon();
123
+ this.searcher = new XTermSearchAddon();
124
+ this.renderer = new XTermWebglAddon();
125
+ }
126
+ setup(configuration) {
127
+ if (this.setupState.hasCompleted) {
128
+ throw new IllegalStateError(`Terminal is already set up`);
129
+ }
130
+ this.setupState.complete();
131
+ // Handling context loss: The browser may drop WebGL contexts for various reasons like OOM or after the system has been suspended.
132
+ // An easy, but suboptimal way, to handle this is by disposing of WebglAddon when the `webglcontextlost` event fires
133
+ // NOTE: we don't really have a resilient way to fallback right now, hopefully, the fallback is to render in DOM
134
+ this.renderer.onContextLoss(() => {
135
+ this.renderer.dispose();
136
+ });
137
+ this.instance.loadAddon(this.resizer);
138
+ this.instance.loadAddon(this.searcher);
139
+ this.instance.loadAddon(this.renderer);
140
+ this.instance.loadAddon(new XTermUnicode11Addon());
141
+ this.instance.unicode.activeVersion = '11';
142
+ // NOTE: since we render the terminal using webgl/canvas, event is not bubbled
143
+ // naturally through the DOM tree, we have to manually force this
144
+ this.instance.attachCustomKeyEventHandler((event) => {
145
+ // NOTE: this is a cheap way to handle hotkey, but this is really the only
146
+ // hotkey we want to support at local scope of the terminal
147
+ // also, since here we have prevent default and stop propagation, we have to do
148
+ // this here instead at in `onKey` handler
149
+ if (isMatchingKeyCombination(event, 'Control+KeyF') ||
150
+ isMatchingKeyCombination(event, 'Meta+KeyF')) {
151
+ // prevent default so as to not trigger browser platform search command
152
+ event.preventDefault();
153
+ event.stopPropagation();
154
+ this.searchConfig.focus();
155
+ return false;
156
+ }
157
+ return true; // return true to indicate the event should still be handled by xterm
158
+ });
159
+ this.webLinkProvider = configuration?.webLinkProvider
160
+ ? new XTermWebLinksAddon(configuration.webLinkProvider.handler, {
161
+ urlRegex: configuration.webLinkProvider.regex,
162
+ })
163
+ : new XTermWebLinksAddon();
164
+ this.instance.loadAddon(this.webLinkProvider);
165
+ (configuration?.commands ?? []).forEach((commandConfig) => {
166
+ [commandConfig.command, ...(commandConfig.aliases ?? [])].forEach((command) => {
167
+ if (!this.commandRegistry.has(command)) {
168
+ this.commandRegistry.set(command, commandConfig);
169
+ }
170
+ else {
171
+ this.applicationStore.log.warn(LogEvent.create(APPLICATION_EVENT.APPLICATION_TERMINAL_COMMAND_CONFIGURATION_CHECK_FAILURE), `Found multiple duplicated terminal commands '${command}'`);
172
+ }
173
+ });
174
+ });
175
+ this.searcher.onDidChangeResults((result) => {
176
+ if (result) {
177
+ this.setSearchResultCount(result.resultCount);
178
+ this.setSearchCurrentResultIndex(result.resultIndex);
179
+ }
180
+ else {
181
+ this.setSearchResultCount(undefined);
182
+ this.setSearchCurrentResultIndex(undefined);
183
+ }
184
+ });
185
+ // NOTE: `xterm` expects to be attached to a proper terminal program which handles
186
+ // input, since we can't do that yet, we implement a fairly basic input handling flow
187
+ // See https://github.com/xtermjs/xterm.js/issues/617#issuecomment-288849502
188
+ this._TEMPORARY__onKeyListener = this.instance.onKey(({ key, domEvent }) => {
189
+ // take care of command history navigation
190
+ if (domEvent.code === 'ArrowUp') {
191
+ this.setCommandFromHistory(this.commandHistoryNavigationIdx !== undefined
192
+ ? this.commandHistoryNavigationIdx + 1
193
+ : 0);
194
+ return;
195
+ // reset current command in place
196
+ }
197
+ else if (domEvent.code === 'ArrowDown') {
198
+ if (this.commandHistoryNavigationIdx !== undefined) {
199
+ this.setCommandFromHistory(this.commandHistoryNavigationIdx === 0
200
+ ? undefined
201
+ : this.commandHistoryNavigationIdx - 1);
202
+ }
203
+ return;
204
+ }
205
+ else {
206
+ // reset navigation history the moment any other key is pressed
207
+ this.commandHistoryNavigationIdx = undefined;
208
+ }
209
+ if (domEvent.code === 'Enter') {
210
+ // run command
211
+ if (this.command.trim()) {
212
+ const text = this.command;
213
+ const [command, ...args] = text.replaceAll(/\s+/g, ' ').split(' ');
214
+ this.addCommandToHistory(this.command);
215
+ if (!command) {
216
+ return;
217
+ }
218
+ const matchingCommand = this.commandRegistry.get(command);
219
+ if (!matchingCommand) {
220
+ this.fail(`command not found: ${command}`);
221
+ return;
222
+ }
223
+ if (this.isRunningCommand) {
224
+ return;
225
+ }
226
+ this.isRunningCommand = true;
227
+ matchingCommand
228
+ .handler(args.map((arg) => arg.trim()), command, text)
229
+ .finally(() => {
230
+ this.isRunningCommand = false;
231
+ if (!this.isFlushed) {
232
+ this.abort();
233
+ }
234
+ });
235
+ }
236
+ }
237
+ else if (isMatchingKeyCombination(domEvent, 'Control+KeyC') ||
238
+ isMatchingKeyCombination(domEvent, 'Control+KeyD')) {
239
+ // abort command
240
+ this.abort();
241
+ }
242
+ else if (domEvent.code === 'Backspace') {
243
+ // Alt: jump word only, Ctrl: jump to end
244
+ // this would apply for Delete, ArrowLeft, ArrowRight
245
+ this.deleteFromCommand(domEvent.altKey || domEvent.ctrlKey
246
+ ? this.computeCursorJumpMovement(true)
247
+ : -1);
248
+ }
249
+ else if (domEvent.code === 'Delete') {
250
+ this.deleteFromCommand(domEvent.altKey || domEvent.ctrlKey
251
+ ? this.computeCursorJumpMovement(false)
252
+ : 1);
253
+ }
254
+ else if (domEvent.code === 'ArrowLeft') {
255
+ const movement = this.computeCursorMovement(domEvent.altKey || domEvent.ctrlKey
256
+ ? this.computeCursorJumpMovement(true)
257
+ : -1);
258
+ // console.log('left', movement);
259
+ this.instance.scrollLines(movement.scroll);
260
+ this.instance.write(movement.seq);
261
+ }
262
+ else if (domEvent.code === 'ArrowRight') {
263
+ const movement = this.computeCursorMovement(domEvent.altKey || domEvent.ctrlKey
264
+ ? this.computeCursorJumpMovement(false)
265
+ : 1);
266
+ // console.log('right', movement);
267
+ this.instance.scrollLines(movement.scroll);
268
+ this.instance.write(movement.seq);
269
+ }
270
+ else if (
271
+ // use key here so we absolute do not allow any characters other than these
272
+ // being added to the input command
273
+ key.match(/^[A-Za-z0-9!@#$%^&*()\-_=+"':;,.<>/?[\]{}|\\~` ]$/)) {
274
+ // commonly supported keys
275
+ this.writeToCommand(key);
276
+ }
277
+ else {
278
+ // for the rest, allow the keyboard event to be bubbled to
279
+ // application keyboard shortcuts handler
280
+ forceDispatchKeyboardEvent(domEvent);
281
+ }
282
+ });
283
+ // this is needed to support copy-pasting
284
+ this._TEMPORARY__onDataListener = this.instance.onData((val) => {
285
+ // only support pasting (not meant for 1 character though) and special functions starting with special
286
+ // ANSI escape sequence
287
+ if (val.length > 1 && !val.startsWith('\x1b')) {
288
+ this.writeToCommand(val
289
+ // remove all unsupported characters, including newline
290
+ .replaceAll(/[^A-Za-z0-9!@#$%^&*()\-_=+"':;,.<>/?[\]{}|\\~` ]/g, '')
291
+ .trimEnd());
292
+ }
293
+ });
294
+ }
295
+ // NOTE: this is fairly HACKY way to detect command
296
+ // we don't really have a better solution at the moment,
297
+ // but we should come with more systematic way of persisting the start line of command
298
+ // the challenge with this is due to text-reflow
299
+ //
300
+ // there is also a quriky known issue with text-reflow and the line with cursor
301
+ // See https://github.com/xtermjs/xterm.js/issues/1941#issuecomment-463660633
302
+ getCommandRange() {
303
+ const buffer = this.instance.buffer.active;
304
+ const cols = this.instance.cols;
305
+ const commandText = `${COMMAND_START}${this.command}`;
306
+ const commandFirstLine = `${COMMAND_START}${this.command.substring(0, cols - COMMAND_START.length)}${this.command.length < cols - COMMAND_START.length
307
+ ? ' '.repeat(cols - this.command.length - COMMAND_START.length)
308
+ : ''}`;
309
+ let startY = 0;
310
+ let cursorIdx = 0;
311
+ for (let i = buffer.baseY + buffer.cursorY; i > -1; --i) {
312
+ const line = guaranteeNonNullable(buffer.getLine(i));
313
+ const lineText = line.translateToString();
314
+ if (lineText === commandFirstLine) {
315
+ startY = i;
316
+ cursorIdx +=
317
+ (i === buffer.baseY + buffer.cursorY ? buffer.cursorX : cols) -
318
+ COMMAND_START.length;
319
+ break;
320
+ }
321
+ else {
322
+ cursorIdx +=
323
+ i === buffer.baseY + buffer.cursorY ? buffer.cursorX : cols;
324
+ }
325
+ }
326
+ // start line == -1 is the rare case where the command is too long and exceeds the buffer length
327
+ // leading to incomplete command being captured
328
+ return {
329
+ startY,
330
+ startX: COMMAND_START.length,
331
+ endY: startY + (commandText.length - (commandText.length % cols)) / cols,
332
+ endX: commandText.length % cols,
333
+ cursorIdx,
334
+ };
335
+ }
336
+ computeCursorJumpMovement(back) {
337
+ const range = this.getCommandRange();
338
+ let distance = undefined;
339
+ let foundWord = false;
340
+ // scan for the boundary of the closest word to the cursor position
341
+ if (back) {
342
+ for (let i = range.cursorIdx - 1; i > -1; --i) {
343
+ const char = this.command.charAt(i);
344
+ if (char.match(/\w/)) {
345
+ if (!foundWord) {
346
+ foundWord = true;
347
+ }
348
+ }
349
+ else {
350
+ if (foundWord) {
351
+ distance = range.cursorIdx - i - 1;
352
+ break;
353
+ }
354
+ }
355
+ }
356
+ }
357
+ else {
358
+ for (let i = range.cursorIdx + 1; i < this.command.length; ++i) {
359
+ const char = this.command.charAt(i);
360
+ if (char.match(/\w/)) {
361
+ if (!foundWord) {
362
+ foundWord = true;
363
+ }
364
+ }
365
+ else {
366
+ if (foundWord) {
367
+ distance = i - range.cursorIdx - 1;
368
+ break;
369
+ }
370
+ }
371
+ }
372
+ }
373
+ if (distance === undefined) {
374
+ distance = back ? range.cursorIdx : this.command.length - range.cursorIdx;
375
+ }
376
+ return back ? -distance : distance;
377
+ }
378
+ /**
379
+ * Generate the ANSI escape sequence for new cursor position
380
+ * after being moved by the the number of cells.
381
+ *
382
+ * @param val a number (negative means cursor move leftwards)
383
+ * @param limit whether to limit the movement of the cursor by the command range
384
+ * @returns cursor movement information including the ANSI escape sequence for new cursor position and scroll distance
385
+ */
386
+ computeCursorMovement(val, limit = true) {
387
+ const buffer = this.instance.buffer.active;
388
+ const cols = this.instance.cols;
389
+ const rows = this.instance.rows;
390
+ const range = this.getCommandRange();
391
+ const maxDistance = limit
392
+ ? val < 0
393
+ ? range.cursorIdx
394
+ : this.command.length - range.cursorIdx
395
+ : val;
396
+ const distance = Math.min(Math.abs(val), maxDistance);
397
+ let newCursorX = buffer.cursorX;
398
+ let newCursorY = buffer.cursorY;
399
+ let abs_cursorY = buffer.baseY + buffer.cursorY;
400
+ if (val < 0) {
401
+ // move leftwards
402
+ newCursorX = (cols + ((buffer.cursorX - distance) % cols)) % cols;
403
+ newCursorY =
404
+ buffer.cursorY -
405
+ (distance > buffer.cursorX ? Math.ceil(distance / cols) : 0);
406
+ abs_cursorY = newCursorY + buffer.baseY;
407
+ newCursorY = Math.max(newCursorY, -1);
408
+ }
409
+ else if (val > 0) {
410
+ // move rightwards
411
+ newCursorX = (buffer.cursorX + distance) % cols;
412
+ newCursorY =
413
+ buffer.cursorY +
414
+ (buffer.cursorX + distance >= cols
415
+ ? Math.floor((buffer.cursorX + distance) / cols)
416
+ : 0);
417
+ abs_cursorY = newCursorY + buffer.baseY;
418
+ newCursorY = Math.min(newCursorY, rows - 1);
419
+ }
420
+ const scroll = abs_cursorY > buffer.viewportY + rows
421
+ ? abs_cursorY - (buffer.viewportY + rows)
422
+ : abs_cursorY < buffer.viewportY
423
+ ? abs_cursorY - buffer.viewportY
424
+ : 0;
425
+ return {
426
+ // NOTE: currently, there is a design limitation with programmatically set the cursor using escape sequence
427
+ // by design, the scrollback (everything above the viewport/ybase) is readonly, and most terminals work like this.
428
+ // So for very long command that causes an overflow, one cannot set the cursor position pass the `baseY`
429
+ // this will affect both navigation and delete/backspace behavior
430
+ // See https://github.com/xtermjs/xterm.js/issues/4405
431
+ seq: ANSI_moveCursor(newCursorY + 1, newCursorX + 1),
432
+ scroll,
433
+ };
434
+ }
435
+ /**
436
+ * Write value to command with awareness of the current cursor position
437
+ */
438
+ writeToCommand(val) {
439
+ const range = this.getCommandRange();
440
+ const left = this.command.slice(0, range.cursorIdx);
441
+ const right = this.command.slice(range.cursorIdx);
442
+ const movement = this.computeCursorMovement(val.length, false);
443
+ this.instance.scrollLines(movement.scroll);
444
+ this.instance.write(val +
445
+ right +
446
+ // update the cursor
447
+ movement.seq);
448
+ this.setCommand(left + val + right);
449
+ }
450
+ /**
451
+ * Remove number of characters from command with awareness of the current cursor position
452
+ * NOTE: negative number means backward deleting (i.e. backspace)
453
+ */
454
+ deleteFromCommand(val) {
455
+ // console.log(val);
456
+ const range = this.getCommandRange();
457
+ const maxDistance = val < 0 ? range.cursorIdx : this.command.length - range.cursorIdx;
458
+ const distance = Math.min(Math.abs(val), maxDistance);
459
+ let left;
460
+ let right;
461
+ let cursorMovement;
462
+ if (val === 0) {
463
+ return;
464
+ }
465
+ else if (val < 0) {
466
+ // remove leftwards
467
+ left = this.command.slice(0, range.cursorIdx - distance);
468
+ right = this.command.slice(range.cursorIdx, this.command.length);
469
+ cursorMovement = -distance;
470
+ }
471
+ else {
472
+ // remove rightwards
473
+ left = this.command.slice(0, range.cursorIdx);
474
+ right = this.command.slice(range.cursorIdx + distance, this.command.length);
475
+ cursorMovement = 0;
476
+ }
477
+ const movement = this.computeCursorMovement(cursorMovement);
478
+ this.instance.scrollLines(movement.scroll);
479
+ this.instance.write(
480
+ // reset cursor to start of command, basically here, we're rewriting the entire command
481
+ ANSI_moveCursor(range.startY + 1, range.startX + 1) +
482
+ left +
483
+ right +
484
+ // fill space to erase cells rendered from previous command
485
+ ' '.repeat(distance) +
486
+ // move the cursor as well
487
+ movement.seq);
488
+ this.setCommand(left + right);
489
+ }
490
+ get isSetup() {
491
+ return this.setupState.hasCompleted;
492
+ }
493
+ isFocused() {
494
+ return document.activeElement === this.instance.textarea;
495
+ }
496
+ mount(container) {
497
+ if (!this.setupState.hasCompleted) {
498
+ throw new IllegalStateError(`XTerm terminal has not been set up yet`);
499
+ }
500
+ this.instance.open(container);
501
+ }
502
+ dispose() {
503
+ this.searcher.dispose();
504
+ this.resizer.dispose();
505
+ this.renderer.dispose();
506
+ this.webLinkProvider?.dispose();
507
+ this._TEMPORARY__onKeyListener?.dispose();
508
+ this._TEMPORARY__onDataListener?.dispose();
509
+ this.instance.dispose();
510
+ }
511
+ autoResize() {
512
+ this.resizer.fit();
513
+ }
514
+ focus() {
515
+ this.instance.focus();
516
+ }
517
+ addCommandToHistory(val) {
518
+ // if this is the same as previous command, do not push it to the history stack
519
+ if (val === this.commandHistory.at(0)) {
520
+ return;
521
+ }
522
+ // history command is essentially a stack, so we only insert at the beginning
523
+ this.commandHistory.unshift(val);
524
+ }
525
+ /**
526
+ * This methods help update the current command to a command in history
527
+ * stack, it does the necessary resetting and helps properly update
528
+ * the history navigation index
529
+ */
530
+ setCommandFromHistory(idx) {
531
+ const val = idx === undefined
532
+ ? this.currentCommandSearchString
533
+ : // NOTE: only consider commands starting with the original command
534
+ // also note that empty string naturaly match all history command
535
+ this.commandHistory
536
+ .filter((command) => command.startsWith(this.currentCommandSearchString))
537
+ .at(idx);
538
+ if (val !== undefined) {
539
+ let range = this.getCommandRange();
540
+ this.instance.write(
541
+ // reset cursor to start of command and rewrite the entire command
542
+ ANSI_moveCursor(range.startY + 1, range.startX + 1) +
543
+ val.padEnd(this.command.length));
544
+ this.command = val;
545
+ range = this.getCommandRange();
546
+ this.instance.write(
547
+ // reset cursor to command end
548
+ ANSI_moveCursor(range.endY + 1, range.endX + 1));
549
+ this.commandHistoryNavigationIdx = idx;
550
+ }
551
+ }
552
+ setCommand(val) {
553
+ this.command = val;
554
+ this.currentCommandSearchString = val;
555
+ this.commandHistoryNavigationIdx = undefined;
556
+ }
557
+ newCommand() {
558
+ this.instance.write(DEFAULT_COMMAND_HEADER);
559
+ this.setCommand('');
560
+ }
561
+ newSystemCommand(command) {
562
+ // if another command is already running, we don't need to print the command header anymore
563
+ // the potential pitfall here is that we could have another process prints to the
564
+ // terminal while the command is being run. Nothing much we can do here for now.
565
+ if (!this.isRunningCommand) {
566
+ if (this.command) {
567
+ this.abort();
568
+ this.newCommand();
569
+ }
570
+ this.instance.write(`${DISPLAY_ANSI_ESCAPE.DIM}(system: ${command})\n${DISPLAY_ANSI_ESCAPE.RESET}`);
571
+ }
572
+ }
573
+ /**
574
+ * Flush the terminal screen completely
575
+ *
576
+ * Probably due to write buffer batching, calling `reset` or `clear` on xterm terminal immediately after
577
+ * write commands will not work. To solve this, we can either promisify the `reset` call or write the ANSI
578
+ * reset sequence \x1bc
579
+ */
580
+ flushScreen() {
581
+ this.instance.write('\x1bc');
582
+ this.instance.reset();
583
+ }
584
+ get isFlushed() {
585
+ const buffer = this.instance.buffer.active;
586
+ let isLastLineEmpty = true;
587
+ for (let i = buffer.baseY + buffer.cursorY; i > -1; --i) {
588
+ const line = guaranteeNonNullable(buffer.getLine(i));
589
+ const lineText = line.translateToString();
590
+ // skip empty lines
591
+ if (!lineText.trim()) {
592
+ continue;
593
+ }
594
+ else {
595
+ isLastLineEmpty = lineText !== COMMAND_START;
596
+ break;
597
+ }
598
+ }
599
+ return this.command === '' && isLastLineEmpty;
600
+ }
601
+ clear() {
602
+ this.flushScreen();
603
+ this.instance.scrollToTop();
604
+ this.newCommand();
605
+ }
606
+ resetANSIStyling() {
607
+ this.instance.write(DISPLAY_ANSI_ESCAPE.RESET);
608
+ }
609
+ showHelp() {
610
+ this.resetANSIStyling();
611
+ this.instance.scrollToBottom();
612
+ if (!this.isFlushed && !this.isRunningCommand) {
613
+ this.abort();
614
+ }
615
+ this.instance.write(getHelpCommandContent(this.commandRegistry));
616
+ this.abort();
617
+ }
618
+ showCommonANSIEscapeSequences() {
619
+ this.resetANSIStyling();
620
+ this.instance.scrollToBottom();
621
+ if (!this.isFlushed && !this.isRunningCommand) {
622
+ this.abort();
623
+ }
624
+ this.instance.write(getCommonANSIEscapeSequencesForStyling());
625
+ this.abort();
626
+ }
627
+ abort() {
628
+ this.resetANSIStyling();
629
+ this.instance.write('\n');
630
+ this.newCommand();
631
+ this.instance.scrollToBottom();
632
+ this.isRunningCommand = false;
633
+ }
634
+ fail(error, opts) {
635
+ if (opts?.systemCommand) {
636
+ this.newSystemCommand(opts.systemCommand);
637
+ }
638
+ this.instance.write(`\n${DISPLAY_ANSI_ESCAPE.RED}${error}${DISPLAY_ANSI_ESCAPE.RED}`);
639
+ this.abort();
640
+ }
641
+ output(val, opts) {
642
+ this.resetANSIStyling();
643
+ if ((!opts?.clear || this.preserveLog) && opts?.systemCommand) {
644
+ this.newSystemCommand(opts.systemCommand);
645
+ }
646
+ if (!this.preserveLog && opts?.clear) {
647
+ this.flushScreen();
648
+ }
649
+ else if (this.preserveLog || this.isRunningCommand) {
650
+ this.instance.write('\n');
651
+ }
652
+ this.instance.write(val);
653
+ this.resetANSIStyling();
654
+ this.instance.write('\n');
655
+ this.instance.scrollToBottom();
656
+ this.newCommand();
657
+ }
658
+ search(val) {
659
+ this.searcher.findNext(val, {
660
+ decorations: LEGEND_XTERM_SEARCH_THEME,
661
+ regex: this.searchConfig.useRegex,
662
+ wholeWord: this.searchConfig.matchWholeWord,
663
+ caseSensitive: this.searchConfig.matchCaseSensitive,
664
+ // do incremental search so that the expansion will be expanded the selection if it
665
+ // still matches the term the user typed.
666
+ incremental: true,
667
+ });
668
+ }
669
+ clearSearch() {
670
+ this.searcher.clearDecorations();
671
+ this.instance.clearSelection();
672
+ this.setSearchText('');
673
+ this.setSearchResultCount(undefined);
674
+ this.setSearchCurrentResultIndex(undefined);
675
+ }
676
+ findPrevious() {
677
+ this.searcher.findPrevious(this.searchConfig.searchText, {
678
+ decorations: LEGEND_XTERM_SEARCH_THEME,
679
+ regex: this.searchConfig.useRegex,
680
+ wholeWord: this.searchConfig.matchWholeWord,
681
+ caseSensitive: this.searchConfig.matchCaseSensitive,
682
+ });
683
+ }
684
+ findNext() {
685
+ this.searcher.findNext(this.searchConfig.searchText, {
686
+ decorations: LEGEND_XTERM_SEARCH_THEME,
687
+ regex: this.searchConfig.useRegex,
688
+ wholeWord: this.searchConfig.matchWholeWord,
689
+ caseSensitive: this.searchConfig.matchCaseSensitive,
690
+ });
691
+ }
692
+ }
693
+ //# sourceMappingURL=XTerm.js.map