diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 485900e41a..8fd6cb2cf5 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -270,6 +270,9 @@ export class MockBuffer implements IBuffer { public clearAllMarkers(): void { throw new Error('Method not implemented.'); } + public setWrapped(row: number, value: boolean): void { + throw new Error('Method not implemented.'); + } } export class MockRenderer implements IRenderer { diff --git a/src/browser/services/SelectionService.test.ts b/src/browser/services/SelectionService.test.ts index 8deedb751c..8ee04b7e3d 100644 --- a/src/browser/services/SelectionService.test.ts +++ b/src/browser/services/SelectionService.test.ts @@ -193,7 +193,7 @@ describe('SelectionService', () => { it('should expand upwards or downards for wrapped lines', () => { buffer.lines.set(0, stringToRow(' foo')); buffer.lines.set(1, stringToRow('bar ')); - buffer.lines.get(1)!.isWrapped = true; + buffer.setWrapped(1, true); selectionService.selectWordAt([1, 1]); assert.equal(selectionService.selectionText, 'foobar'); selectionService.model.clearSelection(); @@ -207,10 +207,10 @@ describe('SelectionService', () => { buffer.lines.set(2, stringToRow('bbbbbbbbbbbbbbbbbbbb')); buffer.lines.set(3, stringToRow('cccccccccccccccccccc')); buffer.lines.set(4, stringToRow('bar ')); - buffer.lines.get(1)!.isWrapped = true; - buffer.lines.get(2)!.isWrapped = true; - buffer.lines.get(3)!.isWrapped = true; - buffer.lines.get(4)!.isWrapped = true; + buffer.setWrapped(1, true); + buffer.setWrapped(2, true); + buffer.setWrapped(3, true); + buffer.setWrapped(4, true); selectionService.selectWordAt([18, 0]); assert.equal(selectionService.selectionText, expectedText); selectionService.model.clearSelection(); @@ -349,8 +349,8 @@ describe('SelectionService', () => { it('should select the entire wrapped line', () => { buffer.lines.set(0, stringToRow('foo')); const line2 = stringToRow('bar'); - line2.isWrapped = true; buffer.lines.set(1, line2); + buffer.setWrapped(1, true); selectionService.selectLineAt(0); assert.equal(selectionService.selectionText, 'foobar', 'The selected text is correct'); assert.deepEqual(selectionService.model.selectionStart, [0, 0]); diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index 68265c5df4..361eb8d875 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -609,7 +609,7 @@ export class InputHandler extends Disposable implements IInputHandler { } // The line already exists (eg. the initial viewport), mark it as a // wrapped line - this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!.isWrapped = true; + this._activeBuffer.setWrapped(this._activeBuffer.ybase + this._activeBuffer.y, true); } // row changed, get it again bufferRow = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y); @@ -773,7 +773,7 @@ export class InputHandler extends Disposable implements IInputHandler { // reprint is common, especially on resize. Note that the windowsMode wrapped line heuristics // can mess with this so windowsMode should be disabled, which is recommended on Windows build // 21376 and above. - this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!.isWrapped = false; + this._activeBuffer.setWrapped(this._activeBuffer.ybase + this._activeBuffer.y, false); } // If the end of the line is hit, prevent this action from wrapping around to the next line. if (this._activeBuffer.x >= this._bufferService.cols) { @@ -837,7 +837,7 @@ export class InputHandler extends Disposable implements IInputHandler { && this._activeBuffer.y > this._activeBuffer.scrollTop && this._activeBuffer.y <= this._activeBuffer.scrollBottom && this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)?.isWrapped) { - this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!.isWrapped = false; + this._activeBuffer.setWrapped(this._activeBuffer.ybase + this._activeBuffer.y, false); this._activeBuffer.y--; this._activeBuffer.x = this._bufferService.cols - 1; // find last taken cell - last cell can have 3 different states: @@ -1201,7 +1201,7 @@ export class InputHandler extends Disposable implements IInputHandler { respectProtect ); if (clearWrap) { - line.isWrapped = false; + this._activeBuffer.setWrapped(this._activeBuffer.ybase + y, false); } } @@ -1215,7 +1215,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (line) { line.fill(this._activeBuffer.getNullCell(this._eraseAttrData()), respectProtect); this._bufferService.buffer.clearMarkers(this._activeBuffer.ybase + y); - line.isWrapped = false; + this._activeBuffer.setWrapped(this._activeBuffer.ybase + y, false); } } @@ -1263,10 +1263,7 @@ export class InputHandler extends Disposable implements IInputHandler { this._eraseInBufferLine(j, 0, this._activeBuffer.x + 1, true, respectProtect); if (this._activeBuffer.x + 1 >= this._bufferService.cols) { // Deleted entire previous line. This next line can no longer be wrapped. - const nextLine = this._activeBuffer.lines.get(j + 1); - if (nextLine) { - nextLine.isWrapped = false; - } + this._activeBuffer.setWrapped(j + 1, false); } while (j--) { this._resetBufferLine(j, respectProtect); @@ -1528,9 +1525,10 @@ export class InputHandler extends Disposable implements IInputHandler { } const param = params.params[0] || 1; for (let y = this._activeBuffer.scrollTop; y <= this._activeBuffer.scrollBottom; ++y) { - const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!; + const row = this._activeBuffer.ybase + y; + const line = this._activeBuffer.lines.get(row)!; line.deleteCells(0, param, this._activeBuffer.getNullCell(this._eraseAttrData())); - line.isWrapped = false; + this._activeBuffer.setWrapped(row, false); } this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); return true; @@ -1561,9 +1559,10 @@ export class InputHandler extends Disposable implements IInputHandler { } const param = params.params[0] || 1; for (let y = this._activeBuffer.scrollTop; y <= this._activeBuffer.scrollBottom; ++y) { - const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!; + const row = this._activeBuffer.ybase + y; + const line = this._activeBuffer.lines.get(row)!; + this._activeBuffer.setWrapped(row, false); line.insertCells(0, param, this._activeBuffer.getNullCell(this._eraseAttrData())); - line.isWrapped = false; } this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); return true; @@ -1584,9 +1583,10 @@ export class InputHandler extends Disposable implements IInputHandler { } const param = params.params[0] || 1; for (let y = this._activeBuffer.scrollTop; y <= this._activeBuffer.scrollBottom; ++y) { - const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!; + const row = this._activeBuffer.ybase + y; + this._activeBuffer.setWrapped(row, false); + const line = this._activeBuffer.lines.get(row)!; line.insertCells(this._activeBuffer.x, param, this._activeBuffer.getNullCell(this._eraseAttrData())); - line.isWrapped = false; } this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); return true; @@ -1607,9 +1607,10 @@ export class InputHandler extends Disposable implements IInputHandler { } const param = params.params[0] || 1; for (let y = this._activeBuffer.scrollTop; y <= this._activeBuffer.scrollBottom; ++y) { - const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!; + const row = this._activeBuffer.ybase + y; + const line = this._activeBuffer.lines.get(row)!; + this._activeBuffer.setWrapped(row, false); line.deleteCells(this._activeBuffer.x, param, this._activeBuffer.getNullCell(this._eraseAttrData())); - line.isWrapped = false; } this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom); return true; @@ -3494,11 +3495,8 @@ export class InputHandler extends Disposable implements IInputHandler { this._setCursor(0, 0); for (let yOffset = 0; yOffset < this._bufferService.rows; ++yOffset) { const row = this._activeBuffer.ybase + this._activeBuffer.y + yOffset; - const line = this._activeBuffer.lines.get(row); - if (line) { - line.fill(cell); - line.isWrapped = false; - } + this._activeBuffer.setWrapped(row, false); + this._activeBuffer.lines.get(row)?.fill(cell); } this._dirtyRowTracker.markAllDirty(); this._setCursor(0, 0); diff --git a/src/common/Types.ts b/src/common/Types.ts index 0a9c89a1a0..4e5cbb19a8 100644 --- a/src/common/Types.ts +++ b/src/common/Types.ts @@ -224,7 +224,7 @@ export interface ICellData extends IAttributeData { */ export interface IBufferLine { length: number; - isWrapped: boolean; + get isWrapped(): boolean; get(index: number): CharData; set(index: number, value: CharData): void; loadCell(index: number, cell: ICellData): ICellData; diff --git a/src/common/WindowsMode.ts b/src/common/WindowsMode.ts index 7cff094b2c..22ba1e2a92 100644 --- a/src/common/WindowsMode.ts +++ b/src/common/WindowsMode.ts @@ -19,9 +19,7 @@ export function updateWindowsModeWrappedState(bufferService: IBufferService): vo // wrapped. const line = bufferService.buffer.lines.get(bufferService.buffer.ybase + bufferService.buffer.y - 1); const lastChar = line?.get(bufferService.cols - 1); - - const nextLine = bufferService.buffer.lines.get(bufferService.buffer.ybase + bufferService.buffer.y); - if (nextLine && lastChar) { - nextLine.isWrapped = (lastChar[CHAR_DATA_CODE_INDEX] !== NULL_CELL_CODE && lastChar[CHAR_DATA_CODE_INDEX] !== WHITESPACE_CELL_CODE); + if (lastChar) { + bufferService.buffer.setWrapped(bufferService.buffer.ybase + bufferService.buffer.y, lastChar[CHAR_DATA_CODE_INDEX] !== NULL_CELL_CODE && lastChar[CHAR_DATA_CODE_INDEX] !== WHITESPACE_CELL_CODE); } } diff --git a/src/common/buffer/Buffer.test.ts b/src/common/buffer/Buffer.test.ts index 9c013c5def..dd0d90fb93 100644 --- a/src/common/buffer/Buffer.test.ts +++ b/src/common/buffer/Buffer.test.ts @@ -68,40 +68,40 @@ describe('Buffer', () => { describe('wrapped', () => { it('should return a range for the first row', () => { buffer.fillViewportRows(); - buffer.lines.get(1)!.isWrapped = true; + buffer.setWrapped(1, true); assert.deepEqual(buffer.getWrappedRangeForLine(0), { first: 0, last: 1 }); }); it('should return a range for a middle row wrapping upwards', () => { buffer.fillViewportRows(); - buffer.lines.get(12)!.isWrapped = true; + buffer.setWrapped(12, true); assert.deepEqual(buffer.getWrappedRangeForLine(12), { first: 11, last: 12 }); }); it('should return a range for a middle row wrapping downwards', () => { buffer.fillViewportRows(); - buffer.lines.get(13)!.isWrapped = true; + buffer.setWrapped(13, true); assert.deepEqual(buffer.getWrappedRangeForLine(12), { first: 12, last: 13 }); }); it('should return a range for a middle row wrapping both ways', () => { buffer.fillViewportRows(); - buffer.lines.get(11)!.isWrapped = true; - buffer.lines.get(12)!.isWrapped = true; - buffer.lines.get(13)!.isWrapped = true; - buffer.lines.get(14)!.isWrapped = true; + buffer.setWrapped(11, true); + buffer.setWrapped(12, true); + buffer.setWrapped(13, true); + buffer.setWrapped(14, true); assert.deepEqual(buffer.getWrappedRangeForLine(12), { first: 10, last: 14 }); }); it('should return a range for the last row', () => { buffer.fillViewportRows(); - buffer.lines.get(23)!.isWrapped = true; + buffer.setWrapped(23, true); assert.deepEqual(buffer.getWrappedRangeForLine(buffer.lines.length - 1), { first: 22, last: 23 }); }); it('should return a range for a row that wraps upward to first row', () => { buffer.fillViewportRows(); - buffer.lines.get(1)!.isWrapped = true; + buffer.setWrapped(1, true); assert.deepEqual(buffer.getWrappedRangeForLine(1), { first: 0, last: 1 }); }); it('should return a range for a row that wraps downward to last row', () => { buffer.fillViewportRows(); - buffer.lines.get(buffer.lines.length - 1)!.isWrapped = true; + buffer.setWrapped(buffer.lines.length - 1, true); assert.deepEqual(buffer.getWrappedRangeForLine(buffer.lines.length - 2), { first: 22, last: 23 }); }); }); @@ -526,7 +526,7 @@ describe('Buffer', () => { buffer.lines.get(0)!.set(1, [0, 'b', 1, 'b'.charCodeAt(0)]); buffer.lines.get(1)!.set(0, [0, 'c', 1, 'c'.charCodeAt(0)]); buffer.lines.get(1)!.set(1, [0, 'd', 1, 'd'.charCodeAt(0)]); - buffer.lines.get(1)!.isWrapped = true; + buffer.setWrapped(1, true); // Buffer: // "ab " (wrapped) // "cd" @@ -557,7 +557,7 @@ describe('Buffer', () => { buffer.lines.get(0)!.set(i, [0, '', 0, 0]); buffer.lines.get(1)!.set(i, [0, '', 0, 0]); } - buffer.lines.get(1)!.isWrapped = true; + buffer.setWrapped(1, true); // Buffer: // 汉语汉语汉语 (wrapped) // 汉语汉语汉语 @@ -584,7 +584,7 @@ describe('Buffer', () => { buffer.lines.get(0)!.set(1, [0, 'b', 1, 'b'.charCodeAt(0)]); buffer.lines.get(1)!.set(0, [0, 'c', 1, 'c'.charCodeAt(0)]); buffer.lines.get(1)!.set(1, [0, 'd', 1, 'd'.charCodeAt(0)]); - buffer.lines.get(1)!.isWrapped = true; + buffer.setWrapped(1, true); // Buffer: // "ab " (wrapped) // "cd" @@ -618,7 +618,7 @@ describe('Buffer', () => { buffer.lines.get(0)!.set(i, [0, '', 0, 0]); buffer.lines.get(1)!.set(i, [0, '', 0, 0]); } - buffer.lines.get(1)!.isWrapped = true; + buffer.setWrapped(1, true); // Buffer: // 汉语汉语汉语 (wrapped) // 汉语汉语汉语 @@ -673,17 +673,17 @@ describe('Buffer', () => { buffer.lines.get(0)!.set(1, [0, 'b', 1, 'b'.charCodeAt(0)]); buffer.lines.get(1)!.set(0, [0, 'c', 1, 'c'.charCodeAt(0)]); buffer.lines.get(1)!.set(1, [0, 'd', 1, 'd'.charCodeAt(0)]); - buffer.lines.get(1)!.isWrapped = true; + buffer.setWrapped(1, true); buffer.lines.get(2)!.set(0, [0, 'e', 1, 'e'.charCodeAt(0)]); buffer.lines.get(2)!.set(1, [0, 'f', 1, 'f'.charCodeAt(0)]); buffer.lines.get(3)!.set(0, [0, 'g', 1, 'g'.charCodeAt(0)]); buffer.lines.get(3)!.set(1, [0, 'h', 1, 'h'.charCodeAt(0)]); - buffer.lines.get(3)!.isWrapped = true; + buffer.setWrapped(3, true); buffer.lines.get(4)!.set(0, [0, 'i', 1, 'i'.charCodeAt(0)]); buffer.lines.get(4)!.set(1, [0, 'j', 1, 'j'.charCodeAt(0)]); buffer.lines.get(5)!.set(0, [0, 'k', 1, 'k'.charCodeAt(0)]); buffer.lines.get(5)!.set(1, [0, 'l', 1, 'l'.charCodeAt(0)]); - buffer.lines.get(5)!.isWrapped = true; + buffer.setWrapped(5, true); }); describe('viewport not yet filled', () => { it('should move the cursor up and add empty lines', () => { diff --git a/src/common/buffer/Buffer.ts b/src/common/buffer/Buffer.ts index 8efefd60a4..b7de7e7416 100644 --- a/src/common/buffer/Buffer.ts +++ b/src/common/buffer/Buffer.ts @@ -121,6 +121,11 @@ export class Buffer implements IBuffer { return correctBufferLength > MAX_BUFFER_SIZE ? MAX_BUFFER_SIZE : correctBufferLength; } + public setWrapped(absrow: number, value: boolean): void { + const line = this.lines.get(absrow); + line instanceof BufferLine && line.setWrapped(value); + } + /** * Fills the buffer's viewport with blank lines. */ @@ -135,7 +140,7 @@ export class Buffer implements IBuffer { } /** - * Clears the buffer to it's initial state, discarding all previous data. + * Clears the buffer to its initial state, discarding all previous data. */ public clear(): void { this.ydisp = 0; diff --git a/src/common/buffer/BufferLine.ts b/src/common/buffer/BufferLine.ts index e415851a0a..b7921942c8 100644 --- a/src/common/buffer/BufferLine.ts +++ b/src/common/buffer/BufferLine.ts @@ -64,14 +64,26 @@ export class BufferLine implements IBufferLine { protected _combined: {[index: number]: string} = {}; protected _extendedAttrs: {[index: number]: IExtendedAttrs | undefined} = {}; public length: number; + private _isWrapped: boolean; - constructor(cols: number, fillCellData?: ICellData, public isWrapped: boolean = false) { + public get isWrapped(): boolean { + return this._isWrapped; + } + /** + * @internal + */ + public setWrapped(isWrapped: boolean): void { + this._isWrapped = isWrapped; + } + + constructor(cols: number, fillCellData?: ICellData, isWrapped: boolean = false) { this._data = new Uint32Array(cols * CELL_SIZE); const cell = fillCellData ?? CellData.fromCharData([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]); for (let i = 0; i < cols; ++i) { this.setCell(i, cell); } this.length = cols; + this._isWrapped = isWrapped; } /** @@ -439,7 +451,7 @@ export class BufferLine implements IBufferLine { for (const el in line._extendedAttrs) { this._extendedAttrs[el] = line._extendedAttrs[el]; } - this.isWrapped = line.isWrapped; + this._isWrapped = line._isWrapped; } /** create a new clone */ @@ -453,7 +465,7 @@ export class BufferLine implements IBufferLine { for (const el in this._extendedAttrs) { newLine._extendedAttrs[el] = this._extendedAttrs[el]; } - newLine.isWrapped = this.isWrapped; + newLine._isWrapped = this._isWrapped; return newLine; } diff --git a/src/common/buffer/Types.ts b/src/common/buffer/Types.ts index 85dd68eec2..a7e7321f2f 100644 --- a/src/common/buffer/Types.ts +++ b/src/common/buffer/Types.ts @@ -39,6 +39,7 @@ export interface IBuffer { addMarker(y: number): IMarker; clearMarkers(y: number): void; clearAllMarkers(): void; + setWrapped(row: number, value: boolean): void; } export interface IBufferSet extends IDisposable { diff --git a/src/common/services/BufferService.ts b/src/common/services/BufferService.ts index 6a7ce2b343..e71bc04ab1 100644 --- a/src/common/services/BufferService.ts +++ b/src/common/services/BufferService.ts @@ -6,6 +6,7 @@ import { Disposable } from 'common/Lifecycle'; import { IAttributeData, IBufferLine } from 'common/Types'; import { BufferSet } from 'common/buffer/BufferSet'; +import { BufferLine } from 'common/buffer/BufferLine'; import { IBuffer, IBufferSet } from 'common/buffer/Types'; import { IBufferService, ILogService, IOptionsService, type IBufferResizeEvent } from 'common/services/Services'; import { Emitter } from 'common/Event'; @@ -73,7 +74,7 @@ export class BufferService extends Disposable implements IBufferService { newLine = buffer.getBlankLine(eraseAttr, isWrapped); this._cachedBlankLine = newLine; } - newLine.isWrapped = isWrapped; + (newLine as BufferLine).setWrapped(isWrapped); const topRow = buffer.ybase + buffer.scrollTop; const bottomRow = buffer.ybase + buffer.scrollBottom;