mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
BufferStringIterator
This commit is contained in:
+26
-33
@@ -355,33 +355,26 @@ describe('Buffer', () => {
|
||||
terminal.write = (s: string): void => {
|
||||
oldWrite(s);
|
||||
(terminal as any)._innerWrite();
|
||||
}
|
||||
};
|
||||
});
|
||||
function wholeString(terminal: ITerminal, range : {first: number, last: number}): string {
|
||||
let result = '';
|
||||
for (let i = range.first; i <= range.last; ++i) {
|
||||
result += terminal.buffer.translateBufferLineToString(i, i == range.last);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
it('multiline ascii', function(): void {
|
||||
const input = 'This is ASCII text spanning multiple lines.';
|
||||
terminal.write(input);
|
||||
const s = wholeString(terminal, terminal.buffer.getWrappedRangeForLine(0));
|
||||
const s = terminal.buffer.contents(true).toArray()[0];
|
||||
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);
|
||||
assert.deepEqual([(i / terminal.cols) | 0, i % terminal.cols], bufferIndex);
|
||||
}
|
||||
});
|
||||
it('combining e\u0301 in a sentence', function(): void {
|
||||
const input = 'Sitting in the cafe\u0301 drinking coffee.';
|
||||
terminal.write(input);
|
||||
const s = wholeString(terminal, terminal.buffer.getWrappedRangeForLine(0));
|
||||
const s = terminal.buffer.contents(true).toArray()[0];
|
||||
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);
|
||||
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(
|
||||
@@ -390,28 +383,28 @@ describe('Buffer', () => {
|
||||
// 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);
|
||||
assert.deepEqual([((i - 1) / terminal.cols) | 0, (i - 1) % terminal.cols], bufferIndex);
|
||||
}
|
||||
});
|
||||
it('multiline combining e\u0301', function(): void {
|
||||
const input = 'e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301';
|
||||
terminal.write(input);
|
||||
const s = wholeString(terminal, terminal.buffer.getWrappedRangeForLine(0));
|
||||
const s = terminal.buffer.contents(true).toArray()[0];
|
||||
assert.equal(input, s);
|
||||
// every buffer cell index contains 2 string indices
|
||||
// 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);
|
||||
assert.deepEqual([((i >> 1) / terminal.cols) | 0, (i >> 1) % terminal.cols], bufferIndex);
|
||||
}
|
||||
});
|
||||
it('surrogate char in a sentence', function(): void {
|
||||
const input = 'The 𝄞 is a clef widely used in modern notation.';
|
||||
terminal.write(input);
|
||||
const s = wholeString(terminal, terminal.buffer.getWrappedRangeForLine(0));
|
||||
const s = terminal.buffer.contents(true).toArray()[0];
|
||||
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);
|
||||
assert.deepEqual([(i / terminal.cols) | 0, i % terminal.cols], bufferIndex);
|
||||
}
|
||||
// string index 4 & 5 point to surrogate char 𝄞 ---> same buffer Index
|
||||
assert.deepEqual(
|
||||
@@ -420,53 +413,53 @@ describe('Buffer', () => {
|
||||
// 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);
|
||||
assert.deepEqual([((i - 1) / terminal.cols) | 0, (i - 1) % terminal.cols], bufferIndex);
|
||||
}
|
||||
});
|
||||
it('multiline surrogate char', function(): void {
|
||||
const input = '𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞';
|
||||
terminal.write(input);
|
||||
const s = wholeString(terminal, terminal.buffer.getWrappedRangeForLine(0));
|
||||
const s = terminal.buffer.contents(true).toArray()[0];
|
||||
assert.equal(input, s);
|
||||
// every buffer cell index contains 2 string indices
|
||||
// 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);
|
||||
assert.deepEqual([((i >> 1) / terminal.cols) | 0, (i >> 1) % terminal.cols], bufferIndex);
|
||||
}
|
||||
});
|
||||
it('surrogate char with combining', function(): void {
|
||||
// eye of Ra with acute accent - string length of 3
|
||||
const input = '𓂀\u0301 - the eye hiroglyph with an acute accent.';
|
||||
terminal.write(input);
|
||||
const s = wholeString(terminal, terminal.buffer.getWrappedRangeForLine(0));
|
||||
const s = terminal.buffer.contents(true).toArray()[0];
|
||||
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);
|
||||
assert.deepEqual([((i - 2) / terminal.cols) | 0, (i - 2) % terminal.cols], bufferIndex);
|
||||
}
|
||||
});
|
||||
it('multiline surrogate with combining', function(): void {
|
||||
const input = '𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301';
|
||||
terminal.write(input);
|
||||
const s = wholeString(terminal, terminal.buffer.getWrappedRangeForLine(0));
|
||||
const s = terminal.buffer.contents(true).toArray()[0];
|
||||
assert.equal(input, s);
|
||||
// every buffer cell index contains 3 string indices
|
||||
// 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);
|
||||
assert.deepEqual([(((i / 3) | 0) / terminal.cols) | 0, ((i / 3) | 0) % terminal.cols], bufferIndex);
|
||||
}
|
||||
});
|
||||
it('fullwidth chars', function(): void {
|
||||
const input = 'These 123 are some fat numbers.';
|
||||
terminal.write(input);
|
||||
const s = wholeString(terminal, terminal.buffer.getWrappedRangeForLine(0));
|
||||
const s = terminal.buffer.contents(true).toArray()[0];
|
||||
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);
|
||||
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));
|
||||
@@ -474,23 +467,23 @@ describe('Buffer', () => {
|
||||
// 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);
|
||||
assert.deepEqual([((i + 3) / terminal.cols) | 0, (i + 3) % terminal.cols], bufferIndex);
|
||||
}
|
||||
});
|
||||
it('multiline fullwidth chars', function(): void {
|
||||
const input = '12345678901234567890';
|
||||
terminal.write(input);
|
||||
const s = wholeString(terminal, terminal.buffer.getWrappedRangeForLine(0));
|
||||
const s = terminal.buffer.contents(true).toArray()[0];
|
||||
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);
|
||||
assert.deepEqual([((i << 1) / terminal.cols) | 0, (i << 1) % terminal.cols], bufferIndex);
|
||||
}
|
||||
});
|
||||
it('fullwidth combining with emoji - match emoji cell', function(): void {
|
||||
const input = 'Lots of ¥\u0301 make me 😃.';
|
||||
terminal.write(input);
|
||||
const s = wholeString(terminal, terminal.buffer.getWrappedRangeForLine(0));
|
||||
const s = terminal.buffer.contents(true).toArray()[0];
|
||||
assert.equal(input, s);
|
||||
const stringIndex = s.match(/😃/).index;
|
||||
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, stringIndex);
|
||||
|
||||
+38
-3
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { CircularList } from './common/CircularList';
|
||||
import { CharData, ITerminal, IBuffer, IBufferLine } from './Types';
|
||||
import { CharData, ITerminal, IBuffer, IBufferLine, BufferIndex, IBufferStringIterator } from './Types';
|
||||
import { EventEmitter } from './common/EventEmitter';
|
||||
import { IMarker } from 'xterm';
|
||||
import { BufferLine } from './BufferLine';
|
||||
@@ -194,12 +194,12 @@ export class Buffer implements IBuffer {
|
||||
this.scrollBottom = newRows - 1;
|
||||
}
|
||||
|
||||
public stringIndexToBufferIndex(lineIndex: number, stringIndex: number): number[] {
|
||||
public stringIndexToBufferIndex(lineIndex: number, stringIndex: number): BufferIndex {
|
||||
if (!stringIndex) {
|
||||
return [lineIndex, 0];
|
||||
}
|
||||
while (stringIndex) {
|
||||
let line = this.lines.get(lineIndex);
|
||||
const line = this.lines.get(lineIndex);
|
||||
if (!line) {
|
||||
[-1, -1];
|
||||
}
|
||||
@@ -360,6 +360,10 @@ export class Buffer implements IBuffer {
|
||||
// TODO: This could probably be optimized by relying on sort order and trimming the array using .length
|
||||
this.markers.splice(this.markers.indexOf(marker), 1);
|
||||
}
|
||||
|
||||
public contents(trimRight: boolean, startIndex?: number, endIndex?: number): IBufferStringIterator {
|
||||
return new BufferStringIterator(this, trimRight, startIndex, endIndex);
|
||||
}
|
||||
}
|
||||
|
||||
export class Marker extends EventEmitter implements IMarker {
|
||||
@@ -386,3 +390,34 @@ export class Marker extends EventEmitter implements IMarker {
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class BufferStringIterator implements IBufferStringIterator {
|
||||
private _start: number;
|
||||
private _end: number;
|
||||
private _current: number;
|
||||
constructor (private _buffer: IBuffer, private _trimRight: boolean, startIndex?: number, endIndex?: number) {
|
||||
this._start = startIndex || 0;
|
||||
this._end = endIndex || this._buffer.lines.length;
|
||||
this._current = this._start;
|
||||
}
|
||||
public hasNext(): boolean {
|
||||
return this._current < this._end;
|
||||
}
|
||||
public next(withRanges: boolean = false): string | [{first: number, last: number}, string] {
|
||||
const range = this._buffer.getWrappedRangeForLine(this._current);
|
||||
let result = '';
|
||||
for (let i = range.first; i <= range.last; ++i) {
|
||||
result += this._buffer.translateBufferLineToString(i, (this._trimRight) ? i === range.last : false);
|
||||
}
|
||||
this._current = range.last;
|
||||
this._current++;
|
||||
return (withRanges) ? [range, result] : result;
|
||||
}
|
||||
toArray(): string[] {
|
||||
const result: string[] = [];
|
||||
while (this.hasNext()) {
|
||||
result.push(this.next() as string);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@ export type LinkMatcherValidationCallback = (uri: string, callback: (isValid: bo
|
||||
|
||||
export type CharacterJoinerHandler = (text: string) => [number, number][];
|
||||
|
||||
// BufferIndex denotes a position in the buffer: [rowIndex, colIndex]
|
||||
export type BufferIndex = [number, number];
|
||||
|
||||
export const enum LinkHoverEventTypes {
|
||||
HOVER = 'linkhover',
|
||||
TOOLTIP = 'linktooltip',
|
||||
@@ -265,6 +268,12 @@ export interface ITerminalOptions extends IPublicTerminalOptions {
|
||||
useFlowControl?: boolean;
|
||||
}
|
||||
|
||||
export interface IBufferStringIterator {
|
||||
hasNext(): boolean;
|
||||
next(withRanges: boolean): string | [{first: number, last: number}, string];
|
||||
toArray(): string[];
|
||||
}
|
||||
|
||||
export interface IBuffer {
|
||||
readonly lines: ICircularList<IBufferLine>;
|
||||
ydisp: number;
|
||||
@@ -283,6 +292,7 @@ export interface IBuffer {
|
||||
nextStop(x?: number): number;
|
||||
prevStop(x?: number): number;
|
||||
stringIndexToBufferIndex(lineIndex: number, stringIndex: number): number[];
|
||||
contents(trimRight: boolean, startIndex?: number, endIndex?: number): IBufferStringIterator;
|
||||
}
|
||||
|
||||
export interface IBufferSet extends IEventEmitter {
|
||||
|
||||
Reference in New Issue
Block a user