diff --git a/src/browser/Terminal.test.ts b/src/browser/Terminal.test.ts index 4e0ebeaba3..13b6c0e616 100644 --- a/src/browser/Terminal.test.ts +++ b/src/browser/Terminal.test.ts @@ -1041,241 +1041,6 @@ describe('Terminal', () => { }); }); - describe('Buffer.stringIndexToBufferIndex', () => { - let terminal: TestTerminal; - - beforeEach(() => { - terminal = new TestTerminal({ rows: 5, cols: 10, scrollback: 5 }); - }); - - it('multiline ascii', async () => { - const input = 'This is ASCII text spanning multiple lines.'; - await terminal.writeP(input); - const s = terminal.buffer.iterator(true).next().content; - assert.equal(input, s); - for (let i = 0; i < input.length; ++i) { - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); - assert.deepEqual([(i / terminal.cols) | 0, i % terminal.cols], bufferIndex); - } - }); - - it('combining e\u0301 in a sentence', async () => { - const input = 'Sitting in the cafe\u0301 drinking coffee.'; - await terminal.writeP(input); - const s = terminal.buffer.iterator(true).next().content; - assert.equal(input, s); - for (let i = 0; i < 19; ++i) { - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); - assert.deepEqual([(i / terminal.cols) | 0, i % terminal.cols], bufferIndex); - } - // string index 18 & 19 point to combining char e\u0301 ---> same buffer Index - assert.deepEqual( - terminal.buffer.stringIndexToBufferIndex(0, 18), - terminal.buffer.stringIndexToBufferIndex(0, 19)); - // after the combining char every string index has an offset of -1 - for (let i = 19; i < input.length; ++i) { - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); - assert.deepEqual([((i - 1) / terminal.cols) | 0, (i - 1) % terminal.cols], bufferIndex); - } - }); - - it('multiline combining e\u0301', async () => { - const input = 'e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301'; - await terminal.writeP(input); - const s = terminal.buffer.iterator(true).next().content; - assert.equal(input, s); - // every buffer cell index contains 2 string indices - for (let i = 0; i < input.length; ++i) { - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); - assert.deepEqual([((i >> 1) / terminal.cols) | 0, (i >> 1) % terminal.cols], bufferIndex); - } - }); - - it('surrogate char in a sentence', async () => { - const input = 'The 𝄞 is a clef widely used in modern notation.'; - await terminal.writeP(input); - const s = terminal.buffer.iterator(true).next().content; - assert.equal(input, s); - for (let i = 0; i < 5; ++i) { - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); - assert.deepEqual([(i / terminal.cols) | 0, i % terminal.cols], bufferIndex); - } - // string index 4 & 5 point to surrogate char 𝄞 ---> same buffer Index - assert.deepEqual( - terminal.buffer.stringIndexToBufferIndex(0, 4), - terminal.buffer.stringIndexToBufferIndex(0, 5)); - // after the combining char every string index has an offset of -1 - for (let i = 5; i < input.length; ++i) { - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); - assert.deepEqual([((i - 1) / terminal.cols) | 0, (i - 1) % terminal.cols], bufferIndex); - } - }); - - it('multiline surrogate char', async () => { - const input = '𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞'; - await terminal.writeP(input); - const s = terminal.buffer.iterator(true).next().content; - assert.equal(input, s); - // every buffer cell index contains 2 string indices - for (let i = 0; i < input.length; ++i) { - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); - assert.deepEqual([((i >> 1) / terminal.cols) | 0, (i >> 1) % terminal.cols], bufferIndex); - } - }); - - it('surrogate char with combining', async () => { - // eye of Ra with acute accent - string length of 3 - const input = '𓂀\u0301 - the eye hiroglyph with an acute accent.'; - await terminal.writeP(input); - const s = terminal.buffer.iterator(true).next().content; - assert.equal(input, s); - // index 0..2 should map to 0 - assert.deepEqual([0, 0], terminal.buffer.stringIndexToBufferIndex(0, 1)); - assert.deepEqual([0, 0], terminal.buffer.stringIndexToBufferIndex(0, 2)); - for (let i = 2; i < input.length; ++i) { - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); - assert.deepEqual([((i - 2) / terminal.cols) | 0, (i - 2) % terminal.cols], bufferIndex); - } - }); - - it('multiline surrogate with combining', async () => { - const input = '𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301'; - await terminal.writeP(input); - const s = terminal.buffer.iterator(true).next().content; - assert.equal(input, s); - // every buffer cell index contains 3 string indices - for (let i = 0; i < input.length; ++i) { - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); - assert.deepEqual([(((i / 3) | 0) / terminal.cols) | 0, ((i / 3) | 0) % terminal.cols], bufferIndex); - } - }); - - it('fullwidth chars', async () => { - const input = 'These 123 are some fat numbers.'; - await terminal.writeP(input); - const s = terminal.buffer.iterator(true).next().content; - assert.equal(input, s); - for (let i = 0; i < 6; ++i) { - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); - assert.deepEqual([(i / terminal.cols) | 0, i % terminal.cols], bufferIndex); - } - // string index 6, 7, 8 take 2 cells - assert.deepEqual([0, 8], terminal.buffer.stringIndexToBufferIndex(0, 7)); - assert.deepEqual([1, 0], terminal.buffer.stringIndexToBufferIndex(0, 8)); - // rest of the string has offset of +3 - for (let i = 9; i < input.length; ++i) { - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); - assert.deepEqual([((i + 3) / terminal.cols) | 0, (i + 3) % terminal.cols], bufferIndex); - } - }); - - it('multiline fullwidth chars', async () => { - const input = '12345678901234567890'; - await terminal.writeP(input); - const s = terminal.buffer.iterator(true).next().content; - assert.equal(input, s); - for (let i = 9; i < input.length; ++i) { - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i); - assert.deepEqual([((i << 1) / terminal.cols) | 0, (i << 1) % terminal.cols], bufferIndex); - } - }); - - it('fullwidth combining with emoji - match emoji cell', async () => { - const input = 'Lots of ¥\u0301 make me 😃.'; - await terminal.writeP(input); - const s = terminal.buffer.iterator(true).next().content; - assert.equal(input, s); - const stringIndex = s.match(/😃/)!.index!; - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, stringIndex); - assert(terminal.buffer.lines.get(bufferIndex[0])!.loadCell(bufferIndex[1], new CellData()).getChars(), '😃'); - }); - - it('multiline fullwidth chars with offset 1 (currently tests for broken behavior)', async () => { - const input = 'a12345678901234567890'; - // the 'a' at the beginning moves all fullwidth chars one to the right - // now the end of the line contains a dangling empty cell since - // the next fullwidth char has to wrap early - // the dangling last cell is wrongly added in the string - // --> fixable after resolving #1685 - await terminal.writeP(input); - const s = terminal.buffer.iterator(true).next().content; - assert.equal(input, s); - for (let i = 10; i < input.length; ++i) { - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i, true); - const j = (i - 0) << 1; - assert.deepEqual([(j / terminal.cols) | 0, j % terminal.cols], bufferIndex); - } - }); - - it('test fully wrapped buffer up to last char', async () => { - const input = Array(6).join('1234567890'); - await terminal.writeP(input); - const s = terminal.buffer.iterator(true).next().content; - assert.equal(input, s); - for (let i = 0; i < input.length; ++i) { - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i, true); - assert.equal(input[i], terminal.buffer.lines.get(bufferIndex[0])!.loadCell(bufferIndex[1], new CellData()).getChars()); - } - }); - - it('test fully wrapped buffer up to last char with full width odd', async () => { - const input = 'a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301' - + 'a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301'; - await terminal.writeP(input); - const s = terminal.buffer.iterator(true).next().content; - assert.equal(input, s); - for (let i = 0; i < input.length; ++i) { - const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i, true); - assert.equal( - (!(i % 3)) - ? input[i] - : (i % 3 === 1) - ? input.slice(i, i + 2) - : input.slice(i - 1, i + 1), - terminal.buffer.lines.get(bufferIndex[0])!.loadCell(bufferIndex[1], new CellData()).getChars()); - } - }); - - it('should handle \t in lines correctly', async () => { - const input = '\thttps://google.de'; - await terminal.writeP(input); - const s = terminal.buffer.iterator(true).next().content; - assert.equal(s, Array(terminal.optionsService.options.tabStopWidth + 1).join(' ') + 'https://google.de'); - }); - }); - - describe('BufferStringIterator', function (): void { - it('iterator does not overflow buffer limits', async () => { - const terminal = new TestTerminal({ rows: 5, cols: 10, scrollback: 5 }); - const data = [ - 'aaaaaaaaaa', - 'aaaaaaaaa\n', - 'aaaaaaaaaa', - 'aaaaaaaaa\n', - 'aaaaaaaaaa', - 'aaaaaaaaaa', - 'aaaaaaaaaa', - 'aaaaaaaaa\n', - 'aaaaaaaaaa', - 'aaaaaaaaaa' - ]; - await terminal.writeP(data.join('')); - // brute force test with insane values - assert.doesNotThrow(() => { - for (let overscan = 0; overscan < 20; ++overscan) { - for (let start = -10; start < 20; ++start) { - for (let end = -10; end < 20; ++end) { - const it = terminal.buffer.iterator(false, start, end, overscan, overscan); - while (it.hasNext()) { - it.next(); - } - } - } - } - }); - }); - }); - describe('Windows Mode', () => { it('should mark lines as wrapped when the line ends in a non-null character after a LF', async () => { const data = [ diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index d55e961ff3..72ed1cd80f 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -8,7 +8,7 @@ import { IEvent, EventEmitter } from 'common/EventEmitter'; import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IMouseService, IRenderService, ISelectionService, IThemeService } from 'browser/services/Services'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types'; import { IColorSet, ITerminal, ILinkifier2, IBrowser, IViewport, ICompositionHelper, CharacterJoinerHandler, IBufferRange, ReadonlyColorSet } from 'browser/Types'; -import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types'; +import { IBuffer, IBufferSet } from 'common/buffer/Types'; import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, ITerminalOptions, ColorIndex } from 'common/Types'; import { Buffer } from 'common/buffer/Buffer'; import * as Browser from 'common/Platform'; @@ -240,12 +240,6 @@ export class MockBuffer implements IBuffer { public getBlankLine(attr: IAttributeData, isWrapped?: boolean): IBufferLine { return Buffer.prototype.getBlankLine.apply(this, arguments as any); } - public stringIndexToBufferIndex(lineIndex: number, stringIndex: number): number[] { - return Buffer.prototype.stringIndexToBufferIndex.apply(this, arguments as any); - } - public iterator(trimRight: boolean, startIndex?: number, endIndex?: number): IBufferStringIterator { - return Buffer.prototype.iterator.apply(this, arguments as any); - } public getNullCell(attr?: IAttributeData): ICellData { throw new Error('Method not implemented.'); } diff --git a/src/common/buffer/Buffer.ts b/src/common/buffer/Buffer.ts index b935b2a3d1..cabc88faae 100644 --- a/src/common/buffer/Buffer.ts +++ b/src/common/buffer/Buffer.ts @@ -4,7 +4,7 @@ */ import { CircularList, IInsertEvent } from 'common/CircularList'; -import { IBuffer, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult } from 'common/buffer/Types'; +import { IBuffer } from 'common/buffer/Types'; import { IBufferLine, ICellData, IAttributeData, ICharset } from 'common/Types'; import { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine'; import { CellData } from 'common/buffer/CellData'; @@ -14,7 +14,7 @@ import { Marker } from 'common/buffer/Marker'; import { IOptionsService, IBufferService } from 'common/services/Services'; import { DEFAULT_CHARSET } from 'common/data/Charsets'; import { ExtendedAttrs } from 'common/buffer/AttributeData'; -import { DebouncedIdleTask, IdleTaskQueue } from 'common/TaskQueue'; +import { IdleTaskQueue } from 'common/TaskQueue'; export const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1 @@ -510,43 +510,6 @@ export class Buffer implements IBuffer { } } - // private _reflowSmallerGetLinesNeeded() - - /** - * Translates a string index back to a BufferIndex. - * To get the correct buffer position the string must start at `startCol` 0 - * (default in translateBufferLineToString). - * The method also works on wrapped line strings given rows were not trimmed. - * The method operates on the CharData string length, there are no - * additional content or boundary checks. Therefore the string and the buffer - * should not be altered in between. - * TODO: respect trim flag after fixing #1685 - * @param lineIndex line index the string was retrieved from - * @param stringIndex index within the string - * @param trimRight Whether to trim whitespace to the right. - */ - public stringIndexToBufferIndex(lineIndex: number, stringIndex: number, trimRight: boolean = false): BufferIndex { - while (stringIndex) { - const line = this.lines.get(lineIndex); - if (!line) { - return [-1, -1]; - } - const length = (trimRight) ? line.getTrimmedLength() : line.length; - for (let i = 0; i < length; ++i) { - if (line.get(i)[CHAR_DATA_WIDTH_INDEX]) { - // empty cells report a string length of 0, but get replaced - // with a whitespace in translateToString, thus replace with 1 - stringIndex -= line.get(i)[CHAR_DATA_CHAR_INDEX].length || 1; - } - if (stringIndex < 0) { - return [lineIndex, i]; - } - } - lineIndex++; - } - return [lineIndex, 0]; - } - /** * Translates a buffer line to a string, with optional start and end columns. * Wide characters will count as two columns in the resulting string. This @@ -684,65 +647,4 @@ export class Buffer implements IBuffer { this.markers.splice(this.markers.indexOf(marker), 1); } } - - public iterator(trimRight: boolean, startIndex?: number, endIndex?: number, startOverscan?: number, endOverscan?: number): IBufferStringIterator { - return new BufferStringIterator(this, trimRight, startIndex, endIndex, startOverscan, endOverscan); - } -} - -/** - * Iterator to get unwrapped content strings from the buffer. - * The iterator returns at least the string data between the borders - * `startIndex` and `endIndex` (exclusive) and will expand the lines - * by `startOverscan` to the top and by `endOverscan` to the bottom, - * if no new line was found in between. - * It will never read/return string data beyond `startIndex - startOverscan` - * or `endIndex + endOverscan`. Therefore the first and last line might be truncated. - * It is possible to always get the full string for the first and last line as well - * by setting the overscan values to the actual buffer length. This not recommended - * since it might return the whole buffer within a single string in a worst case scenario. - */ -export class BufferStringIterator implements IBufferStringIterator { - private _current: number; - - constructor( - private _buffer: IBuffer, - private _trimRight: boolean, - private _startIndex: number = 0, - private _endIndex: number = _buffer.lines.length, - private _startOverscan: number = 0, - private _endOverscan: number = 0 - ) { - if (this._startIndex < 0) { - this._startIndex = 0; - } - if (this._endIndex > this._buffer.lines.length) { - this._endIndex = this._buffer.lines.length; - } - this._current = this._startIndex; - } - - public hasNext(): boolean { - return this._current < this._endIndex; - } - - public next(): IBufferStringIteratorResult { - const range = this._buffer.getWrappedRangeForLine(this._current); - // limit search window to overscan value at both borders - if (range.first < this._startIndex - this._startOverscan) { - range.first = this._startIndex - this._startOverscan; - } - if (range.last > this._endIndex + this._endOverscan) { - range.last = this._endIndex + this._endOverscan; - } - // limit to current buffer length - range.first = Math.max(range.first, 0); - range.last = Math.min(range.last, this._buffer.lines.length); - let content = ''; - for (let i = range.first; i <= range.last; ++i) { - content += this._buffer.translateBufferLineToString(i, this._trimRight); - } - this._current = range.last + 1; - return { range, content }; - } } diff --git a/src/common/buffer/Types.d.ts b/src/common/buffer/Types.d.ts index 421ddd3721..78f01d55d0 100644 --- a/src/common/buffer/Types.d.ts +++ b/src/common/buffer/Types.d.ts @@ -9,16 +9,6 @@ import { IEvent } from 'common/EventEmitter'; // BufferIndex denotes a position in the buffer: [rowIndex, colIndex] export type BufferIndex = [number, number]; -export interface IBufferStringIteratorResult { - range: { first: number, last: number }; - content: string; -} - -export interface IBufferStringIterator { - hasNext(): boolean; - next(): IBufferStringIteratorResult; -} - export interface IBuffer { readonly lines: ICircularList; ydisp: number; @@ -40,8 +30,6 @@ export interface IBuffer { nextStop(x?: number): number; prevStop(x?: number): number; getBlankLine(attr: IAttributeData, isWrapped?: boolean): IBufferLine; - stringIndexToBufferIndex(lineIndex: number, stringIndex: number, trimRight?: boolean): number[]; - iterator(trimRight: boolean, startIndex?: number, endIndex?: number, startOverscan?: number, endOverscan?: number): IBufferStringIterator; getNullCell(attr?: IAttributeData): ICellData; getWhitespaceCell(attr?: IAttributeData): ICellData; addMarker(y: number): IMarker;