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 { 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/addons/search/SearchHelper.ts b/src/addons/search/SearchHelper.ts index 9f59a9fd..7919932a 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))); } /** @@ -191,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);