mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Merge remote-tracking branch 'upstream/master' into protected
This commit is contained in:
@@ -238,21 +238,24 @@ export class GlyphRenderer extends Disposable {
|
||||
// a_cellpos only changes on resize
|
||||
}
|
||||
|
||||
public clear(force?: boolean): void {
|
||||
public clear(): void {
|
||||
const terminal = this._terminal;
|
||||
const newCount = terminal.cols * terminal.rows * INDICES_PER_CELL;
|
||||
|
||||
// Don't clear if not forced and the array length is correct
|
||||
if (!force && this._vertices.count === newCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear vertices
|
||||
this._vertices.count = newCount;
|
||||
this._vertices.attributes = new Float32Array(newCount);
|
||||
for (let i = 0; i < this._vertices.attributesBuffers.length; i++) {
|
||||
this._vertices.attributesBuffers[i] = new Float32Array(newCount);
|
||||
if (this._vertices.count !== newCount) {
|
||||
this._vertices.attributes = new Float32Array(newCount);
|
||||
} else {
|
||||
this._vertices.attributes.fill(0);
|
||||
}
|
||||
for (let i = 0; i < this._vertices.attributesBuffers.length; i++) {
|
||||
if (this._vertices.count !== newCount) {
|
||||
this._vertices.attributesBuffers[i] = new Float32Array(newCount);
|
||||
} else {
|
||||
this._vertices.attributesBuffers[i].fill(0);
|
||||
}
|
||||
}
|
||||
this._vertices.count = newCount;
|
||||
let i = 0;
|
||||
for (let y = 0; y < terminal.rows; y++) {
|
||||
for (let x = 0; x < terminal.cols; x++) {
|
||||
@@ -269,9 +272,6 @@ export class GlyphRenderer extends Disposable {
|
||||
this.clear();
|
||||
}
|
||||
|
||||
public setColors(): void {
|
||||
}
|
||||
|
||||
public render(renderModel: IRenderModel): void {
|
||||
if (!this._atlas) {
|
||||
return;
|
||||
|
||||
@@ -163,12 +163,11 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
}
|
||||
|
||||
this._rectangleRenderer.setColors();
|
||||
this._glyphRenderer.setColors();
|
||||
|
||||
this._refreshCharAtlas();
|
||||
|
||||
// Force a full refresh
|
||||
this._model.clear();
|
||||
this._clearModel(true);
|
||||
}
|
||||
|
||||
public onDevicePixelRatioChange(): void {
|
||||
@@ -208,8 +207,9 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
|
||||
this._refreshCharAtlas();
|
||||
|
||||
// Force a full refresh
|
||||
this._model.clear();
|
||||
// Force a full refresh. Resizing `_glyphRenderer` should clear it already,
|
||||
// so there is no need to clear it again here.
|
||||
this._clearModel(false);
|
||||
}
|
||||
|
||||
public onCharSizeChanged(): void {
|
||||
@@ -293,16 +293,27 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
this._glyphRenderer.setAtlas(this._charAtlas);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the model.
|
||||
* @param clearGlyphRenderer Whether to also clear the glyph renderer. This
|
||||
* should be true generally to make sure it is in the same state as the model.
|
||||
*/
|
||||
private _clearModel(clearGlyphRenderer: boolean): void {
|
||||
this._model.clear();
|
||||
if (clearGlyphRenderer) {
|
||||
this._glyphRenderer.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public clearCharAtlas(): void {
|
||||
this._charAtlas?.clearTexture();
|
||||
this._model.clear();
|
||||
this._clearModel(true);
|
||||
this._updateModel(0, this._terminal.rows - 1);
|
||||
this._requestRedrawViewport();
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this._model.clear();
|
||||
this._glyphRenderer.clear(true);
|
||||
this._clearModel(true);
|
||||
for (const l of this._renderLayers) {
|
||||
l.reset(this._terminal);
|
||||
}
|
||||
@@ -334,7 +345,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
|
||||
// Tell renderer the frame is beginning
|
||||
if (this._glyphRenderer.beginFrame()) {
|
||||
this._model.clear();
|
||||
this._clearModel(true);
|
||||
this._updateSelectionModel(undefined, undefined);
|
||||
}
|
||||
|
||||
|
||||
@@ -226,7 +226,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
protected _fillCharTrueColor(terminal: Terminal, cell: CellData, x: number, y: number): void {
|
||||
this._ctx.font = this._getFont(terminal, false, false);
|
||||
this._ctx.textBaseline = TEXT_BASELINE;
|
||||
this._clipRow(terminal, y);
|
||||
this._clipCell(x, y, cell.getWidth());
|
||||
this._ctx.fillText(
|
||||
cell.getChars(),
|
||||
x * this._scaledCellWidth + this._scaledCharLeft,
|
||||
@@ -234,16 +234,17 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Clips a row to ensure no pixels will be drawn outside the cells in the row.
|
||||
* @param terminal The terminal.
|
||||
* Clips a cell to ensure no pixels will be drawn outside of it.
|
||||
* @param x The column to clip.
|
||||
* @param y The row to clip.
|
||||
* @param width The number of columns to clip.
|
||||
*/
|
||||
private _clipRow(terminal: Terminal, y: number): void {
|
||||
private _clipCell(x: number, y: number, width: number): void {
|
||||
this._ctx.beginPath();
|
||||
this._ctx.rect(
|
||||
0,
|
||||
x * this._scaledCellWidth,
|
||||
y * this._scaledCellHeight,
|
||||
terminal.cols * this._scaledCellWidth,
|
||||
width * this._scaledCellWidth,
|
||||
this._scaledCellHeight);
|
||||
this._ctx.clip();
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import { IRenderService } from 'browser/services/Services';
|
||||
import { IBufferService, ICoreService, IOptionsService } from 'common/services/Services';
|
||||
import { C0 } from 'common/data/EscapeSequences';
|
||||
|
||||
interface IPosition {
|
||||
start: number;
|
||||
@@ -186,11 +187,19 @@ export class CompositionHelper {
|
||||
// Ignore if a composition has started since the timeout
|
||||
if (!this._isComposing) {
|
||||
const newValue = this._textarea.value;
|
||||
|
||||
const diff = newValue.replace(oldValue, '');
|
||||
if (diff.length > 0) {
|
||||
this._dataAlreadySent = diff;
|
||||
|
||||
this._dataAlreadySent = diff;
|
||||
|
||||
if (newValue.length > oldValue.length) {
|
||||
this._coreService.triggerDataEvent(diff, true);
|
||||
} else if (newValue.length < oldValue.length) {
|
||||
this._coreService.triggerDataEvent(`${C0.DEL}`, true);
|
||||
} else if ((newValue.length === oldValue.length) && (newValue !== oldValue)) {
|
||||
this._coreService.triggerDataEvent(newValue, true);
|
||||
}
|
||||
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,11 @@ export function observeDevicePixelDimensions(element: HTMLElement, callback: (de
|
||||
callback(width, height);
|
||||
}
|
||||
});
|
||||
observer.observe(element, { box: ['device-pixel-content-box'] } as any);
|
||||
try {
|
||||
observer.observe(element, { box: ['device-pixel-content-box'] } as any);
|
||||
} catch {
|
||||
observer.disconnect();
|
||||
observer = undefined;
|
||||
}
|
||||
return toDisposable(() => observer?.disconnect());
|
||||
}
|
||||
|
||||
@@ -2189,6 +2189,79 @@ describe('InputHandler', () => {
|
||||
assert.deepEqual(sendStack.pop(), '\x1bP1$r0"q\x1b\\'); // reported as DECSCA 0
|
||||
});
|
||||
});
|
||||
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`);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
+102
-10
@@ -4,13 +4,12 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IInputHandler, IAttributeData, IDisposable, IWindowOptions, IColorEvent, IParseStack, ColorIndex, ColorRequestType, IOscLinkData } from 'common/Types';
|
||||
import { IInputHandler, IAttributeData, IDisposable, IWindowOptions, IColorEvent, IParseStack, ColorIndex, ColorRequestType } from 'common/Types';
|
||||
import { C0, C1 } from 'common/data/EscapeSequences';
|
||||
import { CHARSETS, DEFAULT_CHARSET } from 'common/data/Charsets';
|
||||
import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { concat } from 'common/TypedArrayUtils';
|
||||
import { StringToUtf32, stringFromCodePoint, utf32ToString, Utf8ToUtf32 } from 'common/input/TextDecoder';
|
||||
import { StringToUtf32, stringFromCodePoint, Utf8ToUtf32 } from 'common/input/TextDecoder';
|
||||
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
||||
import { EventEmitter, IEvent } from 'common/EventEmitter';
|
||||
import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IFunctionIdentifier } from 'common/parser/Types';
|
||||
@@ -263,6 +262,8 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
this._parser.registerCsiHandler({ intermediates: '\'', final: '}' }, params => this.insertColumns(params));
|
||||
this._parser.registerCsiHandler({ intermediates: '\'', final: '~' }, params => this.deleteColumns(params));
|
||||
this._parser.registerCsiHandler({ intermediates: '"', final: 'q' }, params => this.selectProtected(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
|
||||
@@ -1694,7 +1695,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++) {
|
||||
@@ -1703,7 +1704,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;
|
||||
}
|
||||
}
|
||||
@@ -1856,7 +1857,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;
|
||||
@@ -1940,7 +1941,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?
|
||||
@@ -1952,7 +1953,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;
|
||||
}
|
||||
}
|
||||
@@ -2094,7 +2095,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;
|
||||
@@ -2122,7 +2123,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
|
||||
@@ -2152,6 +2153,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.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user