mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Progress on reflow smaller with wide chars
This commit is contained in:
+34
-1
@@ -545,7 +545,40 @@ describe('Buffer', () => {
|
||||
assert.equal(buffer.lines.get(1).translateToString(false), '语汉语汉语 ');
|
||||
});
|
||||
it('should wrap wide characters correctly when reflowing smaller', () => {
|
||||
// TODO: ..
|
||||
buffer.fillViewportRows();
|
||||
buffer.resize(12, 10);
|
||||
for (let i = 0; i < 12; i += 4) {
|
||||
buffer.lines.get(0).set(i, [null, '汉', 2, '汉'.charCodeAt(0)]);
|
||||
buffer.lines.get(1).set(i, [null, '汉', 2, '汉'.charCodeAt(0)]);
|
||||
}
|
||||
for (let i = 2; i < 12; i += 4) {
|
||||
buffer.lines.get(0).set(i, [null, '语', 2, '语'.charCodeAt(0)]);
|
||||
buffer.lines.get(1).set(i, [null, '语', 2, '语'.charCodeAt(0)]);
|
||||
}
|
||||
for (let i = 1; i < 12; i += 2) {
|
||||
buffer.lines.get(0).set(i, [null, '', 0, undefined]);
|
||||
buffer.lines.get(1).set(i, [null, '', 0, undefined]);
|
||||
}
|
||||
buffer.lines.get(1).isWrapped = true;
|
||||
// Buffer:
|
||||
// 汉语汉语汉语 (wrapped)
|
||||
// 汉语汉语汉语
|
||||
assert.equal(buffer.lines.get(0).translateToString(true), '汉语汉语汉语');
|
||||
assert.equal(buffer.lines.get(1).translateToString(true), '汉语汉语汉语');
|
||||
buffer.resize(11, 10);
|
||||
assert.equal(buffer.ybase, 0);
|
||||
assert.equal(buffer.lines.length, 10);
|
||||
assert.equal(buffer.lines.get(0).translateToString(true), '汉语汉语汉', '1');
|
||||
assert.equal(buffer.lines.get(1).translateToString(true), '语汉语汉语', '2');
|
||||
assert.equal(buffer.lines.get(2).translateToString(true), '汉语');
|
||||
buffer.resize(10, 10);
|
||||
assert.equal(buffer.lines.get(0).translateToString(true), '汉语汉语汉');
|
||||
assert.equal(buffer.lines.get(1).translateToString(true), '语汉语汉语');
|
||||
assert.equal(buffer.lines.get(2).translateToString(true), '汉语');
|
||||
buffer.resize(9, 10);
|
||||
assert.equal(buffer.lines.get(0).translateToString(true), '汉语汉语');
|
||||
assert.equal(buffer.lines.get(1).translateToString(true), '汉语汉语');
|
||||
assert.equal(buffer.lines.get(2).translateToString(true), '汉语汉语');
|
||||
});
|
||||
|
||||
describe('reflowLarger cases', () => {
|
||||
|
||||
+17
-7
@@ -9,6 +9,7 @@ import { EventEmitter } from './common/EventEmitter';
|
||||
import { IMarker } from 'xterm';
|
||||
import { BufferLine } from './BufferLine';
|
||||
import { DEFAULT_COLOR } from './renderer/atlas/Types';
|
||||
import { reflowSmallerGetLinesNeeded, reflowSmallerGetNewLineLengths } from './BufferReflow';
|
||||
|
||||
export const DEFAULT_ATTR = (0 << 18) | (DEFAULT_COLOR << 9) | (256 << 0);
|
||||
export const CHAR_DATA_ATTR_INDEX = 0;
|
||||
@@ -386,11 +387,17 @@ export class Buffer implements IBuffer {
|
||||
wrappedLines.unshift(nextLine);
|
||||
}
|
||||
|
||||
// Determine how many lines need to be inserted at the end, based on the trimmed length of
|
||||
// the last wrapped line
|
||||
|
||||
|
||||
const lastLineLength = wrappedLines[wrappedLines.length - 1].getTrimmedLength();
|
||||
const cellsNeeded = (wrappedLines.length - 1) * this._cols + lastLineLength;
|
||||
const linesNeeded = Math.ceil(cellsNeeded / newCols);
|
||||
// const linesNeeded = reflowSmallerGetLinesNeeded(wrappedLines, this._cols, newCols);
|
||||
const destLineLengths = reflowSmallerGetNewLineLengths(wrappedLines, this._cols, newCols);
|
||||
console.log(destLineLengths);
|
||||
const linesNeeded = destLineLengths.length;
|
||||
|
||||
|
||||
|
||||
const linesToAdd = linesNeeded - wrappedLines.length;
|
||||
let trimmedLines: number;
|
||||
if (this.ybase === 0 && this.y !== this.lines.length - 1) {
|
||||
@@ -418,8 +425,8 @@ export class Buffer implements IBuffer {
|
||||
wrappedLines.push(...newLines);
|
||||
|
||||
// Copy buffer data to new locations, this needs to happen backwards to do in-place
|
||||
let destLineIndex = Math.floor(cellsNeeded / newCols);
|
||||
let destCol = cellsNeeded % newCols;
|
||||
let destLineIndex = destLineLengths.length - 1; // Math.floor(cellsNeeded / newCols);
|
||||
let destCol = destLineLengths[destLineIndex]; // cellsNeeded % newCols;
|
||||
if (destCol === 0) {
|
||||
destLineIndex--;
|
||||
destCol = newCols;
|
||||
@@ -432,12 +439,13 @@ export class Buffer implements IBuffer {
|
||||
destCol -= cellsToCopy;
|
||||
if (destCol === 0) {
|
||||
destLineIndex--;
|
||||
destCol = newCols;
|
||||
destCol = destLineLengths[destLineIndex];
|
||||
}
|
||||
srcCol -= cellsToCopy;
|
||||
if (srcCol === 0) {
|
||||
srcLineIndex--;
|
||||
srcCol = this._cols;
|
||||
// TODO: srcCol shoudl take trimmed length into account
|
||||
srcCol = wrappedLines[Math.max(srcLineIndex, 0)].getTrimmedLength(); // this._cols;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -516,6 +524,8 @@ 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
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
import { assert } from 'chai';
|
||||
import { BufferLine } from './BufferLine';
|
||||
import { reflowSmallerGetNewLineLengths } from './BufferReflow';
|
||||
|
||||
describe('BufferReflow', () => {
|
||||
describe('reflowSmallerGetNewLineLengths', () => {
|
||||
it('should return correct line lengths for a small line with wide characters', () => {
|
||||
const line = new BufferLine(4);
|
||||
line.set(0, [null, '汉', 2, '汉'.charCodeAt(0)]);
|
||||
line.set(1, [null, '', 0, undefined]);
|
||||
line.set(2, [null, '语', 2, '语'.charCodeAt(0)]);
|
||||
line.set(3, [null, '', 0, undefined]);
|
||||
assert.equal(line.translateToString(true), '汉语');
|
||||
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 4, 3), [2, 2], 'line: 汉, 语');
|
||||
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 4, 2), [2, 2], 'line: 汉, 语');
|
||||
});
|
||||
it('should return correct line lengths for a large line with wide characters', () => {
|
||||
const line = new BufferLine(12);
|
||||
for (let i = 0; i < 12; i += 4) {
|
||||
line.set(i, [null, '汉', 2, '汉'.charCodeAt(0)]);
|
||||
line.set(i + 2, [null, '语', 2, '语'.charCodeAt(0)]);
|
||||
}
|
||||
for (let i = 1; i < 12; i += 2) {
|
||||
line.set(i, [null, '', 0, undefined]);
|
||||
line.set(i, [null, '', 0, undefined]);
|
||||
}
|
||||
assert.equal(line.translateToString(), '汉语汉语汉语');
|
||||
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 12, 11), [10, 2], 'line: 汉语汉语汉, 语');
|
||||
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 12, 10), [10, 2], 'line: 汉语汉语汉, 语');
|
||||
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 12, 9), [8, 4], 'line: 汉语汉语, 汉语');
|
||||
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 12, 8), [8, 4], 'line: 汉语汉语, 汉语');
|
||||
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 12, 7), [6, 6], 'line: 汉语汉, 语汉语');
|
||||
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 12, 6), [6, 6], 'line: 汉语汉, 语汉语');
|
||||
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 12, 5), [4, 4, 4], 'line: 汉语, 汉语, 汉语');
|
||||
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 12, 4), [4, 4, 4], 'line: 汉语, 汉语, 汉语');
|
||||
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 12, 3), [2, 2, 2, 2, 2, 2], 'line: 汉, 语, 汉, 语, 汉, 语');
|
||||
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 12, 2), [2, 2, 2, 2, 2, 2], 'line: 汉, 语, 汉, 语, 汉, 语');
|
||||
});
|
||||
it('should return correct line lengths for a string with wide and single characters', () => {
|
||||
const line = new BufferLine(6);
|
||||
line.set(0, [null, 'a', 1, 'a'.charCodeAt(0)]);
|
||||
line.set(1, [null, '汉', 2, '汉'.charCodeAt(0)]);
|
||||
line.set(2, [null, '', 0, undefined]);
|
||||
line.set(3, [null, '语', 2, '语'.charCodeAt(0)]);
|
||||
line.set(4, [null, '', 0, undefined]);
|
||||
line.set(5, [null, 'b', 1, 'b'.charCodeAt(0)]);
|
||||
assert.equal(line.translateToString(), 'a汉语b');
|
||||
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 6, 5), [5, 1], 'line: a汉语b');
|
||||
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 6, 4), [3, 3], 'line: a汉, 语b');
|
||||
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 6, 3), [3, 3], 'line: a汉, 语b');
|
||||
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 6, 2), [1, 2, 2, 1], 'line: a, 汉, 语, b');
|
||||
});
|
||||
it('should return correct line lengths for a wrapped line with wide and single characters', () => {
|
||||
const line1 = new BufferLine(6);
|
||||
line1.set(0, [null, 'a', 1, 'a'.charCodeAt(0)]);
|
||||
line1.set(1, [null, '汉', 2, '汉'.charCodeAt(0)]);
|
||||
line1.set(2, [null, '', 0, undefined]);
|
||||
line1.set(3, [null, '语', 2, '语'.charCodeAt(0)]);
|
||||
line1.set(4, [null, '', 0, undefined]);
|
||||
line1.set(5, [null, 'b', 1, 'b'.charCodeAt(0)]);
|
||||
const line2 = new BufferLine(6, undefined, true);
|
||||
line2.set(0, [null, 'a', 1, 'a'.charCodeAt(0)]);
|
||||
line2.set(1, [null, '汉', 2, '汉'.charCodeAt(0)]);
|
||||
line2.set(2, [null, '', 0, undefined]);
|
||||
line2.set(3, [null, '语', 2, '语'.charCodeAt(0)]);
|
||||
line2.set(4, [null, '', 0, undefined]);
|
||||
line2.set(5, [null, 'b', 1, 'b'.charCodeAt(0)]);
|
||||
assert.equal(line1.translateToString(), 'a汉语b');
|
||||
assert.equal(line2.translateToString(), 'a汉语b');
|
||||
assert.deepEqual(reflowSmallerGetNewLineLengths([line1, line2], 6, 5), [5, 4, 3], 'lines: a汉语, ba汉, 语b');
|
||||
assert.deepEqual(reflowSmallerGetNewLineLengths([line1, line2], 6, 4), [3, 4, 4, 1], 'lines: a汉, 语ba, 汉语, b');
|
||||
assert.deepEqual(reflowSmallerGetNewLineLengths([line1, line2], 6, 3), [3, 3, 3, 3], 'lines: a汉, 语b, a汉, 语b');
|
||||
assert.deepEqual(reflowSmallerGetNewLineLengths([line1, line2], 6, 2), [1, 2, 2, 2, 2, 2, 1], 'lines: a, 汉, 语, ba, 汉, 语, b');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { BufferLine } from './BufferLine';
|
||||
|
||||
/**
|
||||
* Determine how many lines need to be inserted at the end. This is done by finding what each
|
||||
* wrapping point will be and counting the lines needed This would be a lot simpler but in the case
|
||||
* of a line ending with a wide character, the wide character needs to be put on the following line
|
||||
* or it would be cut in half.
|
||||
* @param wrappedLines The original wrapped lines.
|
||||
* @param newCols The new column count.
|
||||
*/
|
||||
export function reflowSmallerGetLinesNeeded(wrappedLines: BufferLine[], oldCols: number, newCols: number): number {
|
||||
const lastLineLength = wrappedLines[wrappedLines.length - 1].getTrimmedLength();
|
||||
// const cellsNeeded = (wrappedLines.length - 1) * this._cols + lastLineLength;
|
||||
|
||||
// TODO: Make faster
|
||||
const cellsNeeded = wrappedLines.map(l => l.getTrimmedLength()).reduce((p, c) => p + c);
|
||||
|
||||
// Lines needed needs to take into account what the ending character of each new line is
|
||||
let linesNeeded = 0;
|
||||
let cellsAvailable = 0;
|
||||
// let currentCol = 0;
|
||||
|
||||
// Use srcCol and srcLine to find the new wrapping point, use that to get the cellsAvailable and
|
||||
// linesNeeded
|
||||
let srcCol = -1;
|
||||
let srcLine = 0;
|
||||
while (cellsAvailable < cellsNeeded) {
|
||||
// if (srcLine === wrappedLines.length - 1) {
|
||||
// cellsAvailable += newCols;
|
||||
// linesNeeded++;
|
||||
// break;
|
||||
// }
|
||||
|
||||
srcCol += newCols;
|
||||
if (srcCol >= oldCols) {
|
||||
srcCol -= oldCols;
|
||||
srcLine++;
|
||||
}
|
||||
if (srcLine >= wrappedLines.length) {
|
||||
linesNeeded++;
|
||||
break;
|
||||
}
|
||||
const endsWithWide = wrappedLines[srcLine].getWidth(srcCol) === 2;
|
||||
if (endsWithWide) {
|
||||
srcCol--;
|
||||
}
|
||||
cellsAvailable += endsWithWide ? newCols - 1 : newCols;
|
||||
linesNeeded++;
|
||||
}
|
||||
|
||||
return linesNeeded;
|
||||
// return Math.ceil(cellsNeeded / newCols);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the new line lengths for a given wrapped line. The purpose of this function it to pre-
|
||||
* compute the wrapping points since wide characters may need to be wrapped onto the following line.
|
||||
* This function will return an array of numbers of where each line wraps to, the resulting array
|
||||
* will only contain the values `newCols` (when the line does not end with a wide character) and
|
||||
* `newCols - 1` (when the line does end with a wide character), except for the last value which
|
||||
* will contain the remaining items to fill the line.
|
||||
*
|
||||
* Calling this with a `newCols` value of `1` will lock up.
|
||||
*
|
||||
* @param wrappedLines The wrapped lines to evaluate.
|
||||
* @param oldCols The columns before resize.
|
||||
* @param newCols The columns after resize.
|
||||
*/
|
||||
export function reflowSmallerGetNewLineLengths(wrappedLines: BufferLine[], oldCols: number, newCols: number): number[] {
|
||||
const newLineLengths: number[] = [];
|
||||
|
||||
// TODO: Force cols = 2 to be minimum possible value, this will lock up
|
||||
|
||||
const cellsNeeded = wrappedLines.map(l => l.getTrimmedLength()).reduce((p, c) => p + c);
|
||||
|
||||
// Use srcCol and srcLine to find the new wrapping point, use that to get the cellsAvailable and
|
||||
// linesNeeded
|
||||
let srcCol = -1;
|
||||
let srcLine = 0;
|
||||
let cellsAvailable = 0;
|
||||
while (cellsAvailable < cellsNeeded) {
|
||||
srcCol += newCols;
|
||||
if (srcCol >= oldCols) {
|
||||
srcCol -= oldCols;
|
||||
srcLine++;
|
||||
}
|
||||
if (srcLine >= wrappedLines.length) {
|
||||
// Add the final line and exit the loop
|
||||
newLineLengths.push(cellsNeeded - cellsAvailable);
|
||||
break;
|
||||
}
|
||||
const endsWithWide = wrappedLines[srcLine].getWidth(srcCol) === 2;
|
||||
if (endsWithWide) {
|
||||
srcCol--;
|
||||
}
|
||||
const lineLength = endsWithWide ? newCols - 1 : newCols;
|
||||
newLineLengths.push(lineLength);
|
||||
cellsAvailable += lineLength;
|
||||
}
|
||||
|
||||
return newLineLengths;
|
||||
}
|
||||
Reference in New Issue
Block a user