Merge branch 'master' into strikethrough

This commit is contained in:
Daniel Imms
2021-07-09 06:53:05 -07:00
committed by GitHub
36 changed files with 179 additions and 96 deletions
+1
View File
@@ -47,6 +47,7 @@
"readonly": "generic"
}
],
"@typescript-eslint/consistent-type-assertions": "warn",
"@typescript-eslint/consistent-type-definitions": "warn",
"@typescript-eslint/explicit-function-return-type": [
"warn",
-1
View File
@@ -143,7 +143,6 @@ Xterm.js is used in several world-class applications to provide great terminal e
- [**cPanel & WHM**](https://cpanel.com): The hosting platform of choice.
- [**Nutanix**](https://github.com/nutanix): Nutanix Enterprise Cloud uses xterm in the webssh functionality within Nutanix Calm, and is also looking to move our old noserial (termjs) functionality to xterm.js.
- [**SSH Web Client**](https://github.com/roke22/PHP-SSH2-Web-Client): SSH Web Client with PHP.
- [**Shellvault**](https://www.shellvault.io): The cloud-based SSH terminal you can access from anywhere.
- [**Juno**](http://junolab.org/): A flexible Julia IDE, based on Atom.
- [**webssh**](https://github.com/huashengdun/webssh): Web based ssh client.
- [**info-beamer hosted**](https://info-beamer.com): Uses xterm.js to manage digital signage devices from the web dashboard.
+1 -1
View File
@@ -20,7 +20,7 @@ export class AttachAddon implements ITerminalAddon {
this._socket = socket;
// always set binary type to arraybuffer, we do not handle blobs
this._socket.binaryType = 'arraybuffer';
this._bidirectional = (options && options.bidirectional === false) ? false : true;
this._bidirectional = !(options && options.bidirectional === false);
}
public activate(terminal: Terminal): void {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-ligatures",
"version": "0.5.0",
"version": "0.5.1",
"description": "Add support for programming ligatures to xterm.js",
"author": {
"name": "The xterm.js authors",
+2 -2
View File
@@ -37,7 +37,7 @@ export default async function load(fontFamily: string, cacheSize: number): Promi
// Web environment that supports font access API
if (typeof navigator !== 'undefined' && 'fonts' in navigator) {
try {
const status = await (navigator as IFontAccessNavigator).permissions.request?.({
const status = await (navigator as unknown as IFontAccessNavigator).permissions.request?.({
name: 'local-fonts'
});
if (status && status.state !== 'granted') {
@@ -53,7 +53,7 @@ export default async function load(fontFamily: string, cacheSize: number): Promi
}
const fonts: Record<string, IFontMetadata[]> = {};
try {
const fontsIterator = await (navigator as IFontAccessNavigator).fonts.query();
const fontsIterator = await (navigator as unknown as IFontAccessNavigator).fonts.query();
for (const metadata of fontsIterator) {
if (!fonts.hasOwnProperty(metadata.family)) {
fonts[metadata.family] = [];
+1 -1
View File
@@ -394,7 +394,7 @@ export class SearchAddon implements ITerminalAddon {
// If it is not in the viewport then we scroll else it just gets selected
if (result.row >= (terminal.buffer.active.viewportY + terminal.rows) || result.row < terminal.buffer.active.viewportY) {
let scroll = result.row - terminal.buffer.active.viewportY;
scroll = scroll - Math.floor(terminal.rows / 2);
scroll -= Math.floor(terminal.rows / 2);
terminal.scrollLines(scroll);
}
return true;
@@ -487,9 +487,9 @@ function newArray<T>(initial: T | ((index: number) => T), count: number): T[] {
const array: T[] = new Array<T>(count);
for (let i = 0; i < array.length; i++) {
if (typeof initial === 'function') {
array[i] = (<(index: number) => T>initial)(i);
array[i] = (initial as (index: number) => T)(i);
} else {
array[i] = <T>initial;
array[i] = initial as T;
}
}
return array;
@@ -263,7 +263,7 @@ export class GlyphRenderer {
// Get attributes from fg (excluding inverse) and resolve inverse by pullibng rgb colors
// from bg. This is needed since the inverse fg color should be based on the original bg
// color, not on the selection color
fg = (fg & ~(Attributes.CM_MASK | Attributes.RGB_MASK | FgFlags.INVERSE));
fg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK | FgFlags.INVERSE);
switch (workCell.getBgColorMode()) {
case Attributes.CM_P16:
case Attributes.CM_P256:
+3 -3
View File
@@ -24,9 +24,9 @@ export class WebglAddon implements ITerminalAddon {
throw new Error('Cannot activate WebglAddon before Terminal.open');
}
this._terminal = terminal;
const renderService: IRenderService = (<any>terminal)._core._renderService;
const characterJoinerService: ICharacterJoinerService = (<any>terminal)._core._characterJoinerService;
const colors: IColorSet = (<any>terminal)._core._colorManager.colors;
const renderService: IRenderService = (terminal as any)._core._renderService;
const characterJoinerService: ICharacterJoinerService = (terminal as any)._core._characterJoinerService;
const colors: IColorSet = (terminal as any)._core._colorManager.colors;
this._renderer = new WebglRenderer(terminal, colors, characterJoinerService, this._preserveDrawingBuffer);
this._renderer.onContextLoss(() => this._onContextLoss.fire());
renderService.setRenderer(this._renderer);
@@ -342,7 +342,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
// Flag combined chars with a bit mask so they're easily identifiable
if (chars.length > 1) {
code = code | COMBINED_CHAR_BIT_MASK;
code |= COMBINED_CHAR_BIT_MASK;
}
// Cache the results in the model
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "xterm",
"description": "Full xterm terminal, in your browser",
"version": "4.12.0",
"version": "4.13.0",
"main": "lib/xterm.js",
"style": "css/xterm.css",
"types": "typings/xterm.d.ts",
+1 -1
View File
@@ -112,7 +112,7 @@ export class AccessibilityManager extends Disposable {
}
private _onBoundaryFocus(e: FocusEvent, position: BoundaryPosition): void {
const boundaryElement = <HTMLElement>e.target;
const boundaryElement = e.target as HTMLElement;
const beforeBoundaryElement = this._rowElements[position === BoundaryPosition.TOP ? 1 : this._rowElements.length - 2];
// Don't scroll if the buffer top has reached the end in that direction
+2 -2
View File
@@ -17,7 +17,7 @@ describe('ColorManager', () => {
dom = new jsdom.JSDOM('');
window = dom.window;
document = window.document;
(<any>window).HTMLCanvasElement.prototype.getContext = () => ({
(window as any).HTMLCanvasElement.prototype.getContext = () => ({
createLinearGradient(): any {
return null;
},
@@ -36,7 +36,7 @@ describe('ColorManager', () => {
for (const key of Object.keys(cm.colors)) {
if (key !== 'ansi' && key !== 'contrastCache') {
// A #rrggbb or rgba(...)
assert.ok((<any>cm.colors)[key].css.length >= 7);
assert.ok((cm.colors as any)[key].css.length >= 7);
}
}
assert.equal(cm.colors.ansi.length, 256);
+2 -2
View File
@@ -174,7 +174,7 @@ describe('Linkifier', () => {
assert.equal(mouseZoneManager.zones[0].y1, 1);
assert.equal(mouseZoneManager.zones[0].y2, 1);
// Fires done()
mouseZoneManager.zones[0].clickCallback(<any>{});
mouseZoneManager.zones[0].clickCallback({} as any);
}
});
linkifier.linkifyRows();
@@ -210,7 +210,7 @@ describe('Linkifier', () => {
let count = 0;
linkifier.registerLinkMatcher(/test/, () => assert.fail(), {
validationCallback: (url, cb) => {
count += 1;
count++;
if (count === 2) {
done();
}
+3 -1
View File
@@ -89,7 +89,9 @@ export class Linkifier implements ILinkifier {
if (this._rowsTimeoutId) {
clearTimeout(this._rowsTimeoutId);
}
this._rowsTimeoutId = <number><any>setTimeout(() => this._linkifyRows(), Linkifier._timeBeforeLatency);
// Cannot use window.setTimeout since tests need to run in node
this._rowsTimeoutId = setTimeout(() => this._linkifyRows(), Linkifier._timeBeforeLatency) as any as number;
}
/**
+15 -15
View File
@@ -29,10 +29,10 @@ describe('Terminal', () => {
beforeEach(() => {
term = new TestTerminal(termOptions);
term.refresh = () => { };
(<any>term).renderer = new MockRenderer();
(term as any).renderer = new MockRenderer();
term.viewport = new MockViewport();
(<any>term)._compositionHelper = new MockCompositionHelper();
(<any>term).element = {
(term as any)._compositionHelper = new MockCompositionHelper();
(term as any).element = {
classList: {
toggle: () => { },
remove: () => { }
@@ -86,12 +86,12 @@ describe('Terminal', () => {
assert.equal(e.domEvent instanceof Object, true);
done();
});
const evKeyPress = <KeyboardEvent>{
const evKeyPress = {
preventDefault: () => { },
stopPropagation: () => { },
type: 'keypress',
keyCode: 13
};
} as KeyboardEvent;
term.keyPress(evKeyPress);
});
it('should fire a key event after a keydown DOM event', (done) => {
@@ -100,13 +100,13 @@ describe('Terminal', () => {
assert.equal(e.domEvent instanceof Object, true);
done();
});
(<any>term).textarea = { value: '' };
const evKeyDown = <KeyboardEvent>{
(term as any).textarea = { value: '' };
const evKeyDown = {
preventDefault: () => { },
stopPropagation: () => { },
type: 'keydown',
keyCode: 13
};
} as KeyboardEvent;
term.keyDown(evKeyDown);
});
it('should fire the onResize event', (done) => {
@@ -140,18 +140,18 @@ describe('Terminal', () => {
});
describe('attachCustomKeyEventHandler', () => {
const evKeyDown = <KeyboardEvent>{
const evKeyDown = {
preventDefault: () => { },
stopPropagation: () => { },
type: 'keydown',
keyCode: 77
};
const evKeyPress = <KeyboardEvent>{
} as KeyboardEvent;
const evKeyPress = {
preventDefault: () => { },
stopPropagation: () => { },
type: 'keypress',
keyCode: 77
};
} as KeyboardEvent;
beforeEach(() => {
term.clearSelection = () => { };
@@ -374,13 +374,13 @@ describe('Terminal', () => {
describe('keyPress', () => {
it('should scroll down, when a key is pressed and terminal is scrolled up', () => {
const event = <KeyboardEvent>{
const event = {
type: 'keydown',
key: 'a',
keyCode: 65,
preventDefault: () => { },
stopPropagation: () => { }
};
} as KeyboardEvent;
term.buffer.ydisp = 0;
term.buffer.ybase = 40;
@@ -403,7 +403,7 @@ describe('Terminal', () => {
assert.equal(term.buffer.ydisp, startYDisp);
term.scrollLines(-1);
assert.equal(term.buffer.ydisp, startYDisp - 1);
term.keyPress(<KeyboardEvent>{ keyCode: 0 });
term.keyPress({ keyCode: 0 });
assert.equal(term.buffer.ydisp, startYDisp - 1);
});
});
+2 -2
View File
@@ -72,7 +72,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
// private _visualBellTimer: number;
public browser: IBrowser = <any>Browser;
public browser: IBrowser = Browser as any;
// TODO: We should remove options once components adopt optionsService
public get options(): IInitializedTerminalOptions { return this.optionsService.options; }
@@ -601,7 +601,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
let but: CoreMouseButton;
let action: CoreMouseAction | undefined;
switch ((<any>ev).overrideType || ev.type) {
switch ((ev as any).overrideType || ev.type) {
case 'mousemove':
action = CoreMouseAction.MOVE;
if (ev.buttons === undefined) {
+2 -1
View File
@@ -17,6 +17,7 @@ const ROWS = 25;
const TESTFILES = glob.sync('**/escape_sequence_files/*.in', { cwd: path.join(__dirname, '../..')});
const SKIP_FILES = [
't0055-EL.in', // EL/ED handle cursor at cols differently (see #3362)
't0084-CBT.in',
't0101-NLM.in',
't0103-reverse_wrap.in', // not comparable, we deviate from xterm reverse wrap on purpose
@@ -105,7 +106,7 @@ function formatError(input: string, output: string, expected: string): string {
function addLineNumber(start: number, color: string): (s: string) => string {
let counter = start || 0;
return (s: string): string => {
counter += 1;
counter++;
return '\x1b[33m' + (' ' + counter).slice(-2) + color + s;
};
}
+1 -1
View File
@@ -153,7 +153,7 @@ export class MockTerminal implements ITerminal {
public textarea!: HTMLTextAreaElement;
public rows!: number;
public cols!: number;
public browser: IBrowser = <any>Browser;
public browser: IBrowser = Browser as any;
public writeBuffer!: string[];
public children!: HTMLElement[];
public cursorHidden!: boolean;
+24 -24
View File
@@ -49,7 +49,7 @@ describe('CompositionHelper', () => {
it('Should insert simple characters', (done) => {
// First character 'ㅇ'
compositionHelper.compositionstart();
compositionHelper.compositionupdate(<CompositionEvent><CompositionEvent>{ data: 'ㅇ' });
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = 'ㅇ';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
@@ -57,7 +57,7 @@ describe('CompositionHelper', () => {
assert.equal(handledText, 'ㅇ');
// Second character 'ㅇ'
compositionHelper.compositionstart();
compositionHelper.compositionupdate(<CompositionEvent><CompositionEvent>{ data: 'ㅇ' });
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = 'ㅇㅇ';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
@@ -73,13 +73,13 @@ describe('CompositionHelper', () => {
it('Should insert complex characters', (done) => {
// First character '앙'
compositionHelper.compositionstart();
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'ㅇ' });
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = 'ㅇ';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate(<CompositionEvent>{ data: '아' });
compositionHelper.compositionupdate({ data: '아' });
textarea.value = '아';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate(<CompositionEvent>{ data: '앙' });
compositionHelper.compositionupdate({ data: '앙' });
textarea.value = '앙';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
@@ -87,13 +87,13 @@ describe('CompositionHelper', () => {
assert.equal(handledText, '앙');
// Second character '앙'
compositionHelper.compositionstart();
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'ㅇ' });
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = '앙ㅇ';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate(<CompositionEvent>{ data: '아' });
compositionHelper.compositionupdate({ data: '아' });
textarea.value = '앙아';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate(<CompositionEvent>{ data: '앙' });
compositionHelper.compositionupdate({ data: '앙' });
textarea.value = '앙앙';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
@@ -113,19 +113,19 @@ describe('CompositionHelper', () => {
it('Should insert complex characters that change with following character', (done) => {
// First character '아'
compositionHelper.compositionstart();
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'ㅇ' });
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = 'ㅇ';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate(<CompositionEvent>{ data: '아' });
compositionHelper.compositionupdate({ data: '아' });
textarea.value = '아';
setTimeout(() => { // wait for any textarea updates
// Start second character '아' in first character
compositionHelper.compositionupdate(<CompositionEvent>{ data: '앙' });
compositionHelper.compositionupdate({ data: '앙' });
textarea.value = '앙';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
compositionHelper.compositionstart();
compositionHelper.compositionupdate(<CompositionEvent>{ data: '아' });
compositionHelper.compositionupdate({ data: '아' });
textarea.value = '아아';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
@@ -142,14 +142,14 @@ describe('CompositionHelper', () => {
it('Should insert multi-characters compositions', (done) => {
// First character 'だ'
compositionHelper.compositionstart();
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'd' });
compositionHelper.compositionupdate({ data: 'd' });
textarea.value = 'd';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'だ' });
compositionHelper.compositionupdate({ data: 'だ' });
textarea.value = 'だ';
setTimeout(() => { // wait for any textarea updates
// Second character 'あ'
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'だあ' });
compositionHelper.compositionupdate({ data: 'だあ' });
textarea.value = 'だあ';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
@@ -165,18 +165,18 @@ describe('CompositionHelper', () => {
it('Should insert multi-character compositions that are converted to other characters with the same length', (done) => {
// First character 'だ'
compositionHelper.compositionstart();
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'd' });
compositionHelper.compositionupdate({ data: 'd' });
textarea.value = 'd';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'だ' });
compositionHelper.compositionupdate({ data: 'だ' });
textarea.value = 'だ';
setTimeout(() => { // wait for any textarea updates
// Second character 'ー'
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'だー' });
compositionHelper.compositionupdate({ data: 'だー' });
textarea.value = 'だー';
setTimeout(() => { // wait for any textarea updates
// Convert to katakana 'ダー'
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'ダー' });
compositionHelper.compositionupdate({ data: 'ダー' });
textarea.value = 'ダー';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
@@ -193,18 +193,18 @@ describe('CompositionHelper', () => {
it('Should insert multi-character compositions that are converted to other characters with different lengths', (done) => {
// First character 'い'
compositionHelper.compositionstart();
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'い' });
compositionHelper.compositionupdate({ data: 'い' });
textarea.value = 'い';
setTimeout(() => { // wait for any textarea updates
// Second character 'ま'
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'いm' });
compositionHelper.compositionupdate({ data: 'いm' });
textarea.value = 'いm';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'いま' });
compositionHelper.compositionupdate({ data: 'いま' });
textarea.value = 'いま';
setTimeout(() => { // wait for any textarea updates
// Convert to kanji '今'
compositionHelper.compositionupdate(<CompositionEvent>{ data: '今' });
compositionHelper.compositionupdate({ data: '今' });
textarea.value = '今';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
@@ -221,7 +221,7 @@ describe('CompositionHelper', () => {
it('Should insert non-composition characters input immediately after composition characters', (done) => {
// First character 'ㅇ'
compositionHelper.compositionstart();
compositionHelper.compositionupdate(<CompositionEvent>{ data: 'ㅇ' });
compositionHelper.compositionupdate({ data: 'ㅇ' });
textarea.value = 'ㅇ';
setTimeout(() => { // wait for any textarea updates
compositionHelper.compositionend();
+1 -1
View File
@@ -69,7 +69,7 @@ export class CompositionHelper {
* Handles the compositionupdate event, updating the composition view.
* @param ev The event.
*/
public compositionupdate(ev: CompositionEvent): void {
public compositionupdate(ev: Pick<CompositionEvent, 'data'>): void {
this._compositionView.textContent = ev.data;
this.updateCompositionElements();
setTimeout(() => {
+1 -1
View File
@@ -31,7 +31,7 @@ export class AddonManager implements IDisposable {
};
this._addons.push(loadedAddon);
instance.dispose = () => this._wrappedAddonDispose(loadedAddon);
instance.activate(<any>terminal);
instance.activate(terminal as any);
}
private _wrappedAddonDispose(loadedAddon: ILoadedAddon): void {
+1 -1
View File
@@ -280,7 +280,7 @@ class BufferLineApiView implements IBufferLineApi {
}
if (cell) {
this._line.loadCell(x, <ICellData>cell);
this._line.loadCell(x, cell as ICellData);
return cell;
}
return this._line.loadCell(x, new CellData());
+1 -1
View File
@@ -10,7 +10,7 @@ import { ITerminalOptions } from 'common/services/Services';
export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, options: ITerminalOptions, colors: IColorSet): ICharAtlasConfig {
// null out some fields that don't matter
const clonedColors = <IPartialColorSet>{
const clonedColors: IPartialColorSet = {
foreground: colors.foreground,
background: colors.background,
cursor: undefined,
+1 -1
View File
@@ -387,7 +387,7 @@ export class DomRenderer extends Disposable implements IRenderer {
if (!row) {
return;
}
const span = <HTMLElement>row.children[x];
const span = row.children[x] as HTMLElement;
if (span) {
span.style.textDecoration = enabled ? 'underline' : 'none';
}
@@ -270,7 +270,7 @@ function lineData(data: IPartialLineData[]): IBufferLine {
const tline = new BufferLine(0);
for (let i = 0; i < data.length; ++i) {
const line = data[i][0];
const attr = <number>(data[i][1] || 0);
const attr = (data[i][1] || 0) as number;
const offset = tline.length;
tline.resize(tline.length + line.split('').length, CellData.fromCharData([0, '', 0, 0]));
line.split('').map((char, idx) => tline.setCell(idx + offset, CellData.fromCharData([attr, char, 1, char.charCodeAt(0)])));
+2 -2
View File
@@ -133,8 +133,8 @@ export class SelectionService extends Disposable implements ISelectionService {
super();
// Init listeners
this._mouseMoveListener = event => this._onMouseMove(<MouseEvent>event);
this._mouseUpListener = event => this._onMouseUp(<MouseEvent>event);
this._mouseMoveListener = event => this._onMouseMove(event as MouseEvent);
this._mouseUpListener = event => this._onMouseUp(event as MouseEvent);
this._coreService.onUserInput(() => {
if (this.hasSelection) {
this.clearSelection();
+1 -1
View File
@@ -13,7 +13,7 @@ export class SoundService implements ISoundService {
public static get audioContext(): AudioContext | null {
if (!SoundService._audioContext) {
const audioContextCtor: typeof AudioContext = (<any>window).AudioContext || (<any>window).webkitAudioContext;
const audioContextCtor: typeof AudioContext = (window as any).AudioContext || (window as any).webkitAudioContext;
if (!audioContextCtor) {
console.warn('Web Audio API is not supported by this browser. Consider upgrading to the latest version');
return null;
+1 -1
View File
@@ -16,7 +16,7 @@ export function clone<T>(val: T, depth: number = 5): T {
for (const key in val) {
// Recursively clone eack item unless we're at the maximum depth
clonedObject[key] = depth <= 1 ? val[key] : (val[key] ? clone(val[key], depth - 1) : val[key]);
clonedObject[key] = depth <= 1 ? val[key] : (val[key] && clone(val[key], depth - 1));
}
return clonedObject as T;
+4
View File
@@ -125,6 +125,10 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
this._onScroll.fire({ position: this._bufferService.buffer.ydisp, source: ScrollSource.TERMINAL });
this._dirtyRowService.markRangeDirty(this._bufferService.buffer.scrollTop, this._bufferService.buffer.scrollBottom);
}));
this.register(this._inputHandler.onScroll(event => {
this._onScroll.fire({ position: this._bufferService.buffer.ydisp, source: ScrollSource.TERMINAL });
this._dirtyRowService.markRangeDirty(this._bufferService.buffer.scrollTop, this._bufferService.buffer.scrollBottom);
}));
// Setup WriteBuffer
this._writeBuffer = new WriteBuffer((data, promiseResult) => this._inputHandler.parse(data, promiseResult));
+91 -5
View File
@@ -77,7 +77,7 @@ describe('InputHandler', () => {
optionsService.options.scrollback = 1;
bufferService.reset();
});
it('SL (scrollLeft)', async () => {
it('SL (scrollLeft)', () => {
inputHandler.parseP('12345'.repeat(6));
inputHandler.parseP('\x1b[ @');
assert.deepEqual(getLines(bufferService, 6), ['12345', '2345', '2345', '2345', '2345', '2345']);
@@ -86,7 +86,7 @@ describe('InputHandler', () => {
inputHandler.parseP('\x1b[2 @');
assert.deepEqual(getLines(bufferService, 6), ['12345', '5', '5', '5', '5', '5']);
});
it('SR (scrollRight)', async () => {
it('SR (scrollRight)', () => {
inputHandler.parseP('12345'.repeat(6));
inputHandler.parseP('\x1b[ A');
assert.deepEqual(getLines(bufferService, 6), ['12345', ' 1234', ' 1234', ' 1234', ' 1234', ' 1234']);
@@ -95,7 +95,7 @@ describe('InputHandler', () => {
inputHandler.parseP('\x1b[2 A');
assert.deepEqual(getLines(bufferService, 6), ['12345', ' 1', ' 1', ' 1', ' 1', ' 1']);
});
it('insertColumns (DECIC)', async () => {
it('insertColumns (DECIC)', () => {
inputHandler.parseP('12345'.repeat(6));
inputHandler.parseP('\x1b[3;3H');
inputHandler.parseP('\x1b[\'}');
@@ -111,7 +111,7 @@ describe('InputHandler', () => {
inputHandler.parseP('\x1b[2\'}');
assert.deepEqual(getLines(bufferService, 6), ['12345', '12 3', '12 3', '12 3', '12 3', '12 3']);
});
it('deleteColumns (DECDC)', async () => {
it('deleteColumns (DECDC)', () => {
inputHandler.parseP('12345'.repeat(6));
inputHandler.parseP('\x1b[3;3H');
inputHandler.parseP('\x1b[\'~');
@@ -137,7 +137,7 @@ describe('InputHandler', () => {
bufferService.reset();
});
describe('reverseWraparound set', () => {
it('should not reverse outside of scroll margins', async () => {
it('should not reverse outside of scroll margins', () => {
// prepare buffer content
inputHandler.parseP('#####abcdefghijklmnopqrstuvwxy');
assert.deepEqual(getLines(bufferService, 6), ['#####', 'abcde', 'fghij', 'klmno', 'pqrst', 'uvwxy']);
@@ -1919,6 +1919,92 @@ describe('InputHandler', () => {
});
});
});
// issue #3362 and #2979
describe('EL/ED cursor at buffer.cols', () => {
beforeEach(() => {
bufferService.resize(10, 5);
});
describe('cursor should stay at cols / does not overflow', () => {
it('EL0', async () => {
await inputHandler.parseP('##########\x1b[0K');
assert.equal(bufferService.buffer.x, 10);
assert.deepEqual(getLines(bufferService), ['#'.repeat(10), '', '', '', '']);
});
it('EL1', async () => {
await inputHandler.parseP('##########\x1b[1K');
assert.equal(bufferService.buffer.x, 10);
assert.deepEqual(getLines(bufferService), ['', '', '', '', '']);
});
it('EL2', async () => {
await inputHandler.parseP('##########\x1b[2K');
assert.equal(bufferService.buffer.x, 10);
assert.deepEqual(getLines(bufferService), ['', '', '', '', '']);
});
it('ED0', async () => {
await inputHandler.parseP('##########\x1b[0J');
assert.equal(bufferService.buffer.x, 10);
assert.deepEqual(getLines(bufferService), ['#'.repeat(10), '', '', '', '']);
});
it('ED1', async () => {
await inputHandler.parseP('##########\x1b[1J');
assert.equal(bufferService.buffer.x, 10);
assert.deepEqual(getLines(bufferService), ['', '', '', '', '']);
});
it('ED2', async () => {
await inputHandler.parseP('##########\x1b[2J');
assert.equal(bufferService.buffer.x, 10);
assert.deepEqual(getLines(bufferService), ['', '', '', '', '']);
});
it('ED3', async () => {
await inputHandler.parseP('##########\x1b[3J');
assert.equal(bufferService.buffer.x, 10);
assert.deepEqual(getLines(bufferService), ['#'.repeat(10), '', '', '', '']);
});
});
describe('following sequence keeps working', () => {
// sequences to test (cursor related ones)
const SEQ = [
/* ICH */ '\x1b[10@',
/* SL */ '\x1b[10 @',
/* CUU */ '\x1b[10A',
/* SR */ '\x1b[10 A',
/* CUD */ '\x1b[10B',
/* CUF */ '\x1b[10C',
/* CUB */ '\x1b[10D',
/* CNL */ '\x1b[10E',
/* CPL */ '\x1b[10F',
/* CHA */ '\x1b[10G',
/* CUP */ '\x1b[10;10H',
/* CHT */ '\x1b[10I',
/* IL */ '\x1b[10L',
/* DL */ '\x1b[10M',
/* DCH */ '\x1b[10P',
/* SU */ '\x1b[10S',
/* SD */ '\x1b[10T',
/* ECH */ '\x1b[10X',
/* CBT */ '\x1b[10Z',
/* HPA */ '\x1b[10`',
/* HPR */ '\x1b[10a',
/* REP */ '\x1b[10b',
/* VPA */ '\x1b[10d',
/* VPR */ '\x1b[10e',
/* HVP */ '\x1b[10;10f',
/* TBC */ '\x1b[0g',
/* SCOSC */ '\x1b[s',
/* DECIC */ '\x1b[10\'}',
/* DECDC */ '\x1b[10\'~'
];
it('cursor never advances beyond cols', async () => {
for (const seq of SEQ) {
await inputHandler.parseP('##########\x1b[2J' + seq);
assert.equal(bufferService.buffer.x <= bufferService.cols, true);
inputHandler.reset();
bufferService.reset();
}
});
});
});
});
+2 -2
View File
@@ -1247,7 +1247,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* @vt: #P[Protection attributes are not supported.] CSI DECSED "Selective Erase In Display" "CSI ? Ps J" "Currently the same as ED."
*/
public eraseInDisplay(params: IParams): boolean {
this._restrictCursor();
this._restrictCursor(this._bufferService.cols);
let j;
switch (params.params[0]) {
case 0:
@@ -1319,7 +1319,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* @vt: #P[Protection attributes are not supported.] CSI DECSEL "Selective Erase In Line" "CSI ? Ps K" "Currently the same as EL."
*/
public eraseInLine(params: IParams): boolean {
this._restrictCursor();
this._restrictCursor(this._bufferService.cols);
switch (params.params[0]) {
case 0:
this._eraseInBufferLine(this._bufferService.buffer.y, this._bufferService.buffer.x, this._bufferService.cols);
+1 -1
View File
@@ -125,7 +125,7 @@ export class MockOptionsService implements IOptionsService {
constructor(testOptions?: IPartialTerminalOptions) {
if (testOptions) {
for (const key of Object.keys(testOptions)) {
this.options[key] = (<any>testOptions)[key];
this.options[key] = (testOptions as any)[key];
}
}
}
+2 -2
View File
@@ -16,10 +16,10 @@ describe('BufferRange', () => {
assert.throws(() => getRangeLength(createRange(1, 3, 1, 1), 0));
});
it('should get range multiple lines', () => {
assert.equal(getRangeLength(createRange(1, 1, 4, 5), 5), 23);
assert.equal(getRangeLength(createRange(1, 1, 4, 5), 5), 24);
});
it('should get range for end line right after start line', () => {
assert.equal(getRangeLength(createRange(1, 1, 7, 2), 5), 11);
assert.equal(getRangeLength(createRange(1, 1, 7, 2), 5), 12);
});
});
});
+2 -5
View File
@@ -5,12 +5,9 @@
import { IBufferRange } from 'xterm';
export function getRangeLength(range: IBufferRange, cols: number): number {
if (range.start.y === range.end.y) {
return range.end.x - range.start.x + 1;
}
export function getRangeLength(range: IBufferRange, bufferCols: number): number {
if (range.start.y > range.end.y) {
throw new Error(`Buffer range end (${range.end.x}, ${range.end.y}) cannot be before start (${range.start.x}, ${range.start.y})`);
}
return cols * (range.end.y - range.start.y - 1) + cols - range.start.x + range.end.x;
return bufferCols * (range.end.y - range.start.y) + (range.end.x - range.start.x + 1);
}
+1 -8
View File
@@ -2143,20 +2143,13 @@ getpass@^0.1.1:
dependencies:
assert-plus "^1.0.0"
glob-parent@^5.0.0, glob-parent@^5.1.0:
glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
dependencies:
is-glob "^4.0.1"
glob-parent@~5.1.0:
version "5.1.1"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229"
integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==
dependencies:
is-glob "^4.0.1"
glob-to-regexp@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"