diff --git a/README.md b/README.md index 17833598..551a94f4 100644 --- a/README.md +++ b/README.md @@ -171,6 +171,7 @@ computational environment for Jupyter, supporting interactive data science and s - [**SSH Web Client**](https://github.com/roke22/PHP-SSH2-Web-Client): SSH Web Client with PHP. - [**Shellvault**](https://www.shellvault.io): The cloud-based SSH terminal you can access from anywhere. - [**Juno**](http://junolab.org/): A flexible Julia IDE, based on Atom. +- [**webssh**](https://github.com/huashengdun/webssh): Web based ssh client. [And much more...](https://github.com/xtermjs/xterm.js/network/dependents) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index e4314330..aaaf57c3 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -438,6 +438,36 @@ describe('InputHandler', () => { termNew.buffer.x = 40; inputHandlerNew.eraseInDisplay([2]); expect(termContent(termNew)).eql(termContent(termOld)); + + // reset and add a wrapped line + termNew.buffer.y = 0; + termNew.buffer.x = 0; + inputHandlerNew.parse(Array(termNew.cols + 1).join('a')); // line 0 + inputHandlerNew.parse(Array(termNew.cols + 10).join('a')); // line 1 and 2 + for (let i = 3; i < termOld.rows; ++i) inputHandlerNew.parse(Array(termNew.cols + 1).join('a')); + + // params[1] left and above with wrap + // confirm precondition that line 2 is wrapped + expect(termNew.buffer.lines.get(2).isWrapped).true; + termNew.buffer.y = 2; + termNew.buffer.x = 40; + inputHandlerNew.eraseInDisplay([1]); + expect(termNew.buffer.lines.get(2).isWrapped).false; + + // reset and add a wrapped line + termNew.buffer.y = 0; + termNew.buffer.x = 0; + inputHandlerNew.parse(Array(termNew.cols + 1).join('a')); // line 0 + inputHandlerNew.parse(Array(termNew.cols + 10).join('a')); // line 1 and 2 + for (let i = 3; i < termOld.rows; ++i) inputHandlerNew.parse(Array(termNew.cols + 1).join('a')); + + // params[1] left and above with wrap + // confirm precondition that line 2 is wrapped + expect(termNew.buffer.lines.get(2).isWrapped).true; + termNew.buffer.y = 1; + termNew.buffer.x = 90; // Cursor is beyond last column + inputHandlerNew.eraseInDisplay([1]); + expect(termNew.buffer.lines.get(2).isWrapped).false; }); }); it('convertEol setting', function(): void { diff --git a/src/InputHandler.ts b/src/InputHandler.ts index ddb20527..091f41cf 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -24,26 +24,6 @@ const GLEVEL: {[key: string]: number} = {'(': 0, ')': 1, '*': 2, '+': 3, '-': 1, * DCS subparser implementations */ - /** - * DCS + q Pt ST (xterm) - * Request Terminfo String - * not supported - */ -class RequestTerminfo implements IDcsHandler { - private _data: string; - constructor(private _terminal: any) { } - hook(collect: string, params: number[], flag: number): void { - this._data = ''; - } - put(data: string, start: number, end: number): void { - this._data += data.substring(start, end); - } - unhook(): void { - // invalid: DCS 0 + r Pt ST - this._terminal.handler(`${C0.ESC}P0+r${this._data}${C0.ESC}\\`); - } -} - /** * DCS $ q Pt ST * DECRQSS (https://vt100.net/docs/vt510-rm/DECRQSS.html) @@ -86,7 +66,7 @@ class DECRQSS implements IDcsHandler { default: // invalid: DCS 0 $ r Pt ST (xterm) this._terminal.error('Unknown DCS $q %s', this._data); - this._terminal.handler(`${C0.ESC}P0$r${this._data}${C0.ESC}\\`); + this._terminal.handler(`${C0.ESC}P0$r${C0.ESC}\\`); } } } @@ -287,7 +267,6 @@ export class InputHandler extends Disposable implements IInputHandler { * DCS handler */ this._parser.setDcsHandler('$q', new DECRQSS(this._terminal)); - this._parser.setDcsHandler('+q', new RequestTerminfo(this._terminal)); } public dispose(): void { @@ -722,12 +701,25 @@ export class InputHandler extends Disposable implements IInputHandler { * @param start first cell index to be erased * @param end end - 1 is last erased cell */ - private _eraseInBufferLine(y: number, start: number, end: number): void { - this._terminal.buffer.lines.get(this._terminal.buffer.ybase + y).replaceCells( + private _eraseInBufferLine(y: number, start: number, end: number, clearWrap: boolean = false): void { + const line = this._terminal.buffer.lines.get(this._terminal.buffer.ybase + y); + line.replaceCells( start, end, [this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE] ); + if (clearWrap) { + line.isWrapped = false; + } + } + + /** + * Helper method to reset cells in a terminal row. + * The cell gets replaced with the eraseChar of the terminal and the isWrapped property is set to false. + * @param y row index + */ + private _resetBufferLine(y: number): void { + this._eraseInBufferLine(y, 0, this._terminal.cols, true); } /** @@ -748,18 +740,23 @@ export class InputHandler extends Disposable implements IInputHandler { case 0: j = this._terminal.buffer.y; this._terminal.updateRange(j); - this._eraseInBufferLine(j++, this._terminal.buffer.x, this._terminal.cols); + this._eraseInBufferLine(j++, this._terminal.buffer.x, this._terminal.cols, this._terminal.buffer.x === 0); for (; j < this._terminal.rows; j++) { - this._eraseInBufferLine(j, 0, this._terminal.cols); + this._resetBufferLine(j); } this._terminal.updateRange(j); break; case 1: j = this._terminal.buffer.y; this._terminal.updateRange(j); - this._eraseInBufferLine(j, 0, this._terminal.buffer.x + 1); + // Deleted front part of line and everything before. This line will no longer be wrapped. + this._eraseInBufferLine(j, 0, this._terminal.buffer.x + 1, true); + if (this._terminal.buffer.x + 1 >= this._terminal.cols) { + // Deleted entire previous line. This next line can no longer be wrapped. + this._terminal.buffer.lines.get(j + 1).isWrapped = false; + } while (j--) { - this._eraseInBufferLine(j, 0, this._terminal.cols); + this._resetBufferLine(j); } this._terminal.updateRange(0); break; @@ -767,7 +764,7 @@ export class InputHandler extends Disposable implements IInputHandler { j = this._terminal.rows; this._terminal.updateRange(j - 1); while (j--) { - this._eraseInBufferLine(j, 0, this._terminal.cols); + this._resetBufferLine(j); } this._terminal.updateRange(0); break; diff --git a/src/Terminal.ts b/src/Terminal.ts index 516aa2a0..9e79f3a8 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -346,7 +346,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II } public get isFocused(): boolean { - return document.activeElement === this.textarea; + return document.activeElement === this.textarea && document.hasFocus(); } /** @@ -1179,6 +1179,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II * @param isWrapped Whether the new line is wrapped from the previous line. */ public scroll(isWrapped?: boolean): void { +<<<<<<< HEAD let newLine: IBufferLine; const useRecycling = this.options.experimentalPushRecycling; if (useRecycling) { @@ -1192,6 +1193,9 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II newLine = this.buffer.getBlankLine(DEFAULT_ATTR, isWrapped); } +======= + const newLine = this.buffer.getBlankLine(this.eraseAttr(), isWrapped); +>>>>>>> master const topRow = this.buffer.ybase + this.buffer.scrollTop; const bottomRow = this.buffer.ybase + this.buffer.scrollBottom; diff --git a/src/addons/search/SearchHelper.ts b/src/addons/search/SearchHelper.ts index 938887d8..7919932a 100644 --- a/src/addons/search/SearchHelper.ts +++ b/src/addons/search/SearchHelper.ts @@ -195,7 +195,7 @@ export class SearchHelper implements ISearchHelper { do { const nextLine = this._terminal._core.buffer.lines.get(lineIndex + 1); lineWrapsToNext = nextLine ? nextLine.isWrapped : false; - lineString += this._terminal._core.buffer.translateBufferLineToString(lineIndex, !lineWrapsToNext && trimRight); + lineString += this._terminal._core.buffer.translateBufferLineToString(lineIndex, !lineWrapsToNext && trimRight).substring(0, this._terminal.cols); lineIndex++; } while (lineWrapsToNext); diff --git a/src/addons/search/search.test.ts b/src/addons/search/search.test.ts index 1fe18b6a..243e555c 100644 --- a/src/addons/search/search.test.ts +++ b/src/addons/search/search.test.ts @@ -81,6 +81,9 @@ describe('search addon', () => { expect(hello3).eql(undefined); expect(llo).eql(undefined); expect(goodbye).eql({col: 0, row: 5, term: 'goodbye'}); + term.core.resize(9, 5); + const hello0Resize = term.searchHelper.findInLine('Hello', 0); + expect(hello0Resize).eql({col: 8, row: 0, term: 'Hello'}); }); it('should respect search regex', () => { search.apply(MockTerminal); diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts index 8f51c4ef..adce57bc 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/renderer/dom/DomRenderer.ts @@ -352,7 +352,11 @@ export class DomRenderer extends EventEmitter implements IRenderer { private _setCellUnderline(x: number, x2: number, y: number, y2: number, cols: number, enabled: boolean): void { while (x !== x2 || y !== y2) { - const span = this._rowElements[y].children[x]; + const row = this._rowElements[y]; + if (!row) { + return; + } + const span = row.children[x]; span.style.textDecoration = enabled ? 'underline' : 'none'; x = (x + 1) % cols; if (x === 0) {