Skip to content

DECRQM support #4095

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Sep 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions src/common/InputHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2146,6 +2146,79 @@ describe('InputHandler', () => {
});
});
});
describe('DECRQM', () => {
const reportStack: string[] = [];
beforeEach(() => {
reportStack.length = 0;
coreService.onData(data => reportStack.push(data));
});
it('ANSI 2 (keyboard action mode)', async () => {
await inputHandler.parseP('\x1b[2$p');
assert.deepEqual(reportStack.pop(), '\x1b[2;3$y'); // always set
});
it('ANSI 4 (insert mode)', async () => {
await inputHandler.parseP('\x1b[4$p');
assert.deepEqual(reportStack.pop(), '\x1b[4;2$y'); // reset by default
await inputHandler.parseP('\x1b[4h');
await inputHandler.parseP('\x1b[4$p');
assert.deepEqual(reportStack.pop(), '\x1b[4;1$y'); // now active
await inputHandler.parseP('\x1b[4l');
await inputHandler.parseP('\x1b[4$p');
assert.deepEqual(reportStack.pop(), '\x1b[4;2$y'); // again reset
});
it('ANSI 12 (send/receive)', async () => {
await inputHandler.parseP('\x1b[12$p');
assert.deepEqual(reportStack.pop(), '\x1b[12;4$y'); // always reset
});
it('ANSI 20 (newline mode)', async () => {
await inputHandler.parseP('\x1b[20$p');
assert.deepEqual(reportStack.pop(), '\x1b[20;2$y'); // reset by default
await inputHandler.parseP('\x1b[20h');
await inputHandler.parseP('\x1b[20$p');
assert.deepEqual(reportStack.pop(), '\x1b[20;1$y'); // now active
await inputHandler.parseP('\x1b[20l');
await inputHandler.parseP('\x1b[20$p');
assert.deepEqual(reportStack.pop(), '\x1b[20;2$y'); // again reset
});
it('ANSI unknown', async () => {
await inputHandler.parseP('\x1b[1234$p');
assert.deepEqual(reportStack.pop(), '\x1b[1234;0$y'); // not recognized
});
it('DEC privates with set/reset semantic', async () => {
// initially reset
const reset = [1, 6, 9, 12, 45, 66, 1000, 1002, 1003, 1004, 1006, 1016, 47, 1047, 1049, 2004];
for (const mode of reset) {
await inputHandler.parseP(`\x1b[?${mode}$p`);
assert.deepEqual(reportStack.pop(), `\x1b[?${mode};2$y`); // initial reset
await inputHandler.parseP(`\x1b[?${mode}h`);
await inputHandler.parseP(`\x1b[?${mode}$p`);
assert.deepEqual(reportStack.pop(), `\x1b[?${mode};1$y`); // now active
await inputHandler.parseP(`\x1b[?${mode}l`);
await inputHandler.parseP(`\x1b[?${mode}$p`);
assert.deepEqual(reportStack.pop(), `\x1b[?${mode};2$y`); // again reset
}
// initially set
const set = [7, 25];
for (const mode of set) {
await inputHandler.parseP(`\x1b[?${mode}$p`);
assert.deepEqual(reportStack.pop(), `\x1b[?${mode};1$y`); // initial set
await inputHandler.parseP(`\x1b[?${mode}l`);
await inputHandler.parseP(`\x1b[?${mode}$p`);
assert.deepEqual(reportStack.pop(), `\x1b[?${mode};2$y`); // now inactive
await inputHandler.parseP(`\x1b[?${mode}h`);
await inputHandler.parseP(`\x1b[?${mode}$p`);
assert.deepEqual(reportStack.pop(), `\x1b[?${mode};1$y`); // again set
}
});
it('DEC privates perma modes', async () => {
// [mode number, state value]
const perma = [[3, 0], [8, 3], [1005, 4], [1015, 4], [1048, 1]];
for (const [mode, value] of perma) {
await inputHandler.parseP(`\x1b[?${mode}$p`);
assert.deepEqual(reportStack.pop(), `\x1b[?${mode};${value}$y`);
}
});
});
});


Expand Down
107 changes: 100 additions & 7 deletions src/common/InputHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,8 @@ export class InputHandler extends Disposable implements IInputHandler {
this._parser.registerCsiHandler({ final: 'u' }, params => this.restoreCursor(params));
this._parser.registerCsiHandler({ intermediates: '\'', final: '}' }, params => this.insertColumns(params));
this._parser.registerCsiHandler({ intermediates: '\'', final: '~' }, params => this.deleteColumns(params));
this._parser.registerCsiHandler({ intermediates: '$', final: 'p' }, params => this.requestMode(params, true));
this._parser.registerCsiHandler({ prefix: '?', intermediates: '$', final: 'p' }, params => this.requestMode(params, false));

/**
* execute handler
Expand Down Expand Up @@ -1787,7 +1789,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* | 2 | Keyboard Action Mode (KAM). Always on. | #N |
* | 4 | Insert Mode (IRM). | #Y |
* | 12 | Send/receive (SRM). Always off. | #N |
* | 20 | Automatic Newline (LNM). Always off. | #N |
* | 20 | Automatic Newline (LNM). | #Y |
*/
public setMode(params: IParams): boolean {
for (let i = 0; i < params.length; i++) {
Expand All @@ -1796,7 +1798,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this._coreService.modes.insertMode = true;
break;
case 20:
// this._t.convertEol = true;
this._optionsService.options.convertEol = true;
break;
}
}
Expand Down Expand Up @@ -1949,7 +1951,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this._coreService.decPrivateModes.wraparound = true;
break;
case 12:
// this.cursorBlink = true;
this._optionsService.options.cursorBlink = true;
break;
case 45:
this._coreService.decPrivateModes.reverseWraparound = true;
Expand Down Expand Up @@ -2033,7 +2035,7 @@ export class InputHandler extends Disposable implements IInputHandler {
* | 2 | Keyboard Action Mode (KAM). Always on. | #N |
* | 4 | Replace Mode (IRM). (default) | #Y |
* | 12 | Send/receive (SRM). Always off. | #N |
* | 20 | Normal Linefeed (LNM). Always off. | #N |
* | 20 | Normal Linefeed (LNM). | #Y |
*
*
* FIXME: why is LNM commented out?
Expand All @@ -2045,7 +2047,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this._coreService.modes.insertMode = false;
break;
case 20:
// this._t.convertEol = false;
this._optionsService.options.convertEol = false;
break;
}
}
Expand Down Expand Up @@ -2187,7 +2189,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this._coreService.decPrivateModes.wraparound = false;
break;
case 12:
// this.cursorBlink = false;
this._optionsService.options.cursorBlink = false;
break;
case 45:
this._coreService.decPrivateModes.reverseWraparound = false;
Expand Down Expand Up @@ -2215,7 +2217,7 @@ export class InputHandler extends Disposable implements IInputHandler {
case 1015: // urxvt ext mode mouse - removed in #2507
this._logService.debug('DECRST 1015 not supported (see #2507)');
break;
case 1006: // sgr pixels mode mouse
case 1016: // sgr pixels mode mouse
this._coreMouseService.activeEncoding = 'DEFAULT';
break;
case 25: // hide cursor
Expand Down Expand Up @@ -2245,6 +2247,97 @@ export class InputHandler extends Disposable implements IInputHandler {
return true;
}

/**
* CSI Ps $ p Request ANSI Mode (DECRQM).
*
* Reports CSI Ps; Pm $ y (DECRPM), where Ps is the mode number as in SM/RM,
* and Pm is the mode value:
* 0 - not recognized
* 1 - set
* 2 - reset
* 3 - permanently set
* 4 - permanently reset
*
* @vt: #Y CSI DECRQM "Request Mode" "CSI Ps $p" "Request mode state."
* Returns a report as `CSI Ps; Pm $ y` (DECRPM), where `Ps` is the mode number as in SM/RM
* or DECSET/DECRST, and `Pm` is the mode value:
* - 0: not recognized
* - 1: set
* - 2: reset
* - 3: permanently set
* - 4: permanently reset
*
* For modes not understood xterm.js always returns `notRecognized`. In general this means,
* that a certain operation mode is not implemented and cannot be used.
*
* Modes changing the active terminal buffer (47, 1047, 1049) are not subqueried
* and only report, whether the alternate buffer is set.
*
* Mouse encodings and mouse protocols are handled mutual exclusive,
* thus only one of each of those can be set at a given time.
*
* There is a chance, that some mode reports are not fully in line with xterm.js' behavior,
* e.g. if the default implementation already exposes a certain behavior. If you find
* discrepancies in the mode reports, please file a bug.
*/
public requestMode(params: IParams, ansi: boolean): boolean {
// return value as in DECRPM
const enum V {
NOT_RECOGNIZED = 0,
SET = 1,
RESET = 2,
PERMANENTLY_SET = 3,
PERMANENTLY_RESET = 4
}

// access helpers
const dm = this._coreService.decPrivateModes;
const { activeProtocol: mouseProtocol, activeEncoding: mouseEncoding } = this._coreMouseService;
const cs = this._coreService;
const { buffers, cols } = this._bufferService;
const { active, alt } = buffers;
const opts = this._optionsService.rawOptions;

const f = (m: number, v: V): boolean => {
cs.triggerDataEvent(`${C0.ESC}[${ansi ? '' : '?'}${m};${v}$y`);
return true;
};
const b2v = (value: boolean): V => value ? V.SET : V.RESET;

const p = params.params[0];

if (ansi) {
if (p === 2) return f(p, V.PERMANENTLY_SET);
if (p === 4) return f(p, b2v(cs.modes.insertMode));
if (p === 12) return f(p, V.PERMANENTLY_RESET);
if (p === 20) return f(p, b2v(opts.convertEol));
return f(p, V.NOT_RECOGNIZED);
}

if (p === 1) return f(p, b2v(dm.applicationCursorKeys));
if (p === 3) return f(p, opts.windowOptions.setWinLines ? (cols === 80 ? V.RESET : cols === 132 ? V.SET : V.NOT_RECOGNIZED) : V.NOT_RECOGNIZED);
if (p === 6) return f(p, b2v(dm.origin));
if (p === 7) return f(p, b2v(dm.wraparound));
if (p === 8) return f(p, V.PERMANENTLY_SET);
if (p === 9) return f(p, b2v(mouseProtocol === 'X10'));
if (p === 12) return f(p, b2v(opts.cursorBlink));
if (p === 25) return f(p, b2v(!cs.isCursorHidden));
if (p === 45) return f(p, b2v(dm.reverseWraparound));
if (p === 66) return f(p, b2v(dm.applicationKeypad));
if (p === 1000) return f(p, b2v(mouseProtocol === 'VT200'));
if (p === 1002) return f(p, b2v(mouseProtocol === 'DRAG'));
if (p === 1003) return f(p, b2v(mouseProtocol === 'ANY'));
if (p === 1004) return f(p, b2v(dm.sendFocus));
if (p === 1005) return f(p, V.PERMANENTLY_RESET);
if (p === 1006) return f(p, b2v(mouseEncoding === 'SGR'));
if (p === 1015) return f(p, V.PERMANENTLY_RESET);
if (p === 1016) return f(p, b2v(mouseEncoding === 'SGR_PIXELS'));
if (p === 1048) return f(p, V.SET); // xterm always returns SET here
if (p === 47 || p === 1047 || p === 1049) return f(p, b2v(active === alt));
if (p === 2004) return f(p, b2v(dm.bracketedPasteMode));
return f(p, V.NOT_RECOGNIZED);
}

/**
* Helper to write color information packed with color mode.
*/
Expand Down