Merge branch 'v3' into zmodem

This commit is contained in:
Felipe Gasper
2017-10-17 08:21:33 +02:00
27 changed files with 459 additions and 270 deletions
+2 -1
View File
@@ -101,6 +101,7 @@ Since xterm.js is typically implemented as a developer tool, only modern browser
- Edge latest
- Firefox latest
- Safari latest
- IE11
Xterm.js works seamlessly in Electron apps and may even work on earlier versions of the browsers but these are the browsers we strive to keep working.
@@ -129,7 +130,7 @@ Xterm.js is used in several world-class applications to provide great terminal e
- [**JupyterLab**](https://github.com/jupyterlab/jupyterlab): An extensible
computational environment for Jupyter, supporting interactive data science and scientific computing across all programming languages.
- [**Theia**](https://github.com/theia-ide/theia): Theia is a cloud & desktop IDE framework implemented in TypeScript.
- [**DevOps Helper**](https://github.com/ricktbaker/devops_helper) DevOps Helper tool to make life easier working with AWS instances across multiple organizations.
- [**Opshell**](https://github.com/ricktbaker/opshell) Ops Helper tool to make life easier working with AWS instances across multiple organizations.
Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it in our list.
+1 -1
View File
@@ -95,7 +95,7 @@ function createTerminal() {
term.fit();
// fit is called within a setTimeout, cols and rows need this.
setTimeout(() => {
setTimeout(function () {
colsElement.value = term.cols;
rowsElement.value = term.rows;
+2
View File
@@ -140,6 +140,7 @@ namespace methods_core {
const r18: (data: string) => void = t.getOption('handler');
const r19: string = t.getOption('bellSound');
const r20: string = t.getOption('bellStyle');
const r22: number = t.getOption('letterSpacing');
}
{
const t: Terminal = new Terminal();
@@ -157,6 +158,7 @@ namespace methods_core {
t.setOption('useFlowControl', true);
t.setOption('visualBell', true);
t.setOption('colors', ['a', 'b']);
t.setOption('letterSpacing', 1);
t.setOption('cols', 1);
t.setOption('rows', 1);
t.setOption('tabStopWidth', 1);
+2 -2
View File
@@ -25,8 +25,8 @@ let outDir = tsProject.config.compilerOptions.outDir;
// Under some environments like TravisCI, this comes out at absolute which can
// break the build. This ensures that the outDir is absolute.
if (outDir.indexOf(__dirname) !== 0) {
outDir = `${__dirname}/${outDir}`;
if (path.normalize(outDir).indexOf(__dirname) !== 0) {
outDir = `${__dirname}/${path.normalize(outDir)}`;
}
/**
+25 -8
View File
@@ -208,12 +208,19 @@ export class Buffer implements IBuffer {
public translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol: number = 0, endCol: number = null): string {
// Get full line
let lineString = '';
let widthAdjustedStartCol = startCol;
let widthAdjustedEndCol = endCol;
const line = this.lines.get(lineIndex);
if (!line) {
return '';
}
// Initialize column and index values. Column values represent the actual
// cell column, indexes represent the index in the string. Indexes are
// needed here because some chars are 0 characters long (eg. after wide
// chars) and some chars are longer than 1 characters long (eg. emojis).
let startIndex = startCol;
endCol = endCol || line.length;
let endIndex = endCol;
for (let i = 0; i < line.length; i++) {
const char = line[i];
lineString += char[CHAR_DATA_CHAR_INDEX];
@@ -221,29 +228,39 @@ export class Buffer implements IBuffer {
// column indexes
if (char[CHAR_DATA_WIDTH_INDEX] === 0) {
if (startCol >= i) {
widthAdjustedStartCol--;
startIndex--;
}
if (endCol >= i) {
widthAdjustedEndCol--;
endIndex--;
}
} else {
// Adjust the columns to take glyphs that are represented by multiple
// code points into account.
if (char[CHAR_DATA_CHAR_INDEX].length > 1) {
if (startCol > i) {
startIndex += char[CHAR_DATA_CHAR_INDEX].length - 1;
}
if (endCol > i) {
endIndex += char[CHAR_DATA_CHAR_INDEX].length - 1;
}
}
}
}
// Calculate the final end col by trimming whitespace on the right of the
// line if needed.
let finalEndCol = widthAdjustedEndCol || line.length;
if (trimRight) {
const rightWhitespaceIndex = lineString.search(/\s+$/);
if (rightWhitespaceIndex !== -1) {
finalEndCol = Math.min(finalEndCol, rightWhitespaceIndex);
endIndex = Math.min(endIndex, rightWhitespaceIndex);
}
// Return the empty string if only trimmed whitespace is selected
if (finalEndCol <= widthAdjustedStartCol) {
if (endIndex <= startIndex) {
return '';
}
}
return lineString.substring(widthAdjustedStartCol, finalEndCol);
return lineString.substring(startIndex, endIndex);
}
/**
-139
View File
@@ -67,142 +67,3 @@ describe('InputHandler', () => {
});
});
});
const old_wcwidth = (function(opts: {nul: number, control: number}): (ucs: number) => number {
// extracted from https://www.cl.cam.ac.uk/%7Emgk25/ucs/wcwidth.c
// combining characters
const COMBINING = [
[0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489],
[0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2],
[0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603],
[0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670],
[0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED],
[0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A],
[0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902],
[0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D],
[0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981],
[0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD],
[0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C],
[0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D],
[0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC],
[0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD],
[0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C],
[0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D],
[0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0],
[0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48],
[0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC],
[0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD],
[0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D],
[0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6],
[0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E],
[0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC],
[0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35],
[0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E],
[0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97],
[0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030],
[0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039],
[0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F],
[0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753],
[0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD],
[0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD],
[0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922],
[0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B],
[0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34],
[0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42],
[0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF],
[0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063],
[0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F],
[0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B],
[0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F],
[0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB],
[0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F],
[0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169],
[0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD],
[0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F],
[0xE0100, 0xE01EF]
];
// binary search
function bisearch(ucs: number): boolean {
let min = 0;
let max = COMBINING.length - 1;
let mid;
if (ucs < COMBINING[0][0] || ucs > COMBINING[max][1])
return false;
while (max >= min) {
mid = Math.floor((min + max) / 2);
if (ucs > COMBINING[mid][1])
min = mid + 1;
else if (ucs < COMBINING[mid][0])
max = mid - 1;
else
return true;
}
return false;
}
function wcwidth(ucs: number): number {
// test for 8-bit control characters
if (ucs === 0) {
return opts.nul;
}
if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) {
return opts.control;
}
// binary search in table of non-spacing characters
if (bisearch(ucs)) {
return 0;
}
// if we arrive here, ucs is not a combining or C0/C1 control character
if (isWide(ucs)) {
return 2;
}
return 1;
}
function isWide(ucs: number): boolean {
return (
ucs >= 0x1100 && (
ucs <= 0x115f || // Hangul Jamo init. consonants
ucs === 0x2329 ||
ucs === 0x232a ||
(ucs >= 0x2e80 && ucs <= 0xa4cf && ucs !== 0x303f) || // CJK..Yi
(ucs >= 0xac00 && ucs <= 0xd7a3) || // Hangul Syllables
(ucs >= 0xf900 && ucs <= 0xfaff) || // CJK Compat Ideographs
(ucs >= 0xfe10 && ucs <= 0xfe19) || // Vertical forms
(ucs >= 0xfe30 && ucs <= 0xfe6f) || // CJK Compat Forms
(ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms
(ucs >= 0xffe0 && ucs <= 0xffe6) ||
(ucs >= 0x20000 && ucs <= 0x2fffd) ||
(ucs >= 0x30000 && ucs <= 0x3fffd)));
}
return wcwidth;
})({nul: 0, control: 0}); // configurable options
describe('wcwidth', () => {
it('same as old implementation for BMP and individual higher', (done) => {
for (let i = 0; i < 65536; ++i)
assert.equal(wcwidth(i), old_wcwidth(i));
// test some individual higher to fullfill branching
assert.equal(wcwidth(0x10A01), old_wcwidth(0x10A01));
assert.equal(wcwidth(0x30000), old_wcwidth(0x30000));
assert.equal(wcwidth(0x3fffe), old_wcwidth(0x3fffe));
done();
}).timeout(3000);
/*
it('new is at least 5 times faster', () => {
let start_new = new Date().getTime();
let x = 0;
for (let runs = 0; runs < 1; ++runs)
for (let i = 0; i < 65536; ++i)
x = wcwidth(i);
let end_new = new Date().getTime();
let start_old = new Date().getTime();
let y = 0;
for (let runs = 0; runs < 1; ++runs)
for (let i = 0; i < 65536; ++i)
y = old_wcwidth(i);
let end_old = new Date().getTime();
// console.log((end_new - start_new));
// console.log((end_old - start_old));
assert.equal(((end_new - start_new) * 5 < (end_old - start_old)), true);
});
*/
});
+17 -11
View File
@@ -9,6 +9,7 @@ import { C0 } from './EscapeSequences';
import { DEFAULT_CHARSET } from './Charsets';
import { CharData } from './Types';
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX } from './Buffer';
import { FLAGS } from './renderer/Types';
/**
* The terminal's standard implementation of IInputHandler, this handles all
@@ -1107,6 +1108,7 @@ export class InputHandler implements IInputHandler {
* CSI Pm m Character Attributes (SGR).
* Ps = 0 -> Normal (default).
* Ps = 1 -> Bold.
* Ps = 2 -> Faint, decreased intensity (ISO 6429).
* Ps = 4 -> Underlined.
* Ps = 5 -> Blink (appears as Bold).
* Ps = 7 -> Inverse.
@@ -1206,35 +1208,39 @@ export class InputHandler implements IInputHandler {
// bg = 0x1ff;
} else if (p === 1) {
// bold text
flags |= 1;
flags |= FLAGS.BOLD;
} else if (p === 4) {
// underlined text
flags |= 2;
flags |= FLAGS.UNDERLINE;
} else if (p === 5) {
// blink
flags |= 4;
flags |= FLAGS.BLINK;
} else if (p === 7) {
// inverse and positive
// test with: echo -e '\e[31m\e[42mhello\e[7mworld\e[27mhi\e[m'
flags |= 8;
flags |= FLAGS.INVERSE;
} else if (p === 8) {
// invisible
flags |= 16;
flags |= FLAGS.INVISIBLE;
} else if (p === 2) {
// dimmed text
flags |= FLAGS.DIM;
} else if (p === 22) {
// not bold
flags &= ~1;
// not bold nor faint
flags &= ~FLAGS.BOLD;
flags &= ~FLAGS.DIM;
} else if (p === 24) {
// not underlined
flags &= ~2;
flags &= ~FLAGS.UNDERLINE;
} else if (p === 25) {
// not blink
flags &= ~4;
flags &= ~FLAGS.BLINK;
} else if (p === 27) {
// not inverse
flags &= ~8;
flags &= ~FLAGS.INVERSE;
} else if (p === 28) {
// not invisible
flags &= ~16;
flags &= ~FLAGS.INVISIBLE;
} else if (p === 39) {
// reset fg
fg = (this._terminal.defAttr >> 9) & 0x1ff;
+1
View File
@@ -139,6 +139,7 @@ export interface ITerminalOptions {
fontFamily?: string;
geometry?: [number, number];
handler?: (data: string) => void;
letterSpacing?: number;
lineHeight?: number;
rows?: number;
screenKeys?: boolean;
+1 -1
View File
@@ -87,7 +87,7 @@ export class Linkifier extends EventEmitter implements ILinkifier {
this._rowsToLinkify.end = end;
} else {
this._rowsToLinkify.start = this._rowsToLinkify.start < start ? this._rowsToLinkify.start : start;
this._rowsToLinkify.end = this._rowsToLinkify.end < end ? this._rowsToLinkify.end : end;
this._rowsToLinkify.end = this._rowsToLinkify.end > end ? this._rowsToLinkify.end : end;
}
// Clear out any existing links on this row range
+112 -1
View File
@@ -12,7 +12,7 @@ import { SelectionManager } from './SelectionManager';
import { SelectionModel } from './SelectionModel';
import { BufferSet } from './BufferSet';
import { MockTerminal } from './utils/TestUtils.test';
import { LineData } from './Types';
import { LineData, CharData } from './Types';
class TestSelectionManager extends SelectionManager {
constructor(
@@ -66,6 +66,10 @@ describe('SelectionManager', () => {
return result;
}
function stringArrayToRow(chars: string[]): LineData {
return chars.map(c => <CharData>[0, c, 1, c.charCodeAt(0)]);
}
describe('_selectWordAt', () => {
it('should expand selection for normal width chars', () => {
buffer.lines.set(0, stringToRow('foo bar'));
@@ -185,6 +189,113 @@ describe('SelectionManager', () => {
selectionManager.selectWordAt([15, 0]);
assert.equal(selectionManager.selectionText, 'ij"');
});
describe('emoji', () => {
it('should treat a single emoji as a word when wrapped in spaces', () => {
buffer.lines.set(0, stringToRow(' ⚽ a')); // The a is here to prevent the space being trimmed in selectionText
selectionManager.selectWordAt([0, 0]);
assert.equal(selectionManager.selectionText, ' ');
selectionManager.selectWordAt([1, 0]);
assert.equal(selectionManager.selectionText, '⚽');
selectionManager.selectWordAt([2, 0]);
assert.equal(selectionManager.selectionText, ' ');
});
it('should treat multiple emojis as a word when wrapped in spaces', () => {
buffer.lines.set(0, stringToRow(' ⚽⚽ a')); // The a is here to prevent the space being trimmed in selectionText
selectionManager.selectWordAt([0, 0]);
assert.equal(selectionManager.selectionText, ' ');
selectionManager.selectWordAt([1, 0]);
assert.equal(selectionManager.selectionText, '⚽⚽');
selectionManager.selectWordAt([2, 0]);
assert.equal(selectionManager.selectionText, '⚽⚽');
selectionManager.selectWordAt([3, 0]);
assert.equal(selectionManager.selectionText, ' ');
});
it('should treat emojis using the zero-width-joiner as a single word', () => {
// Note that the first 3 emojis include the invisible ZWJ char
buffer.lines.set(0, stringArrayToRow([
' ', '👨‍', '👩‍', '👧‍', '👦', ' ', 'a'
])); // The a is here to prevent the space being trimmed in selectionText
selectionManager.selectWordAt([0, 0]);
assert.equal(selectionManager.selectionText, ' ');
// ZWJ emojis do not combine in the terminal so the family emoji used here consumed 4 cells
// The selection text should retain ZWJ chars despite not combining on the terminal
selectionManager.selectWordAt([1, 0]);
assert.equal(selectionManager.selectionText, '👨‍👩‍👧‍👦');
selectionManager.selectWordAt([2, 0]);
assert.equal(selectionManager.selectionText, '👨‍👩‍👧‍👦');
selectionManager.selectWordAt([3, 0]);
assert.equal(selectionManager.selectionText, '👨‍👩‍👧‍👦');
selectionManager.selectWordAt([4, 0]);
assert.equal(selectionManager.selectionText, '👨‍👩‍👧‍👦');
selectionManager.selectWordAt([5, 0]);
assert.equal(selectionManager.selectionText, ' ');
});
it('should treat emojis and characters joined together as a word', () => {
buffer.lines.set(0, stringToRow(' ⚽ab cd⚽ ef⚽gh')); // The a is here to prevent the space being trimmed in selectionText
selectionManager.selectWordAt([0, 0]);
assert.equal(selectionManager.selectionText, ' ');
selectionManager.selectWordAt([1, 0]);
assert.equal(selectionManager.selectionText, '⚽ab');
selectionManager.selectWordAt([2, 0]);
assert.equal(selectionManager.selectionText, '⚽ab');
selectionManager.selectWordAt([3, 0]);
assert.equal(selectionManager.selectionText, '⚽ab');
selectionManager.selectWordAt([4, 0]);
assert.equal(selectionManager.selectionText, ' ');
selectionManager.selectWordAt([5, 0]);
assert.equal(selectionManager.selectionText, 'cd⚽');
selectionManager.selectWordAt([6, 0]);
assert.equal(selectionManager.selectionText, 'cd⚽');
selectionManager.selectWordAt([7, 0]);
assert.equal(selectionManager.selectionText, 'cd⚽');
selectionManager.selectWordAt([8, 0]);
assert.equal(selectionManager.selectionText, ' ');
selectionManager.selectWordAt([9, 0]);
assert.equal(selectionManager.selectionText, 'ef⚽gh');
selectionManager.selectWordAt([10, 0]);
assert.equal(selectionManager.selectionText, 'ef⚽gh');
selectionManager.selectWordAt([11, 0]);
assert.equal(selectionManager.selectionText, 'ef⚽gh');
selectionManager.selectWordAt([12, 0]);
assert.equal(selectionManager.selectionText, 'ef⚽gh');
selectionManager.selectWordAt([13, 0]);
assert.equal(selectionManager.selectionText, 'ef⚽gh');
});
it('should treat complex emojis and characters joined together as a word', () => {
// This emoji is the flag for England and is made up of: 1F3F4 E0067 E0062 E0065 E006E E0067 E007F
buffer.lines.set(0, stringArrayToRow([
' ', '🏴󠁧󠁢󠁥󠁮󠁧󠁿', 'a', 'b', ' ', 'c', 'd', '🏴󠁧󠁢󠁥󠁮󠁧󠁿', ' ', 'e', 'f', '🏴󠁧󠁢󠁥󠁮󠁧󠁿', 'g', 'h', ' ', 'a'
])); // The a is here to prevent the space being trimmed in selectionText
selectionManager.selectWordAt([0, 0]);
assert.equal(selectionManager.selectionText, ' ');
selectionManager.selectWordAt([1, 0]);
assert.equal(selectionManager.selectionText, '🏴󠁧󠁢󠁥󠁮󠁧󠁿ab');
selectionManager.selectWordAt([2, 0]);
assert.equal(selectionManager.selectionText, '🏴󠁧󠁢󠁥󠁮󠁧󠁿ab');
selectionManager.selectWordAt([3, 0]);
assert.equal(selectionManager.selectionText, '🏴󠁧󠁢󠁥󠁮󠁧󠁿ab');
selectionManager.selectWordAt([4, 0]);
assert.equal(selectionManager.selectionText, ' ');
selectionManager.selectWordAt([5, 0]);
assert.equal(selectionManager.selectionText, 'cd🏴󠁧󠁢󠁥󠁮󠁧󠁿');
selectionManager.selectWordAt([6, 0]);
assert.equal(selectionManager.selectionText, 'cd🏴󠁧󠁢󠁥󠁮󠁧󠁿');
selectionManager.selectWordAt([7, 0]);
assert.equal(selectionManager.selectionText, 'cd🏴󠁧󠁢󠁥󠁮󠁧󠁿');
selectionManager.selectWordAt([8, 0]);
assert.equal(selectionManager.selectionText, ' ');
selectionManager.selectWordAt([9, 0]);
assert.equal(selectionManager.selectionText, 'ef🏴󠁧󠁢󠁥󠁮󠁧󠁿gh');
selectionManager.selectWordAt([10, 0]);
assert.equal(selectionManager.selectionText, 'ef🏴󠁧󠁢󠁥󠁮󠁧󠁿gh');
selectionManager.selectWordAt([11, 0]);
assert.equal(selectionManager.selectionText, 'ef🏴󠁧󠁢󠁥󠁮󠁧󠁿gh');
selectionManager.selectWordAt([12, 0]);
assert.equal(selectionManager.selectionText, 'ef🏴󠁧󠁢󠁥󠁮󠁧󠁿gh');
selectionManager.selectWordAt([13, 0]);
assert.equal(selectionManager.selectionText, 'ef🏴󠁧󠁢󠁥󠁮󠁧󠁿gh');
});
});
});
describe('_selectLineAt', () => {
+66 -12
View File
@@ -10,8 +10,8 @@ import { CircularList } from './utils/CircularList';
import { EventEmitter } from './EventEmitter';
import { ITerminal, ICircularList, ISelectionManager, IBuffer } from './Interfaces';
import { SelectionModel } from './SelectionModel';
import { LineData } from './Types';
import { CHAR_DATA_WIDTH_INDEX } from './Buffer';
import { LineData, CharData } from './Types';
import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from './Buffer';
/**
* The number of pixels the mouse needs to be above or below the viewport in
@@ -281,6 +281,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
// Convert to 0-based
coords[0]--;
coords[1]--;
// Convert viewport coords to buffer coords
coords[1] += this._terminal.buffer.ydisp;
return coords;
@@ -545,7 +546,14 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
for (let i = 0; coords[0] >= i; i++) {
const char = bufferLine[i];
if (char[CHAR_DATA_WIDTH_INDEX] === 0) {
// Wide characters aren't included in the line string so decrement the
// index so the index is back on the wide character.
charIndex--;
} else if (char[CHAR_DATA_CHAR_INDEX].length > 1 && coords[0] !== i) {
// Emojis take up multiple characters, so adjust accordingly. For these
// we don't want ot include the character at the column as we're
// returning the start index in the string, not the end index.
charIndex += char[CHAR_DATA_CHAR_INDEX].length - 1;
}
}
return charIndex;
@@ -572,13 +580,15 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
const line = this._buffer.translateBufferLineToString(coords[1], false);
// Get actual index, taking into consideration wide characters
let endIndex = this._convertViewportColToCharacterIndex(bufferLine, coords);
let startIndex = endIndex;
let startIndex = this._convertViewportColToCharacterIndex(bufferLine, coords);
let endIndex = startIndex;
// Record offset to be used later
const charOffset = coords[0] - startIndex;
let leftWideCharCount = 0;
let rightWideCharCount = 0;
let leftLongCharOffset = 0;
let rightLongCharOffset = 0;
if (line.charAt(startIndex) === ' ') {
// Expand until non-whitespace is hit
@@ -595,6 +605,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
// character is hit, it is recorded and the column index is adjusted.
let startCol = coords[0];
let endCol = coords[0];
// Consider the initial position, skip it and increment the wide char
// variable
if (bufferLine[startCol][CHAR_DATA_WIDTH_INDEX] === 0) {
@@ -605,29 +616,67 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
rightWideCharCount++;
endCol++;
}
// Adjust the end index for characters whose length are > 1 (emojis)
if (bufferLine[endCol][CHAR_DATA_CHAR_INDEX].length > 1) {
rightLongCharOffset += bufferLine[endCol][CHAR_DATA_CHAR_INDEX].length - 1;
endIndex += bufferLine[endCol][CHAR_DATA_CHAR_INDEX].length - 1;
}
// Expand the string in both directions until a space is hit
while (startIndex > 0 && !this._isCharWordSeparator(line.charAt(startIndex - 1))) {
if (bufferLine[startCol - 1][CHAR_DATA_WIDTH_INDEX] === 0) {
while (startCol > 0 && startIndex > 0 && !this._isCharWordSeparator(bufferLine[startCol - 1])) {
const char = bufferLine[startCol - 1];
if (char[CHAR_DATA_WIDTH_INDEX] === 0) {
// If the next character is a wide char, record it and skip the column
leftWideCharCount++;
startCol--;
} else if (char[CHAR_DATA_CHAR_INDEX].length > 1) {
// If the next character's string is longer than 1 char (eg. emoji),
// adjust the index
leftLongCharOffset += char[CHAR_DATA_CHAR_INDEX].length - 1;
startIndex -= char[CHAR_DATA_CHAR_INDEX].length - 1;
}
startIndex--;
startCol--;
}
while (endIndex + 1 < line.length && !this._isCharWordSeparator(line.charAt(endIndex + 1))) {
if (bufferLine[endCol + 1][CHAR_DATA_WIDTH_INDEX] === 2) {
while (endCol < bufferLine.length && endIndex + 1 < line.length && !this._isCharWordSeparator(bufferLine[endCol + 1])) {
const char = bufferLine[endCol + 1];
if (char[CHAR_DATA_WIDTH_INDEX] === 2) {
// If the next character is a wide char, record it and skip the column
rightWideCharCount++;
endCol++;
} else if (char[CHAR_DATA_CHAR_INDEX].length > 1) {
// If the next character's string is longer than 1 char (eg. emoji),
// adjust the index
rightLongCharOffset += char[CHAR_DATA_CHAR_INDEX].length - 1;
endIndex += char[CHAR_DATA_CHAR_INDEX].length - 1;
}
endIndex++;
endCol++;
}
}
const start = startIndex + charOffset - leftWideCharCount;
const length = Math.min(endIndex - startIndex + leftWideCharCount + rightWideCharCount + 1/*include endIndex char*/, this._terminal.cols);
// Incremenet the end index so it is at the start of the next character
endIndex++;
// Calculate the start _column_, converting the the string indexes back to
// column coordinates.
const start =
startIndex // The index of the selection's start char in the line string
+ charOffset // The difference between the initial char's column and index
- leftWideCharCount // The number of wide chars left of the initial char
+ leftLongCharOffset; // The number of additional chars left of the initial char added by columns with strings longer than 1 (emojis)
// Calculate the length in _columns_, converting the the string indexes back
// to column coordinates.
const length = Math.min(this._terminal.cols, // Disallow lengths larger than the terminal cols
endIndex // The index of the selection's end char in the line string
- startIndex // The index of the selection's start char in the line string
+ leftWideCharCount // The number of wide chars left of the initial char
+ rightWideCharCount // The number of wide chars right of the initial char (inclusive)
- leftLongCharOffset // The number of additional chars left of the initial char added by columns with strings longer than 1 (emojis)
- rightLongCharOffset); // The number of additional chars right of the initial char (inclusive) added by columns with strings longer than 1 (emojis)
return { start, length };
}
@@ -659,8 +708,13 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
* word logic.
* @param char The character to check.
*/
private _isCharWordSeparator(char: string): boolean {
return WORD_SEPARATORS.indexOf(char) >= 0;
private _isCharWordSeparator(charData: CharData): boolean {
// Zero width characters are never separators as they are always to the
// right of wide characters
if (charData[CHAR_DATA_WIDTH_INDEX] === 0) {
return false;
}
return WORD_SEPARATORS.indexOf(charData[CHAR_DATA_CHAR_INDEX]) >= 0;
}
/**
+3
View File
@@ -97,6 +97,9 @@ export class SelectionModel {
public areSelectionValuesReversed(): boolean {
const start = this.selectionStart;
const end = this.selectionEnd;
if (!start || !end) {
return false;
}
return start[1] > end[1] || (start[1] === end[1] && start[0] > end[0]);
}
+2
View File
@@ -80,6 +80,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = {
fontFamily: 'courier-new, courier, monospace',
fontSize: 15,
lineHeight: 1.0,
letterSpacing: 0,
scrollback: 1000,
screenKeys: false,
debug: false,
@@ -410,6 +411,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT
this.renderer.clear();
this.charMeasure.measure(this.options);
break;
case 'letterSpacing':
case 'lineHeight':
// When the font changes the size of the cells may change which requires a renderer clear
this.renderer.clear();
+2 -2
View File
@@ -47,7 +47,7 @@ export class Viewport implements IViewport {
*/
private refresh(): void {
if (this.charMeasure.height > 0) {
this.currentRowHeight = this.terminal.renderer.dimensions.scaledLineHeight / window.devicePixelRatio;
this.currentRowHeight = this.terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio;
if (this.lastRecordedViewportHeight !== this.terminal.renderer.dimensions.canvasHeight) {
this.lastRecordedViewportHeight = this.terminal.renderer.dimensions.canvasHeight;
@@ -75,7 +75,7 @@ export class Viewport implements IViewport {
this.refresh();
} else {
// If size has changed, refresh viewport
if (this.terminal.renderer.dimensions.scaledLineHeight / window.devicePixelRatio !== this.currentRowHeight) {
if (this.terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio !== this.currentRowHeight) {
this.refresh();
}
}
+1 -1
View File
@@ -56,7 +56,7 @@
exports.fit = function (term) {
// Wrap fit in a setTimeout as charMeasure needs time to get initialized
// after calling Terminal.open
setTimeout(() => {
setTimeout(function () {
var geometry = exports.proposeGeometry(term);
if (geometry) {
+21 -1
View File
@@ -21,7 +21,6 @@ export class MouseZoneManager implements IMouseZoneManager {
private _areZonesActive: boolean = false;
private _mouseMoveListener: (e: MouseEvent) => any;
private _mouseDownListener: (e: MouseEvent) => any;
private _clickListener: (e: MouseEvent) => any;
private _tooltipTimeout: number = null;
@@ -31,6 +30,8 @@ export class MouseZoneManager implements IMouseZoneManager {
constructor(
private _terminal: ITerminal
) {
this._terminal.element.addEventListener('mousedown', e => this._onMouseDown(e));
// These events are expensive, only listen to it when mouse zones are active
this._mouseMoveListener = e => this._onMouseMove(e);
this._clickListener = e => this._onClick(e);
@@ -140,11 +141,30 @@ export class MouseZoneManager implements IMouseZoneManager {
}
}
private _onMouseDown(e: MouseEvent): void {
// Ignore the event if there are no zones active
if (!this._areZonesActive) {
return;
}
// Find the active zone, prevent event propagation if found to prevent other
// components from handling the mouse event.
const zone = this._findZoneEventAt(e);
if (zone) {
// TODO: When link modifier support is added, the event should only be
// cancelled when the modifier is held (see #1021)
e.preventDefault();
e.stopImmediatePropagation();
}
}
private _onClick(e: MouseEvent): void {
// Find the active zone and click it if found
const zone = this._findZoneEventAt(e);
if (zone) {
zone.clickCallback(e);
e.preventDefault();
e.stopImmediatePropagation();
}
}
+80 -55
View File
@@ -10,14 +10,17 @@ import { CharData } from '../Types';
import { CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from '../Buffer';
export const INVERTED_DEFAULT_COLOR = -1;
const DIM_OPACITY = 0.5;
export abstract class BaseRenderLayer implements IRenderLayer {
private _canvas: HTMLCanvasElement;
protected _ctx: CanvasRenderingContext2D;
private _scaledCharWidth: number;
private _scaledCharHeight: number;
private _scaledLineHeight: number;
private _scaledLineDrawY: number;
private _scaledCellWidth: number;
private _scaledCellHeight: number;
private _scaledCharLeft: number;
private _scaledCharTop: number;
private _charAtlas: HTMLCanvasElement | ImageBitmap;
@@ -31,7 +34,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
this._canvas = document.createElement('canvas');
this._canvas.id = `xterm-${id}-layer`;
this._canvas.style.zIndex = zIndex.toString();
this._ctx = this._canvas.getContext('2d', {_alpha});
this._ctx = this._canvas.getContext('2d', {alpha: _alpha});
this._ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
// Draw the background if this is an opaque layer
if (!_alpha) {
@@ -70,10 +73,12 @@ export abstract class BaseRenderLayer implements IRenderLayer {
}
public resize(terminal: ITerminal, dim: IRenderDimensions, charSizeChanged: boolean): void {
this._scaledCellWidth = dim.scaledCellWidth;
this._scaledCellHeight = dim.scaledCellHeight;
this._scaledCharWidth = dim.scaledCharWidth;
this._scaledCharHeight = dim.scaledCharHeight;
this._scaledLineHeight = dim.scaledLineHeight;
this._scaledLineDrawY = dim.scaledLineDrawY;
this._scaledCharLeft = dim.scaledCharLeft;
this._scaledCharTop = dim.scaledCharTop;
this._canvas.width = dim.scaledCanvasWidth;
this._canvas.height = dim.scaledCanvasHeight;
this._canvas.style.width = `${dim.canvasWidth}px`;
@@ -100,10 +105,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
*/
protected fillCells(x: number, y: number, width: number, height: number): void {
this._ctx.fillRect(
x * this._scaledCharWidth,
y * this._scaledLineHeight,
width * this._scaledCharWidth,
height * this._scaledLineHeight);
x * this._scaledCellWidth,
y * this._scaledCellHeight,
width * this._scaledCellWidth,
height * this._scaledCellHeight);
}
/**
@@ -114,9 +119,9 @@ export abstract class BaseRenderLayer implements IRenderLayer {
*/
protected fillBottomLineAtCells(x: number, y: number, width: number = 1): void {
this._ctx.fillRect(
x * this._scaledCharWidth,
(y + 1) * this._scaledLineHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */,
width * this._scaledCharWidth,
x * this._scaledCellWidth,
(y + 1) * this._scaledCellHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */,
width * this._scaledCellWidth,
window.devicePixelRatio);
}
@@ -128,10 +133,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
*/
protected fillLeftLineAtCell(x: number, y: number): void {
this._ctx.fillRect(
x * this._scaledCharWidth,
y * this._scaledLineHeight,
x * this._scaledCellWidth,
y * this._scaledCellHeight,
window.devicePixelRatio,
this._scaledLineHeight);
this._scaledCellHeight);
}
/**
@@ -143,10 +148,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
protected strokeRectAtCell(x: number, y: number, width: number, height: number): void {
this._ctx.lineWidth = window.devicePixelRatio;
this._ctx.strokeRect(
x * this._scaledCharWidth + window.devicePixelRatio / 2,
y * this._scaledLineHeight + (window.devicePixelRatio / 2),
width * this._scaledCharWidth - window.devicePixelRatio,
(height * this._scaledLineHeight) - window.devicePixelRatio);
x * this._scaledCellWidth + window.devicePixelRatio / 2,
y * this._scaledCellHeight + (window.devicePixelRatio / 2),
width * this._scaledCellWidth - window.devicePixelRatio,
(height * this._scaledCellHeight) - window.devicePixelRatio);
}
/**
@@ -171,17 +176,17 @@ export abstract class BaseRenderLayer implements IRenderLayer {
protected clearCells(x: number, y: number, width: number, height: number): void {
if (this._alpha) {
this._ctx.clearRect(
x * this._scaledCharWidth,
y * this._scaledLineHeight,
width * this._scaledCharWidth,
height * this._scaledLineHeight);
x * this._scaledCellWidth,
y * this._scaledCellHeight,
width * this._scaledCellWidth,
height * this._scaledCellHeight);
} else {
this._ctx.fillStyle = this._colors.background;
this._ctx.fillRect(
x * this._scaledCharWidth,
y * this._scaledLineHeight,
width * this._scaledCharWidth,
height * this._scaledLineHeight);
x * this._scaledCellWidth,
y * this._scaledCellHeight,
width * this._scaledCellWidth,
height * this._scaledCellHeight);
}
}
@@ -198,15 +203,11 @@ export abstract class BaseRenderLayer implements IRenderLayer {
protected fillCharTrueColor(terminal: ITerminal, charData: CharData, x: number, y: number): void {
this._ctx.font = `${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`;
this._ctx.textBaseline = 'top';
// Since uncached characters are not coming off the char atlas with source
// coordinates, it means that text drawn to the canvas (particularly '_')
// can bleed into other cells. This code will clip the following fillText,
// ensuring that its contents don't go beyond the cell bounds.
this._ctx.beginPath();
this._ctx.rect(x * this._scaledCharWidth, y * this._scaledLineHeight + this._scaledLineDrawY, charData[CHAR_DATA_WIDTH_INDEX] * this._scaledCharWidth, this._scaledCharHeight);
this._ctx.clip();
this._ctx.fillText(charData[CHAR_DATA_CHAR_INDEX], x * this._scaledCharWidth, y * this._scaledCharHeight);
this._clipRow(terminal, y);
this._ctx.fillText(
charData[CHAR_DATA_CHAR_INDEX],
x * this._scaledCellWidth + this._scaledCharLeft,
y * this._scaledCellHeight + this._scaledCharTop);
}
/**
@@ -223,12 +224,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
* This is used to validate whether a cached image can be used.
* @param bold Whether the text is bold.
*/
protected drawChar(terminal: ITerminal, char: string, code: number, width: number, x: number, y: number, fg: number, bg: number, bold: boolean): void {
// Clear the cell next to this character if it's wide
if (width === 2) {
this.clearCells(x + 1, y, 1, 1);
}
protected drawChar(terminal: ITerminal, char: string, code: number, width: number, x: number, y: number, fg: number, bg: number, bold: boolean, dim: boolean): void {
let colorIndex = 0;
if (fg < 256) {
colorIndex = fg + 2;
@@ -239,18 +235,31 @@ export abstract class BaseRenderLayer implements IRenderLayer {
}
}
const isAscii = code < 256;
const isBasicColor = (colorIndex > 1 && fg < 16);
// A color is basic if it is one of the standard normal or bold weight
// colors of the characters held in the char atlas. Note that this excludes
// the normal weight light color characters
const isBasicColor = (colorIndex > 1 && fg < 16) && (fg < 8 || bold);
const isDefaultColor = fg >= 256;
const isDefaultBackground = bg >= 256;
if (this._charAtlas && isAscii && (isBasicColor || isDefaultColor) && isDefaultBackground) {
// ImageBitmap's draw about twice as fast as from a canvas
const charAtlasCellWidth = this._scaledCharWidth + CHAR_ATLAS_CELL_SPACING;
const charAtlasCellHeight = this._scaledCharHeight + CHAR_ATLAS_CELL_SPACING;
// Apply alpha to dim the character
if (dim) {
this._ctx.globalAlpha = DIM_OPACITY;
}
this._ctx.drawImage(this._charAtlas,
code * charAtlasCellWidth, colorIndex * charAtlasCellHeight, this._scaledCharWidth, this._scaledCharHeight,
x * this._scaledCharWidth, y * this._scaledLineHeight + this._scaledLineDrawY, this._scaledCharWidth, this._scaledCharHeight);
code * charAtlasCellWidth,
colorIndex * charAtlasCellHeight,
charAtlasCellWidth,
this._scaledCharHeight,
x * this._scaledCellWidth + this._scaledCharLeft,
y * this._scaledCellHeight + this._scaledCharTop,
charAtlasCellWidth,
this._scaledCharHeight);
} else {
this._drawUncachedChar(terminal, char, width, fg, x, y, bold);
this._drawUncachedChar(terminal, char, width, fg, x, y, bold, dim);
}
// This draws the atlas (for debugging purposes)
// this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
@@ -268,7 +277,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
* @param x The column to draw at.
* @param y The row to draw at.
*/
private _drawUncachedChar(terminal: ITerminal, char: string, width: number, fg: number, x: number, y: number, bold: boolean): void {
private _drawUncachedChar(terminal: ITerminal, char: string, width: number, fg: number, x: number, y: number, bold: boolean, dim: boolean): void {
this._ctx.save();
this._ctx.font = `${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`;
if (bold) {
@@ -285,17 +294,33 @@ export abstract class BaseRenderLayer implements IRenderLayer {
this._ctx.fillStyle = this._colors.foreground;
}
// Since uncached characters are not coming off the char atlas with source
// coordinates, it means that text drawn to the canvas (particularly '_')
// can bleed into other cells. This code will clip the following fillText,
// ensuring that its contents don't go beyond the cell bounds.
this._ctx.beginPath();
this._ctx.rect(x * this._scaledCharWidth, y * this._scaledLineHeight + this._scaledLineDrawY, width * this._scaledCharWidth, this._scaledCharHeight);
this._ctx.clip();
this._clipRow(terminal, y);
// Apply alpha to dim the character
if (dim) {
this._ctx.globalAlpha = DIM_OPACITY;
}
// Draw the character
this._ctx.fillText(char, x * this._scaledCharWidth, y * this._scaledLineHeight + this._scaledLineDrawY);
this._ctx.fillText(
char,
x * this._scaledCellWidth + this._scaledCharLeft,
y * this._scaledCellHeight + this._scaledCharTop);
this._ctx.restore();
}
/**
* Clips a row to ensure no pixels will be drawn outside the cells in the row.
* @param terminal The terminal.
* @param y The row to clip.
*/
private _clipRow(terminal: ITerminal, y: number): void {
this._ctx.beginPath();
this._ctx.rect(
0,
y * this._scaledCellHeight,
terminal.cols * this._scaledCellWidth,
this._scaledCellHeight);
this._ctx.clip();
}
}
+33 -2
View File
@@ -142,13 +142,23 @@ class CharAtlasGenerator {
// Default color
for (let i = 0; i < 256; i++) {
this._ctx.save();
this._ctx.beginPath();
this._ctx.rect(i * cellWidth, 0, cellWidth, cellHeight);
this._ctx.clip();
this._ctx.fillText(String.fromCharCode(i), i * cellWidth, 0);
this._ctx.restore();
}
// Default color bold
this._ctx.save();
this._ctx.font = `bold ${this._ctx.font}`;
for (let i = 0; i < 256; i++) {
this._ctx.save();
this._ctx.beginPath();
this._ctx.rect(i * cellWidth, cellHeight, cellWidth, cellHeight);
this._ctx.clip();
this._ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight);
this._ctx.restore();
}
this._ctx.restore();
@@ -162,14 +172,17 @@ class CharAtlasGenerator {
const y = (colorIndex + 2) * cellHeight;
// Draw ascii characters
for (let i = 0; i < 256; i++) {
this._ctx.save();
this._ctx.beginPath();
this._ctx.rect(i * cellWidth, y, cellWidth, cellHeight);
this._ctx.clip();
this._ctx.fillStyle = ansiColors[colorIndex];
this._ctx.fillText(String.fromCharCode(i), i * cellWidth, y);
this._ctx.restore();
}
}
this._ctx.restore();
const charAtlasImageData = this._ctx.getImageData(0, 0, this._canvas.width, this._canvas.height);
// Support is patchy for createImageBitmap at the moment, pass a canvas back
// if support is lacking as drawImage works there too. Firefox is also
// included here as ImageBitmap appears both buggy and has horrible
@@ -183,9 +196,27 @@ class CharAtlasGenerator {
return result;
}
const charAtlasImageData = this._ctx.getImageData(0, 0, this._canvas.width, this._canvas.height);
// Remove the background color from the image so characters may overlap
const r = parseInt(background.substr(1, 2), 16);
const g = parseInt(background.substr(3, 2), 16);
const b = parseInt(background.substr(5, 2), 16);
this._clearColor(charAtlasImageData, r, g, b);
const promise = window.createImageBitmap(charAtlasImageData);
// Clear the rect while the promise is in progress
this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
return promise;
}
private _clearColor(imageData: ImageData, r: number, g: number, b: number): void {
for (let offset = 0; offset < imageData.data.length; offset += 4) {
if (imageData.data[offset] === r &&
imageData.data[offset + 1] === g &&
imageData.data[offset + 2] === b) {
imageData.data[offset + 3] = 0;
}
}
}
}
+14 -1
View File
@@ -86,7 +86,7 @@ export class ColorManager implements IColorManager {
*/
public setTheme(theme: ITheme): void {
this.colors.foreground = theme.foreground || DEFAULT_FOREGROUND;
this.colors.background = theme.background || DEFAULT_BACKGROUND;
this.colors.background = this._validateColor(theme.background, DEFAULT_BACKGROUND);
this.colors.cursor = theme.cursor || DEFAULT_CURSOR;
this.colors.cursorAccent = theme.cursorAccent || DEFAULT_CURSOR_ACCENT;
this.colors.selection = theme.selection || DEFAULT_SELECTION;
@@ -107,4 +107,17 @@ export class ColorManager implements IColorManager {
this.colors.ansi[14] = theme.brightCyan || DEFAULT_ANSI_COLORS[14];
this.colors.ansi[15] = theme.brightWhite || DEFAULT_ANSI_COLORS[15];
}
private _validateColor(color: string, fallback: string): string {
if (color.length === 7 && color.charAt(0) === '#') {
return color;
}
if (color.length === 4 && color.charAt(0) === '#') {
const r = color.charAt(1);
const g = color.charAt(2);
const b = color.charAt(3);
return `#${r}${r}${g}${g}${b}${b}`;
}
return fallback;
}
}
+3
View File
@@ -131,6 +131,9 @@ export class CursorRenderLayer extends BaseRenderLayer {
}
const charData = terminal.buffer.lines.get(cursorY)[terminal.buffer.x];
if (!charData) {
return;
}
if (!terminal.isFocused) {
this._clearCursor();

Some files were not shown because too many files have changed in this diff Show More