Merge pull request #4095 from jerch/request_mode

DECRQM support
This commit is contained in:
Daniel Imms
2022-09-07 08:58:33 -07:00
committed by GitHub
2 changed files with 173 additions and 7 deletions
+73
View File
@@ -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`);
}
});
});
});
+100 -7
View File
@@ -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
@@ -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++) {
@@ -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;
}
}
@@ -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;
@@ -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?
@@ -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;
}
}
@@ -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;
@@ -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
@@ -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.
*/