From 73d20c1d3bc43107236ecd0bcac97550a43875e5 Mon Sep 17 00:00:00 2001 From: Jason Lin Date: Tue, 6 Dec 2022 14:20:29 +1100 Subject: [PATCH 1/5] Add option scrollOnKeypress Note that we also change the existing behavior a bit: CoreService.triggerDataEvent() will not scroll to the bottom unless `wasUserInput` is also true. This actually seems to be the intended behavior according to the doc just above ICoreService.triggerDataEvent(). --- src/browser/Terminal.ts | 2 +- src/common/services/CoreService.ts | 2 +- src/common/services/OptionsService.ts | 1 + src/common/services/Services.ts | 3 ++- typings/xterm.d.ts | 6 ++++++ 5 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 3c88b948..2de0200e 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -995,7 +995,7 @@ export class Terminal extends CoreTerminal implements ITerminal { const shouldIgnoreComposition = this.browser.isMac && this.options.macOptionIsMeta && event.altKey; if (!shouldIgnoreComposition && !this._compositionHelper!.keydown(event)) { - if (this.buffer.ybase !== this.buffer.ydisp) { + if (this.options.scrollOnKeypress && this.buffer.ybase !== this.buffer.ydisp) { this._bufferService.scrollToBottom(); } return false; diff --git a/src/common/services/CoreService.ts b/src/common/services/CoreService.ts index 9282197b..101a142a 100644 --- a/src/common/services/CoreService.ts +++ b/src/common/services/CoreService.ts @@ -68,7 +68,7 @@ export class CoreService extends Disposable implements ICoreService { // Input is being sent to the terminal, the terminal should focus the prompt. const buffer = this._bufferService.buffer; - if (buffer.ybase !== buffer.ydisp) { + if (wasUserInput && this._optionsService.rawOptions.scrollOnKeypress && buffer.ybase !== buffer.ydisp) { this._scrollToBottom!(); } diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index 976cdf8d..d1e47051 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -28,6 +28,7 @@ export const DEFAULT_OPTIONS: Readonly> = { linkHandler: null, logLevel: 'info', scrollback: 1000, + scrollOnKeypress: true, scrollSensitivity: 1, screenReaderMode: false, smoothScrollDuration: 0, diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index cc388063..5f4e692b 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -87,7 +87,7 @@ export interface ICoreService { * @param data The data that is being emitted. * @param wasFromUser Whether the data originated from the user (as opposed to * resulting from parsing incoming data). When true this will also: - * - Scroll to the bottom of the buffer.s + * - Scroll to the bottom of the buffer if option scrollOnKeypress is true. * - Fire the `onUserInput` event (so selection can be cleared). */ triggerDataEvent(data: string, wasUserInput?: boolean): void; @@ -243,6 +243,7 @@ export interface ITerminalOptions { rows?: number; screenReaderMode?: boolean; scrollback?: number; + scrollOnKeypress?: boolean; scrollSensitivity?: number; smoothScrollDuration?: number; tabStopWidth?: number; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 926cc6b4..2eedaac8 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -193,6 +193,12 @@ declare module 'xterm' { */ scrollback?: number; + /** + * Whether to scroll to the bottom whenever a key is pressed. The default is + * true. + */ + scrollOnKeypress?: boolean; + /** * The scrolling speed multiplier used for adjusting normal scrolling speed. */ From e46a9e12d0107085ec3f0db73aaf938a8d2389b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Tue, 6 Dec 2022 13:40:25 +0100 Subject: [PATCH 2/5] fix demo in epiphany --- .../xterm-addon-canvas/src/BaseRenderLayer.ts | 3 +++ demo/client.ts | 26 +++++++++---------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index d2a7a4ed..fb146884 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -373,6 +373,9 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer } else { glyph = this._charAtlas.getRasterizedGlyph(cell.getCode() || WHITESPACE_CELL_CODE, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext); } + if (!glyph.size.x || !glyph.size.y) { + return; + } this._ctx.save(); this._clipRow(y); // Draw the image, use the bitmap if it's available diff --git a/demo/client.ts b/demo/client.ts index 38c4748a..280b5e56 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -273,22 +273,22 @@ function createTerminal(): void { socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/'; addons.fit.instance!.fit(); - typedTerm.loadAddon(addons.webgl.instance); - setTimeout(() => { - if (addons.webgl.instance !== undefined) { - setTextureAtlas(addons.webgl.instance.textureAtlas); - addons.webgl.instance.onChangeTextureAtlas(e => setTextureAtlas(e)); - addons.webgl.instance.onAddTextureAtlasCanvas(e => appendTextureAtlas(e)); - addons.webgl.instance.onRemoveTextureAtlasCanvas(e => removeTextureAtlas(e)); - } - }, 0); - try { // try-catch to allow the demo to load if webgl is not supported + // try to start with webgl renderer (might throw on older safari/webkit) + try { + typedTerm.loadAddon(addons.webgl.instance); + term.open(terminalContainer); + setTextureAtlas(addons.webgl.instance.textureAtlas); + addons.webgl.instance.onChangeTextureAtlas(e => setTextureAtlas(e)); + addons.webgl.instance.onAddTextureAtlasCanvas(e => appendTextureAtlas(e)); + addons.webgl.instance.onRemoveTextureAtlasCanvas(e => removeTextureAtlas(e)); + } catch (e) { + console.log(e); + addons.webgl.instance.dispose(); + addons.webgl.instance = undefined; term.open(terminalContainer); } - catch { - addons.webgl.instance = undefined; - } + term.focus(); addDomListener(paddingElement, 'change', setPadding); From 9e84895141a40a958ec14241a159bbf1e9f2f6a4 Mon Sep 17 00:00:00 2001 From: Jason Lin Date: Wed, 7 Dec 2022 10:12:43 +1100 Subject: [PATCH 3/5] polish --- src/browser/Terminal.ts | 2 +- src/common/services/CoreService.ts | 2 +- src/common/services/OptionsService.ts | 2 +- src/common/services/Services.ts | 6 +++--- typings/xterm.d.ts | 6 +++--- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 2de0200e..606f3a53 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -995,7 +995,7 @@ export class Terminal extends CoreTerminal implements ITerminal { const shouldIgnoreComposition = this.browser.isMac && this.options.macOptionIsMeta && event.altKey; if (!shouldIgnoreComposition && !this._compositionHelper!.keydown(event)) { - if (this.options.scrollOnKeypress && this.buffer.ybase !== this.buffer.ydisp) { + if (this.options.scrollOnUserInput && this.buffer.ybase !== this.buffer.ydisp) { this._bufferService.scrollToBottom(); } return false; diff --git a/src/common/services/CoreService.ts b/src/common/services/CoreService.ts index 101a142a..321678a1 100644 --- a/src/common/services/CoreService.ts +++ b/src/common/services/CoreService.ts @@ -68,7 +68,7 @@ export class CoreService extends Disposable implements ICoreService { // Input is being sent to the terminal, the terminal should focus the prompt. const buffer = this._bufferService.buffer; - if (wasUserInput && this._optionsService.rawOptions.scrollOnKeypress && buffer.ybase !== buffer.ydisp) { + if (wasUserInput && this._optionsService.rawOptions.scrollOnUserInput && buffer.ybase !== buffer.ydisp) { this._scrollToBottom!(); } diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index d1e47051..9709f2a7 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -28,7 +28,7 @@ export const DEFAULT_OPTIONS: Readonly> = { linkHandler: null, logLevel: 'info', scrollback: 1000, - scrollOnKeypress: true, + scrollOnUserInput: true, scrollSensitivity: 1, screenReaderMode: false, smoothScrollDuration: 0, diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 5f4e692b..a5e1c5bc 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -85,9 +85,9 @@ export interface ICoreService { /** * Triggers the onData event in the public API. * @param data The data that is being emitted. - * @param wasFromUser Whether the data originated from the user (as opposed to + * @param wasUserInput Whether the data originated from the user (as opposed to * resulting from parsing incoming data). When true this will also: - * - Scroll to the bottom of the buffer if option scrollOnKeypress is true. + * - Scroll to the bottom of the buffer if option scrollOnUserInput is true. * - Fire the `onUserInput` event (so selection can be cleared). */ triggerDataEvent(data: string, wasUserInput?: boolean): void; @@ -243,7 +243,7 @@ export interface ITerminalOptions { rows?: number; screenReaderMode?: boolean; scrollback?: number; - scrollOnKeypress?: boolean; + scrollOnUserInput?: boolean; scrollSensitivity?: number; smoothScrollDuration?: number; tabStopWidth?: number; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 2eedaac8..d5e207fa 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -194,10 +194,10 @@ declare module 'xterm' { scrollback?: number; /** - * Whether to scroll to the bottom whenever a key is pressed. The default is - * true. + * Whether to scroll to the bottom whenever there is some user input. The + * default is true. */ - scrollOnKeypress?: boolean; + scrollOnUserInput?: boolean; /** * The scrolling speed multiplier used for adjusting normal scrolling speed. From 0b56b56c069ab257a9ce55a642d5369dbc04f3ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 7 Dec 2022 22:55:45 +0100 Subject: [PATCH 4/5] skipping renderer on NUL and SP --- addons/xterm-addon-canvas/src/TextRenderLayer.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/addons/xterm-addon-canvas/src/TextRenderLayer.ts b/addons/xterm-addon-canvas/src/TextRenderLayer.ts index 66fc5106..b429e8c2 100644 --- a/addons/xterm-addon-canvas/src/TextRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/TextRenderLayer.ts @@ -95,6 +95,12 @@ export class TextRenderLayer extends BaseRenderLayer { continue; } + // exit early for NULL and SP + const code = cell.getCode(); + if (code === 0 || code === 32) { + continue; + } + // Process any joined character ranges as needed. Because of how the // ranges are produced, we know that they are valid for the characters // and attributes of our input. From d2298d84a0b2846d146dcf232d791bd1632d20ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Wed, 7 Dec 2022 23:56:43 +0100 Subject: [PATCH 5/5] move safari check to WebglAddon ctor --- addons/xterm-addon-webgl/src/WebglAddon.ts | 8 ++--- demo/client.ts | 34 +++++++++++++--------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts index 6dbbeca9..97e2ce0c 100644 --- a/addons/xterm-addon-webgl/src/WebglAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -9,7 +9,6 @@ import { EventEmitter, forwardEvent } from 'common/EventEmitter'; import { Disposable, toDisposable } from 'common/Lifecycle'; import { getSafariVersion, isSafari } from 'common/Platform'; import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; -import { ICoreTerminal } from 'common/Types'; import { ITerminalAddon, Terminal } from 'xterm'; import { WebglRenderer } from './WebglRenderer'; @@ -29,14 +28,13 @@ export class WebglAddon extends Disposable implements ITerminalAddon { constructor( private _preserveDrawingBuffer?: boolean ) { + if (isSafari && getSafariVersion() < 16) { + throw new Error('Webgl2 is only supported on Safari 16 and above'); + } super(); } public activate(terminal: Terminal): void { - if (isSafari && getSafariVersion() < 16) { - throw new Error('Webgl2 is only supported on Safari 16 and above'); - } - const core = (terminal as any)._core as ITerminal; if (!terminal.element) { this.register(core.onWillOpen(() => this.activate(terminal))); diff --git a/demo/client.ts b/demo/client.ts index 280b5e56..7c04bb2a 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -250,7 +250,11 @@ function createTerminal(): void { addons.serialize.instance = new SerializeAddon(); addons.fit.instance = new FitAddon(); addons.unicode11.instance = new Unicode11Addon(); - addons.webgl.instance = new WebglAddon(); + try { // try to start with webgl renderer (might throw on older safari/webkit) + addons.webgl.instance = new WebglAddon(); + } catch (e) { + console.warn(e); + } addons['web-links'].instance = new WebLinksAddon(); typedTerm.loadAddon(addons.fit.instance); typedTerm.loadAddon(addons.search.instance); @@ -274,18 +278,22 @@ function createTerminal(): void { addons.fit.instance!.fit(); - // try to start with webgl renderer (might throw on older safari/webkit) - try { - typedTerm.loadAddon(addons.webgl.instance); - term.open(terminalContainer); - setTextureAtlas(addons.webgl.instance.textureAtlas); - addons.webgl.instance.onChangeTextureAtlas(e => setTextureAtlas(e)); - addons.webgl.instance.onAddTextureAtlasCanvas(e => appendTextureAtlas(e)); - addons.webgl.instance.onRemoveTextureAtlasCanvas(e => removeTextureAtlas(e)); - } catch (e) { - console.log(e); - addons.webgl.instance.dispose(); - addons.webgl.instance = undefined; + if (addons.webgl.instance) { + try { + typedTerm.loadAddon(addons.webgl.instance); + term.open(terminalContainer); + setTextureAtlas(addons.webgl.instance.textureAtlas); + addons.webgl.instance.onChangeTextureAtlas(e => setTextureAtlas(e)); + addons.webgl.instance.onAddTextureAtlasCanvas(e => appendTextureAtlas(e)); + addons.webgl.instance.onRemoveTextureAtlasCanvas(e => removeTextureAtlas(e)); + } catch (e) { + console.warn('error during loading webgl addon:', e); + addons.webgl.instance.dispose(); + addons.webgl.instance = undefined; + } + } + if (!typedTerm.element) { + // webgl loading failed for some reason, attach with DOM renderer term.open(terminalContainer); }