Merge pull request #2393 from jerch/window_manipulation

Support for window options
This commit is contained in:
Daniel Imms
2020-02-04 07:38:51 -08:00
committed by GitHub
12 changed files with 551 additions and 38 deletions
+2 -1
View File
@@ -250,7 +250,8 @@ function initOptions(term: TerminalType): void {
'convertEol',
'termName',
// Complex option
'theme'
'theme',
'windowOptions'
];
const stringOptions = {
bellSound: null,
+125 -9
View File
@@ -44,7 +44,7 @@ describe('InputHandler', () => {
bufferService.buffer.x = 1;
bufferService.buffer.y = 2;
bufferService.buffer.ybase = 0;
const inputHandler = new TestInputHandler(terminal, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService());
const inputHandler = new TestInputHandler(terminal, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any);
inputHandler.curAttrData.fg = 3;
// Save cursor position
inputHandler.saveCursor();
@@ -64,7 +64,7 @@ describe('InputHandler', () => {
describe('setCursorStyle', () => {
it('should call Terminal.setOption with correct params', () => {
const optionsService = new MockOptionsService();
const inputHandler = new InputHandler(new MockInputHandlingTerminal(), new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), optionsService, new MockCoreMouseService(), new MockUnicodeService());
const inputHandler = new InputHandler(new MockInputHandlingTerminal(), new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), optionsService, new MockCoreMouseService(), new MockUnicodeService(), {} as any);
inputHandler.setCursorStyle(Params.fromArray([0]));
assert.equal(optionsService.options['cursorStyle'], 'block');
@@ -105,7 +105,7 @@ describe('InputHandler', () => {
it('should toggle Terminal.bracketedPasteMode', () => {
const terminal = new MockInputHandlingTerminal();
terminal.bracketedPasteMode = false;
const inputHandler = new InputHandler(terminal, new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService());
const inputHandler = new InputHandler(terminal, new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any);
// Set bracketed paste mode
inputHandler.setModePrivate(Params.fromArray([2004]));
assert.equal(terminal.bracketedPasteMode, true);
@@ -124,7 +124,7 @@ describe('InputHandler', () => {
it('insertChars', function(): void {
const term = new Terminal();
const bufferService = new MockBufferService(80, 30);
const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService());
const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any);
// insert some data in first and second line
inputHandler.parse(Array(bufferService.cols - 9).join('a'));
@@ -162,7 +162,7 @@ describe('InputHandler', () => {
it('deleteChars', function(): void {
const term = new Terminal();
const bufferService = new MockBufferService(80, 30);
const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService());
const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any);
// insert some data in first and second line
inputHandler.parse(Array(bufferService.cols - 9).join('a'));
@@ -203,7 +203,7 @@ describe('InputHandler', () => {
it('eraseInLine', function(): void {
const term = new Terminal();
const bufferService = new MockBufferService(80, 30);
const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService());
const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any);
// fill 6 lines to test 3 different states
inputHandler.parse(Array(bufferService.cols + 1).join('a'));
@@ -232,7 +232,7 @@ describe('InputHandler', () => {
it('eraseInDisplay', function(): void {
const term = new Terminal({cols: 80, rows: 7});
const bufferService = new MockBufferService(80, 7);
const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService());
const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any);
// fill display with a's
for (let i = 0; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a'));
@@ -367,7 +367,7 @@ describe('InputHandler', () => {
describe('print', () => {
it('should not cause an infinite loop (regression test)', () => {
const term = new Terminal();
const inputHandler = new InputHandler(term, new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService());
const inputHandler = new InputHandler(term, new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any);
const container = new Uint32Array(10);
container[0] = 0x200B;
inputHandler.print(container, 0, 1);
@@ -382,7 +382,7 @@ describe('InputHandler', () => {
beforeEach(() => {
term = new Terminal();
bufferService = new MockBufferService(80, 30);
handler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService());
handler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService(), new MockUnicodeService(), {} as any);
});
it('should handle DECSET/DECRST 47 (alt screen buffer)', () => {
handler.parse('\x1b[?47h\r\n\x1b[31mJUNK\x1b[?47lTEST');
@@ -1270,6 +1270,122 @@ describe('InputHandler', () => {
[131072, 131072], [131072, 131072], [131072, 300000 - 131072 - 131072]
]);
});
describe('windowOptions', () => {
it('all should be disabled by default and not report', () => {
const term = new TestTerminal({cols: 10, rows: 10});
const stack: string[] = [];
term.onData(data => stack.push(data));
term.writeSync('\x1b[14t');
term.writeSync('\x1b[16t');
term.writeSync('\x1b[18t');
term.writeSync('\x1b[20t');
term.writeSync('\x1b[21t');
assert.deepEqual(stack, []);
});
it('14 - GetWinSizePixels', () => {
const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {getWinSizePixels: true}});
const stack: string[] = [];
term.onData(data => stack.push(data));
term.writeSync('\x1b[14t');
// does not report in test terminal due to missing renderer
assert.deepEqual(stack, []);
});
it('16 - GetCellSizePixels', () => {
const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {getCellSizePixels: true}});
const stack: string[] = [];
term.onData(data => stack.push(data));
term.writeSync('\x1b[16t');
// does not report in test terminal due to missing renderer
assert.deepEqual(stack, []);
});
it('18 - GetWinSizeChars', () => {
const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {getWinSizeChars: true}});
const stack: string[] = [];
term.onData(data => stack.push(data));
term.writeSync('\x1b[18t');
assert.deepEqual(stack, ['\x1b[8;10;10t']);
term.resize(50, 20);
term.writeSync('\x1b[18t');
assert.deepEqual(stack, ['\x1b[8;10;10t', '\x1b[8;20;50t']);
});
it('22/23 - PushTitle/PopTitle', () => {
const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {pushTitle: true, popTitle: true}});
const stack: string[] = [];
term.onTitleChange(data => stack.push(data));
term.writeSync('\x1b]0;1\x07');
term.writeSync('\x1b[22t');
term.writeSync('\x1b]0;2\x07');
term.writeSync('\x1b[22t');
term.writeSync('\x1b]0;3\x07');
term.writeSync('\x1b[22t');
assert.deepEqual((term as any)._inputHandler._windowTitleStack, ['1', '2', '3']);
assert.deepEqual((term as any)._inputHandler._iconNameStack, ['1', '2', '3']);
assert.deepEqual(stack, ['1', '2', '3']);
term.writeSync('\x1b[23t');
term.writeSync('\x1b[23t');
term.writeSync('\x1b[23t');
term.writeSync('\x1b[23t'); // one more to test "overflow"
assert.deepEqual((term as any)._inputHandler._windowTitleStack, []);
assert.deepEqual((term as any)._inputHandler._iconNameStack, []);
assert.deepEqual(stack, ['1', '2', '3', '3', '2', '1']);
});
it('22/23 - PushTitle/PopTitle with ;1', () => {
const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {pushTitle: true, popTitle: true}});
const stack: string[] = [];
term.onTitleChange(data => stack.push(data));
term.writeSync('\x1b]0;1\x07');
term.writeSync('\x1b[22;1t');
term.writeSync('\x1b]0;2\x07');
term.writeSync('\x1b[22;1t');
term.writeSync('\x1b]0;3\x07');
term.writeSync('\x1b[22;1t');
assert.deepEqual((term as any)._inputHandler._windowTitleStack, []);
assert.deepEqual((term as any)._inputHandler._iconNameStack, ['1', '2', '3']);
assert.deepEqual(stack, ['1', '2', '3']);
term.writeSync('\x1b[23;1t');
term.writeSync('\x1b[23;1t');
term.writeSync('\x1b[23;1t');
term.writeSync('\x1b[23;1t'); // one more to test "overflow"
assert.deepEqual((term as any)._inputHandler._windowTitleStack, []);
assert.deepEqual((term as any)._inputHandler._iconNameStack, []);
assert.deepEqual(stack, ['1', '2', '3']);
});
it('22/23 - PushTitle/PopTitle with ;2', () => {
const term = new TestTerminal({cols: 10, rows: 10, windowOptions: {pushTitle: true, popTitle: true}});
const stack: string[] = [];
term.onTitleChange(data => stack.push(data));
term.writeSync('\x1b]0;1\x07');
term.writeSync('\x1b[22;2t');
term.writeSync('\x1b]0;2\x07');
term.writeSync('\x1b[22;2t');
term.writeSync('\x1b]0;3\x07');
term.writeSync('\x1b[22;2t');
assert.deepEqual((term as any)._inputHandler._windowTitleStack, ['1', '2', '3']);
assert.deepEqual((term as any)._inputHandler._iconNameStack, []);
assert.deepEqual(stack, ['1', '2', '3']);
term.writeSync('\x1b[23;2t');
term.writeSync('\x1b[23;2t');
term.writeSync('\x1b[23;2t');
term.writeSync('\x1b[23;2t'); // one more to test "overflow"
assert.deepEqual((term as any)._inputHandler._windowTitleStack, []);
assert.deepEqual((term as any)._inputHandler._iconNameStack, []);
assert.deepEqual(stack, ['1', '2', '3', '3', '2', '1']);
});
it('DECCOLM - should only work with "SetWinLines" (24) enabled', () => {
// disabled
const term = new TestTerminal({cols: 10, rows: 10});
term.writeSync('\x1b[?3l');
assert.equal((term as any)._bufferService.cols, 10);
term.writeSync('\x1b[?3h');
assert.equal((term as any)._bufferService.cols, 10);
// enabled
const term2 = new TestTerminal({cols: 10, rows: 10, windowOptions: {setWinLines: true}});
term2.writeSync('\x1b[?3l');
assert.equal((term2 as any)._bufferService.cols, 80);
term2.writeSync('\x1b[?3h');
assert.equal((term2 as any)._bufferService.cols, 132);
});
});
describe('should correctly reset cells taken by wide chars', () => {
let term: TestTerminal;
beforeEach(() => {
+172 -17
View File
@@ -17,10 +17,11 @@ import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IFunctionId
import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags, Content } from 'common/buffer/Constants';
import { CellData } from 'common/buffer/CellData';
import { AttributeData } from 'common/buffer/AttributeData';
import { IAttributeData, IDisposable } from 'common/Types';
import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService } from 'common/services/Services';
import { IAttributeData, IDisposable, IWindowOptions } from 'common/Types';
import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService, IInstantiationService } from 'common/services/Services';
import { OscHandler } from 'common/parser/OscParser';
import { DcsHandler } from 'common/parser/DcsParser';
import { IRenderService } from 'browser/services/Services';
/**
* Map collect to glevel. Used in `selectCharset`.
@@ -56,6 +57,44 @@ const GLEVEL: {[key: string]: number} = {'(': 0, ')': 1, '*': 2, '+': 3, '-': 1,
*/
const MAX_PARSEBUFFER_LENGTH = 131072;
/**
* Limit length of title and icon name stacks.
*/
const STACK_LIMIT = 10;
// map params to window option
function paramToWindowOption(n: number, opts: IWindowOptions): boolean {
if (n > 24) {
return opts.setWinLines || false;
}
switch (n) {
case 1: return !!opts.restoreWin;
case 2: return !!opts.minimizeWin;
case 3: return !!opts.setWinPosition;
case 4: return !!opts.setWinSizePixels;
case 5: return !!opts.raiseWin;
case 6: return !!opts.lowerWin;
case 7: return !!opts.refreshWin;
case 8: return !!opts.setWinSizeChars;
case 9: return !!opts.maximizeWin;
case 10: return !!opts.fullscreenWin;
case 11: return !!opts.getWinState;
case 13: return !!opts.getWinPosition;
case 14: return !!opts.getWinSizePixels;
case 15: return !!opts.getScreenSizePixels;
case 16: return !!opts.getCellSizePixels;
case 18: return !!opts.getWinSizeChars;
case 19: return !!opts.getScreenSizeChars;
case 20: return !!opts.getIconTitle;
case 21: return !!opts.getWinTitle;
case 22: return !!opts.pushTitle;
case 23: return !!opts.popTitle;
case 24: return !!opts.setWinLines;
}
return false;
}
/**
* DCS subparser implementations
@@ -177,6 +216,10 @@ export class InputHandler extends Disposable implements IInputHandler {
private _stringDecoder: StringToUtf32 = new StringToUtf32();
private _utf8Decoder: Utf8ToUtf32 = new Utf8ToUtf32();
private _workCell: CellData = new CellData();
private _windowTitle = '';
private _iconName = '';
private _windowTitleStack: string[] = [];
private _iconNameStack: string[] = [];
private _curAttrData: IAttributeData = DEFAULT_ATTR_DATA.clone();
private _eraseAttrDataInternal: IAttributeData = DEFAULT_ATTR_DATA.clone();
@@ -204,6 +247,7 @@ export class InputHandler extends Disposable implements IInputHandler {
private readonly _optionsService: IOptionsService,
private readonly _coreMouseService: ICoreMouseService,
private readonly _unicodeService: IUnicodeService,
private readonly _instantiationService: IInstantiationService,
private readonly _parser: IEscapeSequenceParser = new EscapeSequenceParser())
{
super();
@@ -282,6 +326,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this._parser.setCsiHandler({intermediates: ' ', final: 'q'}, params => this.setCursorStyle(params));
this._parser.setCsiHandler({final: 'r'}, params => this.setScrollRegion(params));
this._parser.setCsiHandler({final: 's'}, params => this.saveCursor(params));
this._parser.setCsiHandler({final: 't'}, params => this.windowOptions(params));
this._parser.setCsiHandler({final: 'u'}, params => this.restoreCursor(params));
this._parser.setCsiHandler({intermediates: '\'', final: '}'}, params => this.insertColumns(params));
this._parser.setCsiHandler({intermediates: '\'', final: '~'}, params => this.deleteColumns(params));
@@ -308,8 +353,9 @@ export class InputHandler extends Disposable implements IInputHandler {
* OSC handler
*/
// 0 - icon name + title
this._parser.setOscHandler(0, new OscHandler((data: string) => this.setTitle(data)));
this._parser.setOscHandler(0, new OscHandler((data: string) => { this.setTitle(data); this.setIconName(data); }));
// 1 - icon name
this._parser.setOscHandler(1, new OscHandler((data: string) => this.setIconName(data)));
// 2 - title
this._parser.setOscHandler(2, new OscHandler((data: string) => this.setTitle(data)));
// 3 - set property X in the form "prop=value"
@@ -575,6 +621,15 @@ export class InputHandler extends Disposable implements IInputHandler {
* Forward addCsiHandler from parser.
*/
public addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable {
if (id.final === 't' && !id.prefix && !id.intermediates) {
// security: always check whether window option is allowed
return this._parser.addCsiHandler(id, params => {
if (!paramToWindowOption(params.params[0], this._optionsService.options.windowOptions)) {
return true;
}
return callback(params);
});
}
return this._parser.addCsiHandler(id, callback);
}
@@ -1674,11 +1729,16 @@ export class InputHandler extends Disposable implements IInputHandler {
this._charsetService.setgCharset(3, DEFAULT_CHARSET);
// set VT100 mode here
break;
case 3: // 132 col mode
// TODO: move DECCOLM into compat addon
this._terminal.savedCols = this._bufferService.cols;
this._terminal.resize(132, this._bufferService.rows);
this._onRequestReset.fire();
case 3:
/**
* DECCOLM - 132 column mode.
* This is only active if 'SetWinLines' (24) is enabled
* through `options.windowsOptions`.
*/
if (this._optionsService.options.windowOptions.setWinLines) {
this._terminal.resize(132, this._bufferService.rows);
this._onRequestReset.fire();
}
break;
case 6:
this._coreService.decPrivateModes.origin = true;
@@ -1897,14 +1957,15 @@ export class InputHandler extends Disposable implements IInputHandler {
this._coreService.decPrivateModes.applicationCursorKeys = false;
break;
case 3:
// TODO: move DECCOLM into compat addon
// Note: This impl currently does not enforce col 80, instead reverts
// to previous terminal width before entering DECCOLM 132
if (this._bufferService.cols === 132 && this._terminal.savedCols) {
this._terminal.resize(this._terminal.savedCols, this._bufferService.rows);
/**
* DECCOLM - 80 column mode.
* This is only active if 'SetWinLines' (24) is enabled
* through `options.windowsOptions`.
*/
if (this._optionsService.options.windowOptions.setWinLines) {
this._terminal.resize(80, this._bufferService.rows);
this._onRequestReset.fire();
}
delete this._terminal.savedCols;
this._onRequestReset.fire();
break;
case 6:
this._coreService.decPrivateModes.origin = false;
@@ -2365,6 +2426,92 @@ export class InputHandler extends Disposable implements IInputHandler {
}
}
/**
* CSI Ps ; Ps ; Ps t - Various window manipulations and reports (xterm)
*
* Note: Only those listed below are supported. All others are left to integrators and
* need special treatment based on the embedding environment.
*
* Ps = 1 4 supported
* Report xterm text area size in pixels.
* Result is CSI 4 ; height ; width t
* Ps = 14 ; 2 not implemented
* Ps = 16 supported
* Report xterm character cell size in pixels.
* Result is CSI 6 ; height ; width t
* Ps = 18 supported
* Report the size of the text area in characters.
* Result is CSI 8 ; height ; width t
* Ps = 20 supported
* Report xterm window's icon label.
* Result is OSC L label ST
* Ps = 21 supported
* Report xterm window's title.
* Result is OSC l label ST
* Ps = 22 ; 0 -> Save xterm icon and window title on stack. supported
* Ps = 22 ; 1 -> Save xterm icon title on stack. supported
* Ps = 22 ; 2 -> Save xterm window title on stack. supported
* Ps = 23 ; 0 -> Restore xterm icon and window title from stack. supported
* Ps = 23 ; 1 -> Restore xterm icon title from stack. supported
* Ps = 23 ; 2 -> Restore xterm window title from stack. supported
* Ps >= 24 not implemented
*/
public windowOptions(params: IParams): void {
if (!paramToWindowOption(params.params[0], this._optionsService.options.windowOptions)) {
return;
}
const second = (params.length > 1) ? params.params[1] : 0;
const rs = this._instantiationService.getService(IRenderService);
switch (params.params[0]) {
case 14: // GetWinSizePixels, returns CSI 4 ; height ; width t
if (rs && second !== 2) {
console.log(rs.dimensions);
const w = rs.dimensions.scaledCanvasWidth.toFixed(0);
const h = rs.dimensions.scaledCanvasHeight.toFixed(0);
this._coreService.triggerDataEvent(`${C0.ESC}[4;${h};${w}t`);
}
break;
case 16: // GetCellSizePixels, returns CSI 6 ; height ; width t
if (rs) {
const w = rs.dimensions.scaledCellWidth.toFixed(0);
const h = rs.dimensions.scaledCellHeight.toFixed(0);
this._coreService.triggerDataEvent(`${C0.ESC}[6;${h};${w}t`);
}
break;
case 18: // GetWinSizeChars, returns CSI 8 ; height ; width t
if (this._bufferService) {
this._coreService.triggerDataEvent(`${C0.ESC}[8;${this._bufferService.rows};${this._bufferService.cols}t`);
}
break;
case 22: // PushTitle
if (second === 0 || second === 2) {
this._windowTitleStack.push(this._windowTitle);
if (this._windowTitleStack.length > STACK_LIMIT) {
this._windowTitleStack.shift();
}
}
if (second === 0 || second === 1) {
this._iconNameStack.push(this._iconName);
if (this._iconNameStack.length > STACK_LIMIT) {
this._iconNameStack.shift();
}
}
break;
case 23: // PopTitle
if (second === 0 || second === 2) {
if (this._windowTitleStack.length) {
this.setTitle(this._windowTitleStack.pop());
}
}
if (second === 0 || second === 1) {
if (this._iconNameStack.length) {
this.setIconName(this._iconNameStack.pop());
}
}
break;
}
}
/**
* CSI s
@@ -2405,9 +2552,8 @@ export class InputHandler extends Disposable implements IInputHandler {
/**
* OSC 0; <data> ST (set icon name + window title)
* OSC 2; <data> ST (set window title)
* Proxy to set window title. Icon name is not supported.
* Proxy to set window title.
*
* @vt: #P[Icon name is not exposed.] OSC 0 "Set Windows Title and Icon Name" "OSC 0 ; Pt BEL" "Set window title and icon name."
* Icon name is not supported. For Window Title see below.
@@ -2416,9 +2562,18 @@ export class InputHandler extends Disposable implements IInputHandler {
* xterm.js does not manipulate the title directly, instead exposes changes via the event `Terminal.onTitleChange`.
*/
public setTitle(data: string): void {
this._windowTitle = data;
this._terminal.handleTitle(data);
}
/**
* OSC 1; <data> ST
* Note: Icon name is not exposed.
*/
public setIconName(data: string): void {
this._iconName = data;
}
/**
* ESC E
* C1.NEL
+2 -5
View File
@@ -116,9 +116,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
public mouseEvents: CoreMouseEventType = CoreMouseEventType.NONE;
public sendFocus: boolean;
// misc
public savedCols: number;
// write buffer
private _writeBuffer: WriteBuffer;
@@ -248,7 +245,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
this._inputHandler.reset();
} else {
// Register input handler and refire/handle events
this._inputHandler = new InputHandler(this, this._bufferService, this._charsetService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService, this.unicodeService);
this._inputHandler = new InputHandler(this, this._bufferService, this._charsetService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService, this.unicodeService, this._instantiationService);
this._inputHandler.onRequestBell(() => this.bell());
this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end));
this._inputHandler.onRequestReset(() => this.reset());
@@ -1392,7 +1389,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
// Sync the scroll area to make sure scroll events don't fire and scroll the viewport to an
// invalid location
this.viewport.syncScrollArea(true);
this.viewport?.syncScrollArea(true);
this.refresh(0, this.rows - 1);
this._onResize.fire({ cols: x, rows: y });
-1
View File
@@ -201,7 +201,6 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal {
onA11yTabEmitter: EventEmitter<number>;
insertMode: boolean;
bracketedPasteMode: boolean;
savedCols: number;
sendFocus: boolean;
buffers: IBufferSet;
buffer: IBuffer = new MockBuffer();
-1
View File
@@ -23,7 +23,6 @@ export type LineData = CharData[];
export interface IInputHandlingTerminal {
insertMode: boolean;
bracketedPasteMode: boolean;
savedCols: number;
sendFocus: boolean;
buffers: IBufferSet;
+28
View File
@@ -257,3 +257,31 @@ export interface ICoreMouseProtocol {
* (DEFAULT only supports up to 223 1-based as coord value).
*/
export type CoreMouseEncoding = (event: ICoreMouseEvent) => string;
/**
* windowOptions
*/
export interface IWindowOptions {
restoreWin?: boolean;
minimizeWin?: boolean;
setWinPosition?: boolean;
setWinSizePixels?: boolean;
raiseWin?: boolean;
lowerWin?: boolean;
refreshWin?: boolean;
setWinSizeChars?: boolean;
maximizeWin?: boolean;
fullscreenWin?: boolean;
getWinState?: boolean;
getWinPosition?: boolean;
getWinSizePixels?: boolean;
getScreenSizePixels?: boolean;
getCellSizePixels?: boolean;
getWinSizeChars?: boolean;
getScreenSizeChars?: boolean;
getIconTitle?: boolean;
getWinTitle?: boolean;
pushTitle?: boolean;
popTitle?: boolean;
setWinLines?: boolean;
}
+5 -1
View File
@@ -36,7 +36,7 @@ export class ServiceCollection {
return this._entries.has(id);
}
get<T>(id: IServiceIdentifier<T>): T {
get<T>(id: IServiceIdentifier<T>): T | undefined {
return this._entries.get(id);
}
}
@@ -52,6 +52,10 @@ export class InstantiationService implements IInstantiationService {
this._services.set(id, instance);
}
public getService<T>(id: IServiceIdentifier<T>): T | undefined {
return this._services.get(id);
}
public createInstance<T>(ctor: any, ...args: any[]): any {
const serviceDependencies = getServiceDependencies(ctor).sort((a, b) => a.index - b.index);
+3 -2
View File
@@ -45,12 +45,13 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({
theme: {},
rightClickSelectsWord: isMac,
rendererType: 'canvas',
windowOptions: {},
windowsMode: false,
wordSeparator: ' ()[]{}\',"`',
convertEol: false,
termName: 'xterm',
cancelEvents: false,
wordSeparator: ' ()[]{}\',"`'
cancelEvents: false
});
/**
+4 -1
View File
@@ -5,7 +5,7 @@
import { IEvent } from 'common/EventEmitter';
import { IBuffer, IBufferSet } from 'common/buffer/Types';
import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset } from 'common/Types';
import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions } from 'common/Types';
import { createDecorator } from 'common/services/ServiceRegistry';
export const IBufferService = createDecorator<IBufferService>('BufferService');
@@ -171,6 +171,7 @@ export interface IConstructorSignature8<A1, A2, A3, A4, A5, A6, A7, A8, T> {
export const IInstantiationService = createDecorator<IInstantiationService>('InstantiationService');
export interface IInstantiationService {
setService<T>(id: IServiceIdentifier<T>, instance: T): void;
getService<T>(id: IServiceIdentifier<T>): T | undefined;
createInstance<T>(ctor: IConstructorSignature0<T>): T;
createInstance<A1, T>(ctor: IConstructorSignature1<A1, T>, first: A1): T;
@@ -240,6 +241,7 @@ export interface IPartialTerminalOptions {
theme?: ITheme;
windowsMode?: boolean;
wordSeparator?: string;
windowOptions?: IWindowOptions;
}
export interface ITerminalOptions {
@@ -273,6 +275,7 @@ export interface ITerminalOptions {
tabStopWidth: number;
theme: ITheme;
windowsMode: boolean;
windowOptions: IWindowOptions;
wordSeparator: string;
[key: string]: any;
+48
View File
@@ -345,6 +345,44 @@ describe('InputHandler Integration Tests', function(): void {
`);
await pollFor(page, () => getLinesAsArray(3), ['#', ' #', 'abcd####']);
});
describe('Window Options - CSI Ps ; Ps ; Ps t', () => {
it('should be disabled by default', async function() {
await page.evaluate(`(() => {
window._stack = [];
const _h = window.term.onData(data => window._stack.push(data));
window.term.write('\x1b[14t');
window.term.write('\x1b[16t');
window.term.write('\x1b[18t');
window.term.write('\x1b[20t');
window.term.write('\x1b[21t');
return new Promise((r) => window.term.write('', () => { _h.dispose(); r(); }));
})()`);
await pollFor(page, async () => await page.evaluate(`(() => _stack)()`), []);
});
it('14 - GetWinSizePixels', async function() {
await page.evaluate(`window.term.setOption('windowOptions', {getWinSizePixels: true});`);
await page.evaluate(`(() => {
window._stack = [];
const _h = window.term.onData(data => window._stack.push(data));
window.term.write('\x1b[14t');
return new Promise((r) => window.term.write('', () => { _h.dispose(); r(); }));
})()`);
const d = await getDimensions();
await pollFor(page, async () => await page.evaluate(`(() => _stack)()`), [`\x1b[4;${d.height};${d.width}t`]);
});
it('16 - GetCellSizePixels', async function() {
await page.evaluate(`window.term.setOption('windowOptions', {getCellSizePixels: true});`);
await page.evaluate(`(() => {
window._stack = [];
const _h = window.term.onData(data => window._stack.push(data));
window.term.write('\x1b[16t');
return new Promise((r) => window.term.write('', () => { _h.dispose(); r(); }));
})()`);
const d = await getDimensions();
await pollFor(page, async () => await page.evaluate(`(() => _stack)()`), [`\x1b[6;${d.cellHeight};${d.cellWidth}t`]);
});
});
});
describe('ESC', () => {
@@ -403,3 +441,13 @@ async function getCursor(): Promise<{col: number, row: number}> {
})();
`);
}
async function getDimensions(): Promise<any> {
const dim = await page.evaluate(`term._core._renderService.dimensions`);
return {
cellWidth: dim.actualCellWidth.toFixed(0),
cellHeight: dim.actualCellHeight.toFixed(0),
width: dim.canvasWidth.toFixed(0),
height: dim.canvasHeight.toFixed(0)
};
}
+162
View File
@@ -232,6 +232,12 @@ declare module 'xterm' {
* double click to select work logic.
*/
wordSeparator?: string;
/**
* Enable various window manipulation and report features.
* All features are disabled by default for security reasons.
*/
windowOptions?: IWindowOptions;
}
/**
@@ -379,6 +385,162 @@ declare module 'xterm' {
tooMuchOutput: string;
}
/**
* Enable various window manipulation and report features (CSI Ps ; Ps ; Ps t).
*
* Most settings have no default implementation, as they heavily rely on
* the embedding environment.
*
* To implement a feature, create a custom CSI hook like this:
* ```ts
* term.parser.addCsiHandler({final: 't'}, params => {
* const ps = params[0];
* switch (ps) {
* case XY:
* ... // your implementation for option XY
* return true; // signal Ps=XY was handled
* }
* return false; // any Ps that was not handled
* });
* ```
*
* Note on security:
* Most features are meant to deal with some information of the host machine
* where the terminal runs on. This is seen as a security risk possibly leaking
* sensitive data of the host to the program in the terminal. Therefore all options
* (even those without a default implementation) are guarded by the boolean flag
* and disabled by default.
*/
export interface IWindowOptions {
/**
* Ps=1 De-iconify window.
* No default implementation.
*/
restoreWin?: boolean;
/**
* Ps=2 Iconify window.
* No default implementation.
*/
minimizeWin?: boolean;
/**
* Ps=3 ; x ; y
* Move window to [x, y].
* No default implementation.
*/
setWinPosition?: boolean;
/**
* Ps = 4 ; height ; width
* Resize the window to given `height` and `width` in pixels.
* Omitted parameters should reuse the current height or width.
* Zero parameters should use the display's height or width.
* No default implementation.
*/
setWinSizePixels?: boolean;
/**
* Ps=5 Raise the window to the front of the stacking order.
* No default implementation.
*/
raiseWin?: boolean;
/**
* Ps=6 Lower the xterm window to the bottom of the stacking order.
* No default implementation.
*/
lowerWin?: boolean;
/** Ps=7 Refresh the window. */
refreshWin?: boolean;
/**
* Ps = 8 ; height ; width
* Resize the text area to given height and width in characters.
* Omitted parameters should reuse the current height or width.
* Zero parameters use the display's height or width.
* No default implementation.
*/
setWinSizeChars?: boolean;
/**
* Ps=9 ; 0 Restore maximized window.
* Ps=9 ; 1 Maximize window (i.e., resize to screen size).
* Ps=9 ; 2 Maximize window vertically.
* Ps=9 ; 3 Maximize window horizontally.
* No default implementation.
*/
maximizeWin?: boolean;
/**
* Ps=10 ; 0 Undo full-screen mode.
* Ps=10 ; 1 Change to full-screen.
* Ps=10 ; 2 Toggle full-screen.
* No default implementation.
*/
fullscreenWin?: boolean;
/** Ps=11 Report xterm window state.
* If the xterm window is non-iconified, it returns "CSI 1 t".
* If the xterm window is iconified, it returns "CSI 2 t".
* No default implementation.
*/
getWinState?: boolean;
/**
* Ps=13 Report xterm window position. Result is "CSI 3 ; x ; y t".
* Ps=13 ; 2 Report xterm text-area position. Result is "CSI 3 ; x ; y t".
* No default implementation.
*/
getWinPosition?: boolean;
/**
* Ps=14 Report xterm text area size in pixels. Result is "CSI 4 ; height ; width t".
* Ps=14 ; 2 Report xterm window size in pixels. Result is "CSI 4 ; height ; width t".
* Has a default implementation.
*/
getWinSizePixels?: boolean;
/**
* Ps=15 Report size of the screen in pixels. Result is "CSI 5 ; height ; width t".
* No default implementation.
*/
getScreenSizePixels?: boolean;
/**
* Ps=16 Report xterm character cell size in pixels. Result is "CSI 6 ; height ; width t".
* Has a default implementation.
*/
getCellSizePixels?: boolean;
/**
* Ps=18 Report the size of the text area in characters. Result is "CSI 8 ; height ; width t".
* Has a default implementation.
*/
getWinSizeChars?: boolean;
/**
* Ps=19 Report the size of the screen in characters. Result is "CSI 9 ; height ; width t".
* No default implementation.
*/
getScreenSizeChars?: boolean;
/**
* Ps=20 Report xterm window's icon label. Result is "OSC L label ST".
* No default implementation.
*/
getIconTitle?: boolean;
/**
* Ps=21 Report xterm window's title. Result is "OSC l label ST".
* No default implementation.
*/
getWinTitle?: boolean;
/**
* Ps=22 ; 0 Save xterm icon and window title on stack.
* Ps=22 ; 1 Save xterm icon title on stack.
* Ps=22 ; 2 Save xterm window title on stack.
* All variants have a default implementation.
*/
pushTitle?: boolean;
/**
* Ps=23 ; 0 Restore xterm icon and window title from stack.
* Ps=23 ; 1 Restore xterm icon title from stack.
* Ps=23 ; 2 Restore xterm window title from stack.
* All variants have a default implementation.
*/
popTitle?: boolean;
/**
* Ps>=24 Resize to Ps lines (DECSLPP).
* DECSLPP is not implemented. This settings is also used to
* enable / disable DECCOLM (earlier variant of DECSLPP).
*/
setWinLines?: boolean;
}
/**
* The class that represents an xterm.js terminal.
*/