Skip to content

Commit 52518bd

Browse files
authored
Merge branch 'master' into issue_3074_fix
2 parents dab73aa + fb43ff7 commit 52518bd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+3800
-352
lines changed

.eslintrc.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"project": [
1010
"src/browser/tsconfig.json",
1111
"src/common/tsconfig.json",
12+
"src/headless/tsconfig.json",
1213
"test/api/tsconfig.json",
1314
"test/benchmark/tsconfig.json",
1415
"addons/xterm-addon-attach/src/tsconfig.json",

README.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ We also partially support *Internet Explorer 11*, meaning xterm.js should work f
9191

9292
Xterm.js works seamlessly in [Electron](https://electronjs.org/) apps and may even work on earlier versions of the browsers. These are the versions we strive to keep working.
9393

94+
### Node.js Support
95+
96+
We also publish [`xterm-headless`](https://www.npmjs.com/package/xterm-headless) which is a stripped down version of xterm.js that runs in Node.js. An example use case for this is to keep track of a terminal's state where the process is running and using the serialize addon so it can get all state restored upon reconnection.
97+
9498
## API
9599

96100
The full API for xterm.js is contained within the [TypeScript declaration file](https://github.com/xtermjs/xterm.js/blob/master/typings/xterm.d.ts), use the branch/tag picker in GitHub (`w`) to navigate to the correct version of the API.
@@ -162,20 +166,23 @@ Xterm.js is used in several world-class applications to provide great terminal e
162166
- [**CoCalc**](https://cocalc.com/): Lots of free software pre-installed, to chat, collaborate, develop, program, publish, research, share, teach, in C++, HTML, Julia, Jupyter, LaTeX, Markdown, Python, R, SageMath, Scala, ...
163167
- [**Dank Domain**](https://www.DDgame.us/): Open source multiuser medieval game supporting old & new terminal emulation.
164168
- [**DockerStacks**](https://docker-stacks.com/): Local LAMP/LEMP development studio
165-
- [**Codecademy**](https://codecademy.com/): Uses xterm.js in its courses on Bash.
169+
- [**Codecademy**](https://codecademy.com/): Uses xterm.js in its courses on Bash.
166170
- [**Laravel Ssh Web Client**](https://github.com/roke22/Laravel-ssh-client): Laravel server inventory with ssh web client to connect at server using xterm.js
167171
- [**Repl.it**](https://repl.it): Collaborative browser based IDE with support for 50+ different languages.
168172
- [**TeleType**](https://github.com/akshaykmr/TeleType): cli tool that allows you to share your terminal online conveniently. Show off mad cli-fu, help a colleague, teach, or troubleshoot.
169173
- [**Intervue**](https://www.intervue.io): Pair programming for interviews. Multiple programming languages are supported, with results displayed by xterm.js.
170-
- [**TRASA**](https://trasa.io): Zero trust access to Web, SSH, RDP, and Database services.
174+
- [**TRASA**](https://trasa.io): Zero trust access to Web, SSH, RDP, and Database services.
171175
- [**Commas**](https://github.com/CyanSalt/commas): Commas is a hackable terminal and command runner.
172-
- [**Devtron**](https://github.com/devtron-labs/devtron): Software Delivery Workflow For Kubernetes.
176+
- [**Devtron**](https://github.com/devtron-labs/devtron): Software Delivery Workflow For Kubernetes.
173177
- [**NxShell**](https://github.com/nxshell/nxshell): An easy to use new terminal for SSH.
174178
- [**gifcast**](https://dstein64.github.io/gifcast/): Converts an asciinema cast to an animated GIF.
175179
- [**WizardWebssh**](https://gitlab.com/mikeramsey/wizardwebssh): A terminal with Pyqt5 Widget for embedding, which can be used as an ssh client to connect to your ssh servers. It is written in Python, based on tornado, paramiko, and xterm.js.
176-
- [**Wizard Assistant**](https://wizardassistant.com/): Wizard Assistant comes with advanced automation tools, preloaded common and special time-saving commands, and a built-in SSH terminal. Now you can remotely administer, troubleshoot, and analyze any system with ease.
180+
- [**Wizard Assistant**](https://wizardassistant.com/): Wizard Assistant comes with advanced automation tools, preloaded common and special time-saving commands, and a built-in SSH terminal. Now you can remotely administer, troubleshoot, and analyze any system with ease.
177181
- [**ucli**](https://github.com/tsadarsh/ucli): Command Line for everyone :family_man_woman_girl_boy: at [www.ucli.tech](https://www.ucli.tech).
178182
- [**Tess**](https://github.com/SquitchYT/Tess/): Simple Terminal Fully Customizable for Everyone.
183+
- [**HashiCorp Nomad**](https://www.nomadproject.io/): A container orchestrator with the ability to connect to remote tasks via a web interface using websockets and xterm.js.
184+
- [**TermPair**](https://github.com/cs01/termpair): View and control terminals from your browser with end-to-end encryption
185+
- [**gdbgui**](https://github.com/cs01/gdbgui): Browser-based frontend to gdb (gnu debugger)
179186
- [And much more...](https://github.com/xtermjs/xterm.js/network/dependents)
180187

181188
Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it on our list. Note: Please add any new contributions to the end of the list only.

addons/xterm-addon-search/src/SearchAddon.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export interface ISearchResult {
2323
row: number;
2424
}
2525

26-
const NON_WORD_CHARACTERS = ' ~!@#$%^&*()+`-=[]{}|\;:"\',./<>?';
26+
const NON_WORD_CHARACTERS = ' ~!@#$%^&*()+`-=[]{}|\\;:"\',./<>?';
2727
const LINES_CACHE_TIME_TO_LIVE = 15 * 1000; // 15 secs
2828

2929
export class SearchAddon implements ITerminalAddon {

addons/xterm-addon-serialize/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
},
88
"main": "lib/xterm-addon-serialize.js",
99
"types": "typings/xterm-addon-serialize.d.ts",
10+
"repository": "https://github.com/xtermjs/xterm.js",
1011
"license": "MIT",
1112
"scripts": {
1213
"build": "../../node_modules/.bin/tsc -p .",

addons/xterm-addon-serialize/src/SerializeAddon.ts

Lines changed: 76 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@
77

88
import { Terminal, ITerminalAddon, IBuffer, IBufferCell } from 'xterm';
99

10+
1011
function constrain(value: number, low: number, high: number): number {
1112
return Math.max(low, Math.min(value, high));
1213
}
1314

1415
// TODO: Refine this template class later
1516
abstract class BaseSerializeHandler {
16-
constructor(private _buffer: IBuffer) { }
17+
constructor(
18+
protected readonly _buffer: IBuffer
19+
) {
20+
}
1721

1822
public serialize(startRow: number, endRow: number): string {
1923
// we need two of them to flip between old and new cell
@@ -71,8 +75,6 @@ function equalFlags(cell1: IBufferCell, cell2: IBufferCell): boolean {
7175
&& cell1.isDim() === cell2.isDim();
7276
}
7377

74-
75-
7678
class StringSerializeHandler extends BaseSerializeHandler {
7779
private _rowIndex: number = 0;
7880
private _allRows: string[] = new Array<string>();
@@ -83,7 +85,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
8385
// we can see a full colored cell and a null cell that only have background the same style
8486
// but the information isn't preserved by null cell itself
8587
// so wee need to record it when required.
86-
private _cursorStyle: IBufferCell = this._buffer1.getNullCell();
88+
private _cursorStyle: IBufferCell = this._buffer.getNullCell();
8789

8890
// where exact the cursor styles comes from
8991
// because we can't copy the cell directly
@@ -92,16 +94,19 @@ class StringSerializeHandler extends BaseSerializeHandler {
9294
private _cursorStyleCol: number = 0;
9395

9496
// this is a null cell for reference for checking whether background is empty or not
95-
private _backgroundCell: IBufferCell = this._buffer1.getNullCell();
97+
private _backgroundCell: IBufferCell = this._buffer.getNullCell();
9698

9799
private _firstRow: number = 0;
98100
private _lastCursorRow: number = 0;
99101
private _lastCursorCol: number = 0;
100102
private _lastContentCursorRow: number = 0;
101103
private _lastContentCursorCol: number = 0;
102104

103-
constructor(private _buffer1: IBuffer, private _terminal: Terminal) {
104-
super(_buffer1);
105+
constructor(
106+
buffer: IBuffer,
107+
private readonly _terminal: Terminal
108+
) {
109+
super(buffer);
105110
}
106111

107112
protected _beforeSerialize(rows: number, start: number, end: number): void {
@@ -111,14 +116,14 @@ class StringSerializeHandler extends BaseSerializeHandler {
111116
this._firstRow = start;
112117
}
113118

114-
private _thisRowLastChar: IBufferCell = this._buffer1.getNullCell();
115-
private _thisRowLastSecondChar: IBufferCell = this._buffer1.getNullCell();
116-
private _nextRowFirstChar: IBufferCell = this._buffer1.getNullCell();
119+
private _thisRowLastChar: IBufferCell = this._buffer.getNullCell();
120+
private _thisRowLastSecondChar: IBufferCell = this._buffer.getNullCell();
121+
private _nextRowFirstChar: IBufferCell = this._buffer.getNullCell();
117122
protected _rowEnd(row: number, isLastRow: boolean): void {
118123
// if there is colorful empty cell at line end, whe must pad it back, or the the color block will missing
119124
if (this._nullCellCount > 0 && !equalBg(this._cursorStyle, this._backgroundCell)) {
120125
// use clear right to set background.
121-
this._currentRow += `\x1b[${this._nullCellCount}X`;
126+
this._currentRow += `\u001b[${this._nullCellCount}X`;
122127
}
123128

124129
let rowSeparator = '';
@@ -127,13 +132,13 @@ class StringSerializeHandler extends BaseSerializeHandler {
127132
if (!isLastRow) {
128133
// Enable BCE
129134
if (row - this._firstRow >= this._terminal.rows) {
130-
this._buffer1.getLine(this._cursorStyleRow)?.getCell(this._cursorStyleCol, this._backgroundCell);
135+
this._buffer.getLine(this._cursorStyleRow)?.getCell(this._cursorStyleCol, this._backgroundCell);
131136
}
132137

133138
// Fetch current line
134-
const currentLine = this._buffer1.getLine(row)!;
139+
const currentLine = this._buffer.getLine(row)!;
135140
// Fetch next line
136-
const nextLine = this._buffer1.getLine(row + 1)!;
141+
const nextLine = this._buffer.getLine(row + 1)!;
137142

138143
if (!nextLine.isWrapped) {
139144
// just insert the line break
@@ -187,15 +192,15 @@ class StringSerializeHandler extends BaseSerializeHandler {
187192
// insert enough character to force the wrap
188193
rowSeparator = '-'.repeat(this._nullCellCount + 1);
189194
// move back and erase next line head
190-
rowSeparator += '\x1b[1D\x1b[1X';
195+
rowSeparator += '\u001b[1D\u001b[1X';
191196

192197
if (this._nullCellCount > 0) {
193198
// do these because we filled the last several null slot, which we shouldn't
194-
rowSeparator += '\x1b[A';
195-
rowSeparator += `\x1b[${currentLine.length - this._nullCellCount}C`;
196-
rowSeparator += `\x1b[${this._nullCellCount}X`;
197-
rowSeparator += `\x1b[${currentLine.length - this._nullCellCount}D`;
198-
rowSeparator += '\x1b[B';
199+
rowSeparator += '\u001b[A';
200+
rowSeparator += `\u001b[${currentLine.length - this._nullCellCount}C`;
201+
rowSeparator += `\u001b[${this._nullCellCount}X`;
202+
rowSeparator += `\u001b[${currentLine.length - this._nullCellCount}D`;
203+
rowSeparator += '\u001b[B';
199204
}
200205

201206
// This is content and need the be serialized even it is invisible.
@@ -285,20 +290,20 @@ class StringSerializeHandler extends BaseSerializeHandler {
285290
if (this._nullCellCount > 0) {
286291
// use clear right to set background.
287292
if (!equalBg(this._cursorStyle, this._backgroundCell)) {
288-
this._currentRow += `\x1b[${this._nullCellCount}X`;
293+
this._currentRow += `\u001b[${this._nullCellCount}X`;
289294
}
290295
// use move right to move cursor.
291-
this._currentRow += `\x1b[${this._nullCellCount}C`;
296+
this._currentRow += `\u001b[${this._nullCellCount}C`;
292297
this._nullCellCount = 0;
293298
}
294299

295300
this._lastContentCursorRow = this._lastCursorRow = row;
296301
this._lastContentCursorCol = this._lastCursorCol = col;
297302

298-
this._currentRow += `\x1b[${sgrSeq.join(';')}m`;
303+
this._currentRow += `\u001b[${sgrSeq.join(';')}m`;
299304

300305
// update the last cursor style
301-
const line = this._buffer1.getLine(row);
306+
const line = this._buffer.getLine(row);
302307
if (line !== undefined) {
303308
line.getCell(col, this._cursorStyle);
304309
this._cursorStyleRow = row;
@@ -317,10 +322,10 @@ class StringSerializeHandler extends BaseSerializeHandler {
317322
// because style change is handled by previous stage
318323
// use move right when background is empty, use clear right when there is background.
319324
if (equalBg(this._cursorStyle, this._backgroundCell)) {
320-
this._currentRow += `\x1b[${this._nullCellCount}C`;
325+
this._currentRow += `\u001b[${this._nullCellCount}C`;
321326
} else {
322-
this._currentRow += `\x1b[${this._nullCellCount}X`;
323-
this._currentRow += `\x1b[${this._nullCellCount}C`;
327+
this._currentRow += `\u001b[${this._nullCellCount}X`;
328+
this._currentRow += `\u001b[${this._nullCellCount}C`;
324329
}
325330
this._nullCellCount = 0;
326331
}
@@ -338,7 +343,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
338343

339344
// the fixup is only required for data without scrollback
340345
// because it will always be placed at last line otherwise
341-
if (this._buffer1.length - this._firstRow <= this._terminal.rows) {
346+
if (this._buffer.length - this._firstRow <= this._terminal.rows) {
342347
rowEnd = this._lastContentCursorRow + 1 - this._firstRow;
343348
this._lastCursorCol = this._lastContentCursorCol;
344349
this._lastCursorRow = this._lastContentCursorRow;
@@ -354,8 +359,8 @@ class StringSerializeHandler extends BaseSerializeHandler {
354359
}
355360

356361
// restore the cursor
357-
const realCursorRow = this._buffer1.baseY + this._buffer1.cursorY;
358-
const realCursorCol = this._buffer1.cursorX;
362+
const realCursorRow = this._buffer.baseY + this._buffer.cursorY;
363+
const realCursorCol = this._buffer.cursorX;
359364

360365
const cursorMoved = (realCursorRow !== this._lastCursorRow || realCursorCol !== this._lastCursorCol);
361366

@@ -379,7 +384,6 @@ class StringSerializeHandler extends BaseSerializeHandler {
379384
moveRight(realCursorCol - this._lastCursorCol);
380385
}
381386

382-
383387
return content;
384388
}
385389
}
@@ -393,14 +397,40 @@ export class SerializeAddon implements ITerminalAddon {
393397
this._terminal = terminal;
394398
}
395399

396-
private _getString(buffer: IBuffer, scrollback?: number): string {
400+
private _serializeBuffer(terminal: Terminal, buffer: IBuffer, scrollback?: number): string {
397401
const maxRows = buffer.length;
398-
const handler = new StringSerializeHandler(buffer, this._terminal!);
402+
const handler = new StringSerializeHandler(buffer, terminal);
403+
const correctRows = (scrollback === undefined) ? maxRows : constrain(scrollback + terminal.rows, 0, maxRows);
404+
return handler.serialize(maxRows - correctRows, maxRows);
405+
}
399406

400-
const correctRows = (scrollback === undefined) ? maxRows : constrain(scrollback + this!._terminal!.rows, 0, maxRows);
401-
const result = handler.serialize(maxRows - correctRows, maxRows);
407+
private _serializeModes(terminal: Terminal): string {
408+
let content = '';
409+
const modes = terminal.modes;
410+
411+
// Default: false
412+
if (modes.applicationCursorKeysMode) content += '\x1b[?1h';
413+
if (modes.applicationKeypadMode) content += '\x1b[?66h';
414+
if (modes.bracketedPasteMode) content += '\x1b[?2004h';
415+
if (modes.insertMode) content += '\x1b[4h';
416+
if (modes.originMode) content += '\x1b[?6h';
417+
if (modes.reverseWraparoundMode) content += '\x1b[?45h';
418+
if (modes.sendFocusMode) content += '\x1b[?1004h';
419+
420+
// Default: true
421+
if (modes.wraparoundMode === false) content += '\x1b[?7l';
422+
423+
// Default: 'none'
424+
if (modes.mouseTrackingMode !== 'none') {
425+
switch (modes.mouseTrackingMode) {
426+
case 'x10': content += '\x1b[?9h'; break;
427+
case 'vt200': content += '\x1b[?1000h'; break;
428+
case 'drag': content += '\x1b[?1002h'; break;
429+
case 'any': content += '\x1b[?1003h'; break;
430+
}
431+
}
402432

403-
return result;
433+
return content;
404434
}
405435

406436
public serialize(scrollback?: number): string {
@@ -409,17 +439,19 @@ export class SerializeAddon implements ITerminalAddon {
409439
throw new Error('Cannot use addon until it has been loaded');
410440
}
411441

412-
if (this._terminal.buffer.active.type === 'normal') {
413-
return this._getString(this._terminal.buffer.active, scrollback);
442+
// Normal buffer
443+
let content = this._serializeBuffer(this._terminal, this._terminal.buffer.normal, scrollback);
444+
445+
// Alternate buffer
446+
if (this._terminal.buffer.active.type === 'alternate') {
447+
const alternativeScreenContent = this._serializeBuffer(this._terminal, this._terminal.buffer.alternate, undefined);
448+
content += `\u001b[?1049h\u001b[H${alternativeScreenContent}`;
414449
}
415450

416-
const normalScreenContent = this._getString(this._terminal.buffer.normal, scrollback);
417-
// alt screen don't have scrollback
418-
const alternativeScreenContent = this._getString(this._terminal.buffer.alternate, undefined);
451+
// Modes
452+
content += this._serializeModes(this._terminal);
419453

420-
return normalScreenContent
421-
+ '\u001b[?1049h\u001b[H'
422-
+ alternativeScreenContent;
454+
return content;
423455
}
424456

425457
public dispose(): void { }

0 commit comments

Comments
 (0)