mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
add ability to register/deregister character joiner
This commit is contained in:
+13
-1
@@ -21,7 +21,7 @@
|
||||
* http://linux.die.net/man/7/urxvt
|
||||
*/
|
||||
|
||||
import { ICharset, IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharData, LineData } from './Types';
|
||||
import { ICharset, IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharData, LineData, CharacterJoinerHandler } from './Types';
|
||||
import { IMouseZoneManager } from './input/Types';
|
||||
import { IRenderer } from './renderer/Types';
|
||||
import { BufferSet } from './BufferSet';
|
||||
@@ -1373,6 +1373,18 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
||||
}
|
||||
}
|
||||
|
||||
public registerCharacterJoiner(handler: CharacterJoinerHandler): number {
|
||||
const joinerId = this.renderer.registerCharacterJoiner(handler);
|
||||
this.refresh(0, this.rows - 1);
|
||||
return joinerId;
|
||||
}
|
||||
|
||||
public deregisterCharacterJoiner(joinerId: number): void {
|
||||
if (this.renderer.deregisterCharacterJoiner(joinerId)) {
|
||||
this.refresh(0, this.rows - 1);
|
||||
}
|
||||
}
|
||||
|
||||
public get markers(): IMarker[] {
|
||||
return this.buffer.markers;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ export type LineData = CharData[];
|
||||
export type LinkMatcherHandler = (event: MouseEvent, uri: string) => void;
|
||||
export type LinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void;
|
||||
|
||||
export type CharacterJoinerHandler = (text: string) => [number, number][];
|
||||
|
||||
export const enum LinkHoverEventTypes {
|
||||
HOVER = 'linkhover',
|
||||
TOOLTIP = 'linktooltip',
|
||||
|
||||
@@ -7,8 +7,8 @@ import { TextRenderLayer } from './TextRenderLayer';
|
||||
import { SelectionRenderLayer } from './SelectionRenderLayer';
|
||||
import { CursorRenderLayer } from './CursorRenderLayer';
|
||||
import { ColorManager } from './ColorManager';
|
||||
import { IRenderLayer, IColorSet, IRenderer, IRenderDimensions } from './Types';
|
||||
import { ITerminal } from '../Types';
|
||||
import { IRenderLayer, IColorSet, IRenderer, IRenderDimensions, ICharacterJoiner } from './Types';
|
||||
import { ITerminal, CharacterJoinerHandler } from '../Types';
|
||||
import { LinkRenderLayer } from './LinkRenderLayer';
|
||||
import { EventEmitter } from '../EventEmitter';
|
||||
import { RenderDebouncer } from '../utils/RenderDebouncer';
|
||||
@@ -23,6 +23,8 @@ export class Renderer extends EventEmitter implements IRenderer {
|
||||
private _screenDprMonitor: ScreenDprMonitor;
|
||||
private _isPaused: boolean = false;
|
||||
private _needsFullRefresh: boolean = false;
|
||||
private _joiners: ICharacterJoiner[] = [];
|
||||
private _nextJoinerId: number = 0;
|
||||
|
||||
public colorManager: ColorManager;
|
||||
public dimensions: IRenderDimensions;
|
||||
@@ -66,7 +68,7 @@ export class Renderer extends EventEmitter implements IRenderer {
|
||||
// Detect whether IntersectionObserver is detected and enable renderer pause
|
||||
// and resume based on terminal visibility if so
|
||||
if ('IntersectionObserver' in window) {
|
||||
const observer = new IntersectionObserver(e => this.onIntersectionChange(e[0]), {threshold: 0});
|
||||
const observer = new IntersectionObserver(e => this.onIntersectionChange(e[0]), { threshold: 0 });
|
||||
observer.observe(this._terminal.element);
|
||||
}
|
||||
}
|
||||
@@ -186,7 +188,7 @@ export class Renderer extends EventEmitter implements IRenderer {
|
||||
*/
|
||||
private _renderRows(start: number, end: number): void {
|
||||
this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end));
|
||||
this._terminal.emit('refresh', {start, end});
|
||||
this._terminal.emit('refresh', { start, end });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -248,4 +250,35 @@ export class Renderer extends EventEmitter implements IRenderer {
|
||||
this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._terminal.rows;
|
||||
this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._terminal.cols;
|
||||
}
|
||||
|
||||
public registerCharacterJoiner(handler: CharacterJoinerHandler): number {
|
||||
const joiner: ICharacterJoiner = {
|
||||
id: this._nextJoinerId++,
|
||||
handler: handler
|
||||
};
|
||||
|
||||
this._renderLayers.forEach(l => {
|
||||
if (l.registerCharacterJoiner) {
|
||||
l.registerCharacterJoiner(joiner);
|
||||
}
|
||||
});
|
||||
|
||||
return joiner.id;
|
||||
}
|
||||
|
||||
public deregisterCharacterJoiner(joinerId: number): boolean {
|
||||
for (let i = 0; i < this._joiners.length; i++) {
|
||||
if (this._joiners[i].id === joinerId) {
|
||||
this._joiners.splice(i, 1);
|
||||
this._renderLayers.forEach(l => {
|
||||
if (l.deregisterCharacterJoiner) {
|
||||
l.deregisterCharacterJoiner(joinerId);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
*/
|
||||
|
||||
import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from '../Buffer';
|
||||
import { FLAGS, IColorSet, IRenderDimensions } from './Types';
|
||||
import { FLAGS, IColorSet, IRenderDimensions, ICharacterJoiner } from './Types';
|
||||
import { CharData, ITerminal } from '../Types';
|
||||
import { INVERTED_DEFAULT_COLOR } from './atlas/Types';
|
||||
import { GridCache } from './GridCache';
|
||||
import { BaseRenderLayer } from './BaseRenderLayer';
|
||||
import { merge } from '../utils/MergeRanges';
|
||||
|
||||
/**
|
||||
* This CharData looks like a null character, which will forc a clear and render
|
||||
@@ -22,6 +23,7 @@ export class TextRenderLayer extends BaseRenderLayer {
|
||||
private _characterWidth: number;
|
||||
private _characterFont: string;
|
||||
private _characterOverlapCache: { [key: string]: boolean } = {};
|
||||
private _joiners: ICharacterJoiner[] = [];
|
||||
|
||||
constructor(container: HTMLElement, zIndex: number, colors: IColorSet, alpha: boolean) {
|
||||
super(container, 'text', zIndex, alpha, colors);
|
||||
@@ -52,6 +54,7 @@ export class TextRenderLayer extends BaseRenderLayer {
|
||||
terminal: ITerminal,
|
||||
firstRow: number,
|
||||
lastRow: number,
|
||||
foreground: boolean,
|
||||
callback: (
|
||||
code: number,
|
||||
char: string,
|
||||
@@ -66,10 +69,12 @@ export class TextRenderLayer extends BaseRenderLayer {
|
||||
for (let y = firstRow; y <= lastRow; y++) {
|
||||
const row = y + terminal.buffer.ydisp;
|
||||
const line = terminal.buffer.lines.get(row);
|
||||
let index = 0;
|
||||
const joinedRanges = foreground ? this._getJoinedCharacters(terminal, row) : [];
|
||||
for (let x = 0; x < terminal.cols; x++) {
|
||||
const charData = line[x];
|
||||
let charData = line[x];
|
||||
const code: number = <number>charData[CHAR_DATA_CODE_INDEX];
|
||||
const char: string = charData[CHAR_DATA_CHAR_INDEX];
|
||||
let char: string = charData[CHAR_DATA_CHAR_INDEX];
|
||||
const attr: number = charData[CHAR_DATA_ATTR_INDEX];
|
||||
let width: number = charData[CHAR_DATA_WIDTH_INDEX];
|
||||
|
||||
@@ -79,6 +84,41 @@ export class TextRenderLayer extends BaseRenderLayer {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Just in case we ended up in the middle of a range, lop off any
|
||||
// ranges that have already passed
|
||||
while (joinedRanges.length > 0 && joinedRanges[0][0] < x) {
|
||||
joinedRanges.shift();
|
||||
}
|
||||
|
||||
// Process any joined character ranges as needed. Because of how the
|
||||
// ranges are produced, we know that they are valid for the characters
|
||||
// and attributes of our input.
|
||||
let lastCharX = x;
|
||||
if (joinedRanges.length > 0 && x === joinedRanges[0][0]) {
|
||||
const range = joinedRanges.shift();
|
||||
|
||||
// We need to start the searching at the next character
|
||||
lastCharX++;
|
||||
index++;
|
||||
|
||||
// Build up the string
|
||||
for (; lastCharX < terminal.cols && index < range[1]; lastCharX++) {
|
||||
charData = line[lastCharX];
|
||||
if (charData[CHAR_DATA_WIDTH_INDEX] !== 0) {
|
||||
char += charData[CHAR_DATA_CHAR_INDEX];
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
// Update our data accordingly. We use the width of the last character
|
||||
// for the rest of the checks and decrement our column/index so that
|
||||
// they align with the last character matched rather than the next
|
||||
// character in the sequence.
|
||||
width = charData[CHAR_DATA_WIDTH_INDEX];
|
||||
lastCharX--;
|
||||
index--;
|
||||
}
|
||||
|
||||
// If the character is an overlapping char and the character to the right is a
|
||||
// space, take ownership of the cell to the right.
|
||||
if (this._isOverlapping(charData)) {
|
||||
@@ -89,7 +129,7 @@ export class TextRenderLayer extends BaseRenderLayer {
|
||||
// get removed, and `a` would not re-render because it thinks it's
|
||||
// already in the correct state.
|
||||
// this._state.cache[x][y] = OVERLAP_OWNED_CHAR_DATA;
|
||||
if (x < line.length - 1 && line[x + 1][CHAR_DATA_CODE_INDEX] === 32 /*' '*/) {
|
||||
if (lastCharX < line.length - 1 && line[lastCharX + 1][CHAR_DATA_CODE_INDEX] === 32 /*' '*/) {
|
||||
width = 2;
|
||||
// this._clearChar(x + 1, y);
|
||||
// The overlapping char's char data will force a clear and render when the
|
||||
@@ -116,7 +156,19 @@ export class TextRenderLayer extends BaseRenderLayer {
|
||||
}
|
||||
}
|
||||
|
||||
callback(code, char, width, x, y, fg, bg, flags);
|
||||
callback(
|
||||
char.length === 1 ? code : Infinity,
|
||||
char,
|
||||
char.length + width - 1,
|
||||
x,
|
||||
y,
|
||||
fg,
|
||||
bg,
|
||||
flags
|
||||
);
|
||||
|
||||
x = lastCharX;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,7 +186,7 @@ export class TextRenderLayer extends BaseRenderLayer {
|
||||
|
||||
ctx.save();
|
||||
|
||||
this._forEachCell(terminal, firstRow, lastRow, (code, char, width, x, y, fg, bg, flags) => {
|
||||
this._forEachCell(terminal, firstRow, lastRow, false, (code, char, width, x, y, fg, bg, flags) => {
|
||||
// libvte and xterm both draw the background (but not foreground) of invisible characters,
|
||||
// so we should too.
|
||||
let nextFillStyle = null; // null represents default background color
|
||||
@@ -176,7 +228,7 @@ export class TextRenderLayer extends BaseRenderLayer {
|
||||
}
|
||||
|
||||
private _drawForeground(terminal: ITerminal, firstRow: number, lastRow: number): void {
|
||||
this._forEachCell(terminal, firstRow, lastRow, (code, char, width, x, y, fg, bg, flags) => {
|
||||
this._forEachCell(terminal, firstRow, lastRow, true, (code, char, width, x, y, fg, bg, flags) => {
|
||||
if (flags & FLAGS.INVISIBLE) {
|
||||
return;
|
||||
}
|
||||
@@ -190,7 +242,7 @@ export class TextRenderLayer extends BaseRenderLayer {
|
||||
} else {
|
||||
this._ctx.fillStyle = this._colors.foreground.css;
|
||||
}
|
||||
this.fillBottomLineAtCells(x, y);
|
||||
this.fillBottomLineAtCells(x, y, width);
|
||||
this._ctx.restore();
|
||||
}
|
||||
this.drawChar(
|
||||
@@ -219,6 +271,19 @@ export class TextRenderLayer extends BaseRenderLayer {
|
||||
this.setTransparency(terminal, terminal.options.allowTransparency);
|
||||
}
|
||||
|
||||
public registerCharacterJoiner(joiner: ICharacterJoiner): void {
|
||||
this._joiners.push(joiner);
|
||||
}
|
||||
|
||||
public deregisterCharacterJoiner(joinerId: number): void {
|
||||
for (let i = 0; i < this._joiners.length; i++) {
|
||||
if (this._joiners[i].id === joinerId) {
|
||||
this._joiners.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a character is overlapping to the next cell.
|
||||
*/
|
||||
@@ -258,6 +323,78 @@ export class TextRenderLayer extends BaseRenderLayer {
|
||||
return overlaps;
|
||||
}
|
||||
|
||||
private _getJoinedCharacters(terminal: ITerminal, row: number): [number, number][] {
|
||||
if (this._joiners.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const line = terminal.buffer.lines.get(row);
|
||||
if (line.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const ranges: [number, number][] = [];
|
||||
const lineStr = terminal.buffer.translateBufferLineToString(row, true);
|
||||
|
||||
let currentIndex = 0;
|
||||
let rangeStartIndex = 0;
|
||||
let rangeAttr = line[0][CHAR_DATA_ATTR_INDEX] >> 9;
|
||||
for (let x = 0; x < terminal.cols; x++) {
|
||||
const charData = line[x];
|
||||
const width = charData[CHAR_DATA_WIDTH_INDEX];
|
||||
const attr = charData[CHAR_DATA_ATTR_INDEX] >> 9;
|
||||
|
||||
if (width === 0) {
|
||||
// If this character is of width 0, skip it
|
||||
continue;
|
||||
}
|
||||
|
||||
// End of range
|
||||
if (attr !== rangeAttr) {
|
||||
// If we ended up with a sequence of more than one character, look for
|
||||
// ranges to join
|
||||
if (currentIndex - rangeStartIndex > 1) {
|
||||
const subRanges = this._getSubRanges(lineStr, rangeStartIndex, currentIndex);
|
||||
for (let i = 0; i < subRanges.length; i++) {
|
||||
ranges.push(subRanges[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset our markers for a new range
|
||||
rangeStartIndex = x;
|
||||
rangeAttr = attr;
|
||||
}
|
||||
|
||||
currentIndex++;
|
||||
}
|
||||
|
||||
// Process any trailing ranges
|
||||
if (currentIndex - rangeStartIndex > 1) {
|
||||
const subRanges = this._getSubRanges(lineStr, rangeStartIndex, currentIndex);
|
||||
for (let i = 0; i < subRanges.length; i++) {
|
||||
ranges.push(subRanges[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return ranges;
|
||||
}
|
||||
|
||||
private _getSubRanges(line: string, startIndex: number, endIndex: number): [number, number][] {
|
||||
const text = line.substring(startIndex, endIndex);
|
||||
// At this point we already know that there is at least one joiner so
|
||||
// we can just pull its value and assign it directly rather than
|
||||
// merging it into an empty array, which incurs unnecessary writes.
|
||||
const subRanges: [number, number][] = this._joiners[0].handler(text);
|
||||
for (let i = 1; i < this._joiners.length; i++) {
|
||||
// We merge any overlapping ranges across the different joiners
|
||||
const joinerSubRanges = this._joiners[i].handler(text);
|
||||
for (let j = 0; j < joinerSubRanges.length; j++) {
|
||||
merge(subRanges, joinerSubRanges[j]);
|
||||
}
|
||||
}
|
||||
return subRanges.map<[number, number]>(range => [range[0] + startIndex, range[1] + endIndex]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the charcater at the cell specified.
|
||||
* @param x The column of the char.
|
||||
|
||||
+18
-1
@@ -3,7 +3,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ITerminal } from '../Types';
|
||||
import { ITerminal, CharacterJoinerHandler } from '../Types';
|
||||
import { IEventEmitter, ITheme } from 'xterm';
|
||||
import { IColorSet } from '../shared/Types';
|
||||
|
||||
@@ -35,6 +35,8 @@ export interface IRenderer extends IEventEmitter {
|
||||
onOptionsChanged(): void;
|
||||
clear(): void;
|
||||
refreshRows(start: number, end: number): void;
|
||||
registerCharacterJoiner(handler: CharacterJoinerHandler): number;
|
||||
deregisterCharacterJoiner(joinerId: number): boolean;
|
||||
}
|
||||
|
||||
export interface IColorManager {
|
||||
@@ -96,6 +98,16 @@ export interface IRenderLayer {
|
||||
*/
|
||||
onSelectionChanged(terminal: ITerminal, start: [number, number], end: [number, number]): void;
|
||||
|
||||
/**
|
||||
* Registers a handler to join characters to render as a group
|
||||
*/
|
||||
registerCharacterJoiner?(joiner: ICharacterJoiner): void;
|
||||
|
||||
/**
|
||||
* Deregisters the specified character joiner handler
|
||||
*/
|
||||
deregisterCharacterJoiner?(joinerId: number): void;
|
||||
|
||||
/**
|
||||
* Resize the render layer.
|
||||
*/
|
||||
@@ -106,3 +118,8 @@ export interface IRenderLayer {
|
||||
*/
|
||||
reset(terminal: ITerminal): void;
|
||||
}
|
||||
|
||||
export interface ICharacterJoiner {
|
||||
id: number;
|
||||
handler: CharacterJoinerHandler;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { assert } from 'chai';
|
||||
|
||||
import { merge } from './MergeRanges';
|
||||
|
||||
describe('merge', () => {
|
||||
it('inserts a new range before the existing ones', () => {
|
||||
const result = merge([[1, 2], [2, 3]], [0, 1]);
|
||||
assert.deepEqual(result, [[0, 1], [1, 2], [2, 3]]);
|
||||
});
|
||||
|
||||
it('inserts in between two ranges', () => {
|
||||
const result = merge([[0, 2], [4, 6]], [2, 4]);
|
||||
assert.deepEqual(result, [[0, 2], [2, 4], [4, 6]]);
|
||||
});
|
||||
|
||||
it('inserts after the last range', () => {
|
||||
const result = merge([[0, 2], [4, 6]], [6, 8]);
|
||||
assert.deepEqual(result, [[0, 2], [4, 6], [6, 8]]);
|
||||
});
|
||||
|
||||
it('extends the beginning of a range', () => {
|
||||
const result = merge([[0, 2], [4, 6]], [3, 5]);
|
||||
assert.deepEqual(result, [[0, 2], [3, 6]]);
|
||||
});
|
||||
|
||||
it('extends the end of a range', () => {
|
||||
const result = merge([[0, 2], [4, 6]], [1, 4]);
|
||||
assert.deepEqual(result, [[0, 4], [4, 6]]);
|
||||
});
|
||||
|
||||
it('extends the last range', () => {
|
||||
const result = merge([[0, 2], [4, 6]], [5, 7]);
|
||||
assert.deepEqual(result, [[0, 2], [4, 7]]);
|
||||
});
|
||||
|
||||
it('connects two ranges', () => {
|
||||
const result = merge([[0, 2], [4, 6]], [1, 5]);
|
||||
assert.deepEqual(result, [[0, 6]]);
|
||||
});
|
||||
|
||||
it('connects more than two ranges', () => {
|
||||
const result = merge([[0, 2], [4, 6], [8, 10], [12, 14]], [1, 10]);
|
||||
assert.deepEqual(result, [[0, 10], [12, 14]]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Merges the range defined by the provided start and end into the list of
|
||||
* existing ranges. The merge is done in place on the existing range for
|
||||
* performance and is also returned.
|
||||
* @param ranges Existing range list
|
||||
* @param newRange Tuple of two numbers representing the new range to merge in.
|
||||
* @returns The ranges input with the new range merged in place
|
||||
*/
|
||||
export function merge(ranges: [number, number][], newRange: [number, number]): [number, number][] {
|
||||
let inRange = false;
|
||||
for (let i = 0; i < ranges.length; i++) {
|
||||
const range = ranges[i];
|
||||
if (!inRange) {
|
||||
if (newRange[1] <= range[0]) {
|
||||
// Case 1: New range is before the search range
|
||||
ranges.splice(i, 0, newRange);
|
||||
return ranges;
|
||||
}
|
||||
|
||||
if (newRange[1] <= range[1]) {
|
||||
// Case 2: New range is either wholly contained within the
|
||||
// search range or overlaps with the front of it
|
||||
range[0] = Math.min(newRange[0], range[0]);
|
||||
return ranges;
|
||||
}
|
||||
|
||||
if (newRange[0] < range[1]) {
|
||||
// Case 3: New range either wholly contains the search range
|
||||
// or overlaps with the end of it
|
||||
range[0] = Math.min(newRange[0], range[0]);
|
||||
inRange = true;
|
||||
}
|
||||
|
||||
// Case 4: New range starts after the search range
|
||||
continue;
|
||||
} else {
|
||||
if (newRange[1] <= range[0]) {
|
||||
// Case 5: New range extends from previous range but doesn't
|
||||
// reach the current one
|
||||
ranges[i - 1][1] = newRange[1];
|
||||
return ranges;
|
||||
}
|
||||
|
||||
if (newRange[1] <= range[1]) {
|
||||
// Case 6: New range extends from prvious range into the
|
||||
// current range
|
||||
ranges[i - 1][1] = Math.max(newRange[1], range[1]);
|
||||
ranges.splice(i, 1);
|
||||
inRange = false;
|
||||
return ranges;
|
||||
}
|
||||
|
||||
// Case 7: New range extends from previous range past the
|
||||
// end of the current range
|
||||
ranges.splice(i, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
if (inRange) {
|
||||
// Case 8: New range extends past the last existing range
|
||||
ranges[ranges.length - 1][1] = newRange[1];
|
||||
} else {
|
||||
// Case 9: New range starts after the last existing range
|
||||
ranges.push(newRange);
|
||||
}
|
||||
|
||||
return ranges;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { IColorSet, IRenderer, IRenderDimensions, IColorManager } from '../renderer/Types';
|
||||
import { LineData, IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, ICircularList, ILinkifier, IMouseHelper, ILinkMatcherOptions, XtermListener } from '../Types';
|
||||
import { LineData, IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, ICircularList, ILinkifier, IMouseHelper, ILinkMatcherOptions, XtermListener, CharacterJoinerHandler } from '../Types';
|
||||
import { Buffer } from '../Buffer';
|
||||
import * as Browser from '../shared/utils/Browser';
|
||||
import { ITheme, IDisposable, IMarker } from 'xterm';
|
||||
@@ -157,6 +157,8 @@ export class MockTerminal implements ITerminal {
|
||||
}
|
||||
return line;
|
||||
}
|
||||
registerCharacterJoiner(handler: CharacterJoinerHandler): number { return 0; }
|
||||
deregisterCharacterJoiner(joinerId: number): void { }
|
||||
}
|
||||
|
||||
export class MockCharMeasure implements ICharMeasure {
|
||||
@@ -339,6 +341,8 @@ export class MockRenderer implements IRenderer {
|
||||
onWindowResize(devicePixelRatio: number): void {}
|
||||
clear(): void {}
|
||||
refreshRows(start: number, end: number): void {}
|
||||
registerCharacterJoiner(handler: CharacterJoinerHandler): number { return 0; }
|
||||
deregisterCharacterJoiner(): boolean { return true; }
|
||||
}
|
||||
|
||||
export class MockViewport implements IViewport {
|
||||
|
||||
Vendored
+20
@@ -439,6 +439,26 @@ declare module 'xterm' {
|
||||
*/
|
||||
deregisterLinkMatcher(matcherId: number): void;
|
||||
|
||||
/**
|
||||
* (EXPERIMENTAL) Registers a character joiner, allowing custom sequences of
|
||||
* characters to be rendered as a single unit. This is useful in particular
|
||||
* for rendering ligatures. NOTE: character joiners are only used by the
|
||||
* canvas renderer.
|
||||
* @param handler The function that determines character joins. It is called
|
||||
* with a string of text that is eligible for joining and returns an array
|
||||
* where each entry is an array containing the start (inclusive) and end
|
||||
* (exclusive) indexes of ranges that should be rendered as a single unit.
|
||||
* @return The ID of the new joiner, this can be used to deregister
|
||||
*/
|
||||
registerCharacterJoiner(handler: (text: string) => [number, number][]): number;
|
||||
|
||||
/**
|
||||
* (EXPERIMENTAL) Deregisters the character joiner if one was registered.
|
||||
* NOTE: character joiners are only used by the canvas renderer.
|
||||
* @param joinerId The character joiner's ID (returned after register)
|
||||
*/
|
||||
deregisterCharacterJoiner(joinerId: number): void;
|
||||
|
||||
/**
|
||||
* (EXPERIMENTAL) Adds a marker to the normal buffer and returns it. If the
|
||||
* alt buffer is active, undefined is returned.
|
||||
|
||||
Reference in New Issue
Block a user