From 18871f0d3cf3fd41b641ec5dcfb809c3ddd82657 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 1 Oct 2018 13:30:13 -0700 Subject: [PATCH 01/21] Cleared terminal lines should have isWrapped reset Fixes #1710 --- src/InputHandler.ts | 21 ++++++++++++++++++--- src/addons/search/SearchHelper.ts | 2 +- src/addons/search/search.test.ts | 3 +++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index ad5aad39..9a0aae08 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -730,6 +730,21 @@ export class InputHandler extends Disposable implements IInputHandler { ); } + /** + * 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 { + const line = this._terminal.buffer.lines.get(this._terminal.buffer.ybase + y); + line.replaceCells( + 0, + this._terminal.cols, + [this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE] + ); + line.isWrapped = false; + } + /** * CSI Ps J Erase in Display (ED). * Ps = 0 -> Erase Below (default). @@ -750,7 +765,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.updateRange(j); this._eraseInBufferLine(j++, this._terminal.buffer.x, this._terminal.cols); for (; j < this._terminal.rows; j++) { - this._eraseInBufferLine(j, 0, this._terminal.cols); + this._resetBufferLine(j); } this._terminal.updateRange(j); break; @@ -759,7 +774,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._terminal.updateRange(j); this._eraseInBufferLine(j, 0, this._terminal.buffer.x + 1); while (j--) { - this._eraseInBufferLine(j, 0, this._terminal.cols); + this._resetBufferLine(j); } this._terminal.updateRange(0); break; @@ -767,7 +782,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/addons/search/SearchHelper.ts b/src/addons/search/SearchHelper.ts index d07c6b2c..da09f83f 100644 --- a/src/addons/search/SearchHelper.ts +++ b/src/addons/search/SearchHelper.ts @@ -191,7 +191,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 ccdebaf3..b22c22a5 100644 --- a/src/addons/search/search.test.ts +++ b/src/addons/search/search.test.ts @@ -82,6 +82,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); From 0065682bd7ebda786628f16329ae88c0a5c52abd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 9 Oct 2018 21:17:33 +0200 Subject: [PATCH 02/21] resize should not alter length if shrink=false --- src/BufferLine.test.ts | 92 ++++++++++++++++++++++++++++++------------ src/BufferLine.ts | 22 +++++----- 2 files changed, 78 insertions(+), 36 deletions(-) diff --git a/src/BufferLine.test.ts b/src/BufferLine.test.ts index 2db69176..b824cc38 100644 --- a/src/BufferLine.test.ts +++ b/src/BufferLine.test.ts @@ -133,31 +133,71 @@ describe('BufferLine', function(): void { const line3 = line.clone(); chai.expect(TestBufferLine.prototype.toArray.apply(line3)).eql(line.toArray()); }); - it('resize enlarge', function(): void { - const line = new TestBufferLine(5, [1, 'a', 0, 'a'.charCodeAt(0)], false); - line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)]); - chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)])); - }); - it('resize shrink(true)', function(): void { - const line = new TestBufferLine(10, [1, 'a', 0, 'a'.charCodeAt(0)], false); - line.resize(5, [1, 'a', 0, 'a'.charCodeAt(0)], true); - chai.expect(line.toArray()).eql(Array(5).fill([1, 'a', 0, 'a'.charCodeAt(0)])); - }); - it('resize shrink(false)', function(): void { - const line = new TestBufferLine(10, [1, 'a', 0, 'a'.charCodeAt(0)], false); - line.resize(5, [1, 'a', 0, 'a'.charCodeAt(0)], false); - chai.expect(line.toArray()).eql(Array(5).fill([1, 'a', 0, 'a'.charCodeAt(0)])); - }); - it('resize shrink(false) + shrink(false)', function(): void { - const line = new TestBufferLine(20, [1, 'a', 0, 'a'.charCodeAt(0)], false); - line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)], false); - line.resize(5, [1, 'a', 0, 'a'.charCodeAt(0)], false); - chai.expect(line.toArray()).eql(Array(5).fill([1, 'a', 0, 'a'.charCodeAt(0)])); - }); - it('resize shrink(false) + enlarge', function(): void { - const line = new TestBufferLine(20, [1, 'a', 0, 'a'.charCodeAt(0)], false); - line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)], false); - line.resize(15, [1, 'a', 0, 'a'.charCodeAt(0)]); - chai.expect(line.toArray()).eql(Array(15).fill([1, 'a', 0, 'a'.charCodeAt(0)])); + describe('resize', function(): void { + it('enlarge(false)', function(): void { + const line = new TestBufferLine(5, [1, 'a', 0, 'a'.charCodeAt(0)], false); + line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)]); + chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)])); + }); + it('enlarge(true)', function(): void { + const line = new TestBufferLine(5, [1, 'a', 0, 'a'.charCodeAt(0)], false); + line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)], true); + chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)])); + }); + it('shrink(true) - should apply new size', function(): void { + const line = new TestBufferLine(10, [1, 'a', 0, 'a'.charCodeAt(0)], false); + line.resize(5, [1, 'a', 0, 'a'.charCodeAt(0)], true); + chai.expect(line.toArray()).eql(Array(5).fill([1, 'a', 0, 'a'.charCodeAt(0)])); + }); + it('shrink(false) - should not apply new size', function(): void { + const line = new TestBufferLine(10, [1, 'a', 0, 'a'.charCodeAt(0)], false); + line.resize(5, [1, 'a', 0, 'a'.charCodeAt(0)], false); + chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)])); + }); + it('shrink(false) + shrink(false) - should not apply new size', function(): void { + const line = new TestBufferLine(20, [1, 'a', 0, 'a'.charCodeAt(0)], false); + line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)], false); + line.resize(5, [1, 'a', 0, 'a'.charCodeAt(0)], false); + chai.expect(line.toArray()).eql(Array(20).fill([1, 'a', 0, 'a'.charCodeAt(0)])); + }); + it('shrink(false) + enlarge(false) to smaller than before', function(): void { + const line = new TestBufferLine(20, [1, 'a', 0, 'a'.charCodeAt(0)], false); + line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)], false); + line.resize(15, [1, 'a', 0, 'a'.charCodeAt(0)]); + chai.expect(line.toArray()).eql(Array(20).fill([1, 'a', 0, 'a'.charCodeAt(0)])); + }); + it('shrink(false) + enlarge(false) to bigger than before', function(): void { + const line = new TestBufferLine(20, [1, 'a', 0, 'a'.charCodeAt(0)], false); + line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)], false); + line.resize(25, [1, 'a', 0, 'a'.charCodeAt(0)]); + chai.expect(line.toArray()).eql(Array(25).fill([1, 'a', 0, 'a'.charCodeAt(0)])); + }); + it('shrink(false) + resize shrink=true should enforce shrinking', function(): void { + const line = new TestBufferLine(20, [1, 'a', 0, 'a'.charCodeAt(0)], false); + line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)], false); + line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)], true); + chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)])); + }); + it('enlarge from 0 length', function(): void { + const line = new TestBufferLine(0, [1, 'a', 0, 'a'.charCodeAt(0)], false); + line.resize(10, [1, 'a', 0, 'a'.charCodeAt(0)], false); + chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)])); + }); + it('shrink to 0 length', function(): void { + const line = new TestBufferLine(10, [1, 'a', 0, 'a'.charCodeAt(0)], false); + line.resize(0, [1, 'a', 0, 'a'.charCodeAt(0)], true); + chai.expect(line.toArray()).eql(Array(0).fill([1, 'a', 0, 'a'.charCodeAt(0)])); + }); + it('shrink(false) to 0 and enlarge to different sizes', function(): void { + const line = new TestBufferLine(10, [1, 'a', 0, 'a'.charCodeAt(0)], false); + line.resize(0, [1, 'a', 0, 'a'.charCodeAt(0)], false); + chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)])); + line.resize(5, [1, 'a', 0, 'a'.charCodeAt(0)], false); + chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)])); + line.resize(7, [1, 'a', 0, 'a'.charCodeAt(0)], false); + chai.expect(line.toArray()).eql(Array(10).fill([1, 'a', 0, 'a'.charCodeAt(0)])); + line.resize(7, [1, 'a', 0, 'a'.charCodeAt(0)], true); + chai.expect(line.toArray()).eql(Array(7).fill([1, 'a', 0, 'a'.charCodeAt(0)])); + }); }); }); diff --git a/src/BufferLine.ts b/src/BufferLine.ts index 53f5d4d4..f1ea9cf0 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -77,15 +77,15 @@ export class BufferLine implements IBufferLine { /** resize line to cols filling new cells with fill */ public resize(cols: number, fillCharData: CharData, shrink: boolean = false): void { + while (this._data.length < cols) { + this._data.push(fillCharData); + } if (shrink) { while (this._data.length > cols) { this._data.pop(); } } - while (this._data.length < cols) { - this._data.push(fillCharData); - } - this.length = cols; + this.length = this._data.length; } public fill(fillCharData: CharData): void { @@ -141,11 +141,13 @@ export class BufferLineTypedArray implements IBufferLine { if (!fillCharData) { fillCharData = [0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]; } - this._data = new Uint32Array(cols * CELL_SIZE); - for (let i = 0; i < cols; ++i) { - this.set(i, fillCharData); + if (cols) { + this._data = new Uint32Array(cols * CELL_SIZE); + for (let i = 0; i < cols; ++i) { + this.set(i, fillCharData); + } } - this.length = cols || 0; + this.length = cols; } public get(index: number): CharData { @@ -212,12 +214,12 @@ export class BufferLineTypedArray implements IBufferLine { } public resize(cols: number, fillCharData: CharData, shrink: boolean = false): void { - if (cols === this.length) { + if (cols === this.length || (!shrink && cols < this.length)) { return; } if (cols > this.length) { const data = new Uint32Array(cols * CELL_SIZE); - if (this._data) { + if (this.length) { if (cols * CELL_SIZE < this._data.length) { data.set(this._data.subarray(0, cols * CELL_SIZE)); } else { From a63779ae7b12d7b8eb4f408d5a182fc784851f31 Mon Sep 17 00:00:00 2001 From: Oleksandr Andriienko Date: Wed, 10 Oct 2018 10:26:32 +0300 Subject: [PATCH 03/21] Fix docker image build. Signed-off-by: Oleksandr Andriienko --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 1e3a262c..1c72e679 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,4 +16,4 @@ RUN npm install COPY . /usr/src/app # Run the tests and build, to make sure everything is working nicely -RUN npm run build && npm run webpack && npm run test +RUN npm run build && npm run test From 45e64a9167aafc8a6e49743ca3f4450a07d8726b Mon Sep 17 00:00:00 2001 From: Oleksandr Andriienko Date: Wed, 10 Oct 2018 11:39:34 +0300 Subject: [PATCH 04/21] Remove from package.json unused script command Signed-off-by: Oleksandr Andriienko --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index b10b6b54..ad763f0b 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,6 @@ "build": "gulp build", "prepublish": "npm run build", "coveralls": "nyc report --reporter=text-lcov | coveralls", - "webpack": "gulp webpack", "watch": "concurrently --kill-others-on-fail --names \"lib,css\" \"tsc -w\" \"gulp watch-css\"", "watch-addons": "concurrently --kill-others-on-fail --names \"attach,fit,fullscreen,search,terminado,webLinks,winptyCompat,zmodem\" \"tsc -w -p ./src/addons/attach\" \"tsc -w -p ./src/addons/fit\" \"tsc -w -p ./src/addons/fullscreen\" \"tsc -w -p ./src/addons/search\" \"tsc -w -p ./src/addons/terminado\" \"tsc -w -p ./src/addons/webLinks\" \"tsc -w -p ./src/addons/winptyCompat\" \"tsc -w -p ./src/addons/zmodem\"", "layering": "concurrently --kill-others-on-fail --names \"common,core\" \"tsc -p ./src/common\" \"tsc -p ./src/core\"" From f2ecd06e64dcbfdc3cbfdc24bac59f52d1255569 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 10 Oct 2018 15:31:49 +0200 Subject: [PATCH 05/21] Additional isWrapped clearing --- src/InputHandler.ts | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 9a0aae08..97001d0a 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -722,12 +722,16 @@ 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; + } } /** @@ -736,13 +740,7 @@ export class InputHandler extends Disposable implements IInputHandler { * @param y row index */ private _resetBufferLine(y: number): void { - const line = this._terminal.buffer.lines.get(this._terminal.buffer.ybase + y); - line.replaceCells( - 0, - this._terminal.cols, - [this._terminal.eraseAttr(), NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE] - ); - line.isWrapped = false; + this._eraseInBufferLine(y, 0, this._terminal.cols, true); } /** @@ -763,7 +761,11 @@ 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); + if (this._terminal.buffer.x !== 0) { + this._eraseInBufferLine(j++, this._terminal.buffer.x, this._terminal.cols); + } else { + this._resetBufferLine(j++); + } for (; j < this._terminal.rows; j++) { this._resetBufferLine(j); } From eb123ca787a21cd634d08e6e05ff0bd7eedc39e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Thu, 11 Oct 2018 00:32:14 +0200 Subject: [PATCH 06/21] add tiny buffer to server part in demo --- demo/server.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/demo/server.js b/demo/server.js index 0aa9deef..ebe2f644 100644 --- a/demo/server.js +++ b/demo/server.js @@ -58,9 +58,25 @@ app.ws('/terminals/:pid', function (ws, req) { console.log('Connected to terminal ' + term.pid); ws.send(logs[term.pid]); + function buffer(socket, timeout) { + let s = ''; + let sender = null; + return (data) => { + s += data; + if (!sender) { + sender = setTimeout(() => { + socket.send(s); + s = ''; + sender = null; + }, timeout); + } + }; + } + const send = buffer(ws, 5); + term.on('data', function(data) { try { - ws.send(data); + send(data); } catch (ex) { // The WebSocket is not open, ignore } From 8843b0cbc6135d690d6827b044649eb70e92b94d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 10 Oct 2018 18:20:12 -0700 Subject: [PATCH 07/21] Update Azure data strudio info --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6b9b877c..17833598 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,7 @@ computational environment for Jupyter, supporting interactive data science and s - [**rtty**](https://github.com/zhaojh329/rtty): A reverse proxy WebTTY. It is composed of the client and the server. - [**Pisth**](https://github.com/ColdGrub1384/Pisth): An SFTP and SSH client for iOS - [**abstruse**](https://github.com/bleenco/abstruse): Abstruse CI is a continuous integration platform based on Node.JS and Docker. -- [**Microsoft SQL Operations Studio**](https://github.com/Microsoft/sqlopsstudio): A data management tool that enables working with SQL Server, Azure SQL DB and SQL DW from Windows, macOS and Linux +- [**Azure Data Studio**](https://github.com/Microsoft/azuredatastudio): A data management tool that enables working with SQL Server, Azure SQL DB and SQL DW from Windows, macOS and Linux. - [**FreeMAN**](https://github.com/matthew-matvei/freeman): A free, cross-platform file manager for power users - [**Fluent Terminal**](https://github.com/felixse/FluentTerminal): A terminal emulator based on UWP and web technologies. - [**Hyper**](https://hyper.is): A terminal built on web technologies From 64d6354983600b7323b49d875405ff38f2379f18 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 11 Oct 2018 11:18:56 +0200 Subject: [PATCH 08/21] Added testing and better erase left and above handling --- src/InputHandler.test.ts | 30 ++++++++++++++++++++++++++++++ src/InputHandler.ts | 13 +++++++------ 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 722cc287..0e610fc0 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -444,6 +444,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 97001d0a..2fcfac27 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -761,11 +761,7 @@ export class InputHandler extends Disposable implements IInputHandler { case 0: j = this._terminal.buffer.y; this._terminal.updateRange(j); - if (this._terminal.buffer.x !== 0) { - this._eraseInBufferLine(j++, this._terminal.buffer.x, this._terminal.cols); - } else { - this._resetBufferLine(j++); - } + this._eraseInBufferLine(j++, this._terminal.buffer.x, this._terminal.cols, this._terminal.buffer.x === 0); for (; j < this._terminal.rows; j++) { this._resetBufferLine(j); } @@ -774,7 +770,12 @@ export class InputHandler extends Disposable implements IInputHandler { 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._resetBufferLine(j); } From 375b5c5b4f1334ea06ad64d2a22af2c00d031e33 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 11 Oct 2018 09:02:36 -0700 Subject: [PATCH 09/21] Fix indent --- src/InputHandler.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/InputHandler.test.ts b/src/InputHandler.test.ts index 0e610fc0..04b7a173 100644 --- a/src/InputHandler.test.ts +++ b/src/InputHandler.test.ts @@ -461,7 +461,7 @@ describe('InputHandler', () => { expect(termNew.buffer.lines.get(2).isWrapped).false; // reset and add a wrapped line - termNew.buffer.y = 0; + 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 From 9e1551e2f14f76d098b2b8d293dd6c3ef57d7255 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 11 Oct 2018 09:37:19 -0700 Subject: [PATCH 10/21] Fix NPE in DOM renderer underline Fixes #1747 --- src/renderer/dom/DomRenderer.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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) { From a0250c5cea4c8c310c9f5b3dca7a630786465c6a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 12 Oct 2018 08:33:59 -0700 Subject: [PATCH 11/21] Improve types for listener args in .d.ts Without this consumers need an undefined check with strictNullChecks turned on. --- typings/xterm.d.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index b5a8a109..8676a458 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -388,37 +388,37 @@ declare module 'xterm' { * @param type The type of the event. * @param listener The listener. */ - on(type: 'key', listener: (key?: string, event?: KeyboardEvent) => void): void; + on(type: 'key', listener: (key: string, event: KeyboardEvent) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. */ - on(type: 'keypress' | 'keydown', listener: (event?: KeyboardEvent) => void): void; + on(type: 'keypress' | 'keydown', listener: (event: KeyboardEvent) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. */ - on(type: 'refresh', listener: (data?: {start: number, end: number}) => void): void; + on(type: 'refresh', listener: (data: {start: number, end: number}) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. */ - on(type: 'resize', listener: (data?: {cols: number, rows: number}) => void): void; + on(type: 'resize', listener: (data: {cols: number, rows: number}) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. */ - on(type: 'scroll', listener: (ydisp?: number) => void): void; + on(type: 'scroll', listener: (ydisp: number) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. */ - on(type: 'title', listener: (title?: string) => void): void; + on(type: 'title', listener: (title: string) => void): void; /** * Registers an event listener. * @param type The type of the event. From 19f6c0a940e1d3e43e66ced78089ec1d976a72d2 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 12 Oct 2018 09:01:56 -0700 Subject: [PATCH 12/21] Fix tests --- src/utils/TestUtils.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index f575855a..353e615f 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -121,7 +121,7 @@ export class MockTerminal implements ITerminal { handler(data: string): void { throw new Error('Method not implemented.'); } - on(event: string, callback: () => void): void { + on(event: string, callback: (...args: any[]) => void): void { throw new Error('Method not implemented.'); } off(type: string, listener: XtermListener): void { From f61ba9558b6820c52030aee122a8042518bc4a13 Mon Sep 17 00:00:00 2001 From: pkoushik Date: Mon, 15 Oct 2018 23:44:30 +0530 Subject: [PATCH 13/21] Fixes-1750 --- src/addons/search/SearchHelper.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/addons/search/SearchHelper.ts b/src/addons/search/SearchHelper.ts index f23a8c58..7dea831c 100644 --- a/src/addons/search/SearchHelper.ts +++ b/src/addons/search/SearchHelper.ts @@ -205,6 +205,7 @@ export class SearchHelper implements ISearchHelper { */ private _selectResult(result: ISearchResult): boolean { if (!result) { + this._terminal.clearSelection(); return false; } this._terminal._core.selectionManager.setSelection(result.col, result.row, result.term.length); From a53aaa5f25eec4e6e050b357f80d9ae3148aec61 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 15 Oct 2018 11:35:21 -0700 Subject: [PATCH 14/21] Move dynamic atlas canCache check higher Fixes #1757 --- src/renderer/atlas/DynamicCharAtlas.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/renderer/atlas/DynamicCharAtlas.ts b/src/renderer/atlas/DynamicCharAtlas.ts index e67fe4f0..63336900 100644 --- a/src/renderer/atlas/DynamicCharAtlas.ts +++ b/src/renderer/atlas/DynamicCharAtlas.ts @@ -129,12 +129,17 @@ export default class DynamicCharAtlas extends BaseCharAtlas { return true; } + // Exit early for uncachable glyphs + if (!this._canCache(glyph)) { + return false; + } + const glyphKey = getGlyphCacheKey(glyph); const cacheValue = this._cacheMap.get(glyphKey); if (cacheValue !== null && cacheValue !== undefined) { this._drawFromCache(ctx, cacheValue, x, y); return true; - } else if (this._canCache(glyph) && this._drawToCacheCount < FRAME_CACHE_DRAW_LIMIT) { + } else if (this._drawToCacheCount < FRAME_CACHE_DRAW_LIMIT) { let index; if (this._cacheMap.size < this._cacheMap.capacity) { index = this._cacheMap.size; From 161dfa8d2d7c516b49649f623f93b913c8316189 Mon Sep 17 00:00:00 2001 From: Stathis Kavros Date: Tue, 16 Oct 2018 14:23:58 +0300 Subject: [PATCH 15/21] Fix typos --- src/addons/search/SearchHelper.ts | 6 +++--- src/addons/search/search.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/addons/search/SearchHelper.ts b/src/addons/search/SearchHelper.ts index 7dea831c..9f59a9fd 100644 --- a/src/addons/search/SearchHelper.ts +++ b/src/addons/search/SearchHelper.ts @@ -19,7 +19,7 @@ export class SearchHelper implements ISearchHelper { /** * Find the next instance of the term, then scroll to and select it. If it * doesn't exist, do nothing. - * @param term Tne search term. + * @param term The search term. * @param searchOptions Search options. * @return Whether a result was found. */ @@ -61,7 +61,7 @@ export class SearchHelper implements ISearchHelper { /** * Find the previous instance of the term, then scroll to and select it. If it * doesn't exist, do nothing. - * @param term Tne search term. + * @param term The search term. * @param searchOptions Search options. * @return Whether a result was found. */ @@ -116,7 +116,7 @@ export class SearchHelper implements ISearchHelper { * subsequent terminal lines if the text is wrapped. If the provided line number is part of a wrapped text line that * started on an earlier line then it is skipped since it will be properly searched when the terminal line that the * text starts on is searched. - * @param term Tne search term. + * @param term The search term. * @param y The line to search. * @param searchOptions Search options. * @return The search result if it was found. diff --git a/src/addons/search/search.ts b/src/addons/search/search.ts index 9be0b33b..c274f28a 100644 --- a/src/addons/search/search.ts +++ b/src/addons/search/search.ts @@ -10,7 +10,7 @@ import { ISearchAddonTerminal, ISearchOptions } from './Interfaces'; /** * Find the next instance of the term, then scroll to and select it. If it * doesn't exist, do nothing. - * @param term Tne search term. + * @param term The search term. * @param searchOptions Search options * @return Whether a result was found. */ @@ -25,7 +25,7 @@ export function findNext(terminal: Terminal, term: string, searchOptions: ISearc /** * Find the previous instance of the term, then scroll to and select it. If it * doesn't exist, do nothing. - * @param term Tne search term. + * @param term The search term. * @param searchOptions Search options * @return Whether a result was found. */ From 74664a91611d4ae718012097f11e0f58f65989e9 Mon Sep 17 00:00:00 2001 From: pkoushik Date: Wed, 17 Oct 2018 10:48:46 +0530 Subject: [PATCH 16/21] Fix-1749 --- src/addons/search/SearchHelper.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/addons/search/SearchHelper.ts b/src/addons/search/SearchHelper.ts index 7dea831c..6829ef4c 100644 --- a/src/addons/search/SearchHelper.ts +++ b/src/addons/search/SearchHelper.ts @@ -33,7 +33,9 @@ export class SearchHelper implements ISearchHelper { let startRow = this._terminal._core.buffer.ydisp; if (this._terminal._core.selectionManager.selectionEnd) { // Start from the selection end if there is a selection - startRow = this._terminal._core.selectionManager.selectionEnd[1]; + if (this._terminal.getSelection().length !== 0) { + startRow = this._terminal._core.selectionManager.selectionEnd[1]; + } } // Search from ydisp + 1 to end @@ -75,7 +77,9 @@ export class SearchHelper implements ISearchHelper { let startRow = this._terminal._core.buffer.ydisp; if (this._terminal._core.selectionManager.selectionStart) { // Start from the selection end if there is a selection - startRow = this._terminal._core.selectionManager.selectionStart[1]; + if (this._terminal.getSelection().length !== 0) { + startRow = this._terminal._core.selectionManager.selectionStart[1]; + } } // Search from ydisp + 1 to end @@ -108,7 +112,7 @@ export class SearchHelper implements ISearchHelper { */ private _isWholeWord(searchIndex: number, line: string, term: string): boolean { return (((searchIndex === 0) || (nonWordCharacters.indexOf(line[searchIndex - 1]) !== -1)) && - (((searchIndex + term.length) === line.length) || (nonWordCharacters.indexOf(line[searchIndex + term.length]) !== -1))); + (((searchIndex + term.length) === line.length) || (nonWordCharacters.indexOf(line[searchIndex + term.length]) !== -1))); } /** From 8bd49cba35617699f6c69bf7b648b66b6cb17057 Mon Sep 17 00:00:00 2001 From: Jeff Smith Date: Fri, 21 Sep 2018 23:20:38 -0500 Subject: [PATCH 17/21] Fill inserted lines with background color When scrolling occurs due to a LF or scroll code, fill the new line with the currently-set background color. --- src/Terminal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index c995af62..55e212e0 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1174,7 +1174,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 { - const newLine = this.buffer.getBlankLine(DEFAULT_ATTR, isWrapped); + const newLine = this.buffer.getBlankLine(this.eraseAttr(), isWrapped); const topRow = this.buffer.ybase + this.buffer.scrollTop; const bottomRow = this.buffer.ybase + this.buffer.scrollBottom; From 35db216f649006ca5d35dd1d51a1f5f0d5841ea9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 20 Oct 2018 13:55:13 -0700 Subject: [PATCH 18/21] Fix indent --- src/addons/search/SearchHelper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/addons/search/SearchHelper.ts b/src/addons/search/SearchHelper.ts index cd867824..938887d8 100644 --- a/src/addons/search/SearchHelper.ts +++ b/src/addons/search/SearchHelper.ts @@ -112,7 +112,7 @@ export class SearchHelper implements ISearchHelper { */ private _isWholeWord(searchIndex: number, line: string, term: string): boolean { return (((searchIndex === 0) || (nonWordCharacters.indexOf(line[searchIndex - 1]) !== -1)) && - (((searchIndex + term.length) === line.length) || (nonWordCharacters.indexOf(line[searchIndex + term.length]) !== -1))); + (((searchIndex + term.length) === line.length) || (nonWordCharacters.indexOf(line[searchIndex + term.length]) !== -1))); } /** From 4d556fcaf0b6158d5327ef6df61b908bb01893db Mon Sep 17 00:00:00 2001 From: Jeff Smith Date: Tue, 23 Oct 2018 11:57:20 -0500 Subject: [PATCH 19/21] Check for focus on document as well as on textarea When clicking outside of the browser document area, the cursor does not appear to get 'blurred'. This can be confusing as it implies that the cursor is still active. This is caused by Terminal's isFocused getter only checking if the textarea is the active element of the document. Expand isFocused to also check that the document itself has focus. --- src/Terminal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index c995af62..a7b87027 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -342,7 +342,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(); } /** From c2994b0ae1b72fffe95312ef74c5d32897ac82a7 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 23 Oct 2018 15:45:52 -0700 Subject: [PATCH 20/21] Remove request term info handler We're not supporting it anyway, no point having it. --- src/InputHandler.ts | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/src/InputHandler.ts b/src/InputHandler.ts index ddb20527..a282031b 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 { From 213dadf1bd12a5ca796214a796f4c6443b5b75b5 Mon Sep 17 00:00:00 2001 From: Shengdun Hua Date: Thu, 25 Oct 2018 23:01:03 +0800 Subject: [PATCH 21/21] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) 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)