Add WebGL acceleration and link handling to terminal (#205)

This PR adds back WebGL acceleration (enabled by default) for XTerm. It
also adds back link handling and adds a new option (disabled by default)
to open all links internally as a new web block.

---------

Co-authored-by: sawka <mike.sawka@gmail.com>
This commit is contained in:
Evan Simkowitz 2024-08-07 14:27:16 -07:00 committed by GitHub
parent 3c8bac6bf9
commit 7b87b7d7b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 110 additions and 10 deletions

View File

@ -435,6 +435,20 @@ function isDev() {
return cachedIsDev;
}
async function openLink(uri: string) {
if (globalStore.get(atoms.settingsConfigAtom)?.web?.openlinksinternally) {
const blockDef: BlockDef = {
meta: {
view: "web",
url: uri,
},
};
await createBlock(blockDef);
} else {
getApi().openExternal(uri);
}
}
export {
PLATFORM,
WOS,
@ -451,6 +465,7 @@ export {
initGlobal,
initWS,
isDev,
openLink,
sendWSCommand,
setBlockFocus,
setPlatform,

View File

@ -425,7 +425,7 @@ function TableBody({
{
label: "Open Preview in New Block",
click: async () => {
const blockDef = {
const blockDef: BlockDef = {
meta: {
view: "preview",
file: path,

View File

@ -9,14 +9,13 @@ import * as keyutil from "@/util/keyutil";
import clsx from "clsx";
import { produce } from "immer";
import * as jotai from "jotai";
import "public/xterm.css";
import * as React from "react";
import { TermStickers } from "./termsticker";
import { TermThemeUpdater } from "./termtheme";
import { computeTheme } from "./termutil";
import { TermWrap } from "./termwrap";
import "public/xterm.css";
const keyMap = {
Enter: "\r",
Backspace: "\x7f",
@ -248,6 +247,7 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => {
},
{
keydownHandler: handleTerminalKeydown,
useWebGl: !termSettings?.disablewebgl,
}
);
(window as any).term = termWrap;

View File

@ -2,15 +2,36 @@
// SPDX-License-Identifier: Apache-2.0
import { WshServer } from "@/app/store/wshserver";
import { PLATFORM, fetchWaveFile, getFileSubject, sendWSCommand } from "@/store/global";
import { PLATFORM, fetchWaveFile, getFileSubject, openLink, sendWSCommand } from "@/store/global";
import * as services from "@/store/services";
import { base64ToArray } from "@/util/util";
import { base64ToArray, fireAndForget } from "@/util/util";
import { SerializeAddon } from "@xterm/addon-serialize";
import { WebLinksAddon } from "@xterm/addon-web-links";
import { WebglAddon } from "@xterm/addon-webgl";
import * as TermTypes from "@xterm/xterm";
import { Terminal } from "@xterm/xterm";
import { debounce } from "throttle-debounce";
import { FitAddon } from "./fitaddon";
// detect webgl support
function detectWebGLSupport(): boolean {
try {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("webgl");
return !!ctx;
} catch (e) {
return false;
}
}
const WebGLSupported = detectWebGLSupport();
let loggedWebGL = false;
type TermWrapOptions = {
keydownHandler?: (e: KeyboardEvent) => void;
useWebGl?: boolean;
};
export class TermWrap {
blockId: string;
ptyOffset: number;
@ -30,7 +51,7 @@ export class TermWrap {
blockId: string,
connectElem: HTMLDivElement,
options: TermTypes.ITerminalOptions & TermTypes.ITerminalInitOnlyOptions,
waveOptions: { keydownHandler?: (e: KeyboardEvent) => void }
waveOptions: TermWrapOptions
) {
this.blockId = blockId;
this.ptyOffset = 0;
@ -41,6 +62,35 @@ export class TermWrap {
this.serializeAddon = new SerializeAddon();
this.terminal.loadAddon(this.fitAddon);
this.terminal.loadAddon(this.serializeAddon);
this.terminal.loadAddon(
new WebLinksAddon((e, uri) => {
e.preventDefault();
switch (PLATFORM) {
case "darwin":
if (e.metaKey) {
fireAndForget(() => openLink(uri));
}
break;
default:
if (e.ctrlKey) {
fireAndForget(() => openLink(uri));
}
break;
}
})
);
if (WebGLSupported && waveOptions.useWebGl) {
const webglAddon = new WebglAddon();
webglAddon.onContextLoss(() => {
webglAddon.dispose();
});
this.terminal.loadAddon(webglAddon);
if (!loggedWebGL) {
console.log("loaded webgl!");
loggedWebGL = true;
}
}
this.connectElem = connectElem;
this.mainFileSubject = null;
this.loaded = false;

View File

@ -1,7 +1,7 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
import { getApi } from "@/app/store/global";
import { openLink } from "@/app/store/global";
import { WOS, globalStore } from "@/store/global";
import * as services from "@/store/services";
import clsx from "clsx";
@ -336,7 +336,7 @@ const WebView = memo(({ parentRef, model }: WebViewProps) => {
webview.addEventListener("new-window", (e: any) => {
e.preventDefault();
const newUrl = e.detail.url;
getApi().openExternal(newUrl);
openLink(newUrl);
});
// Suppress errors

View File

@ -323,6 +323,7 @@ declare global {
autoupdate: AutoUpdateOpts;
termthemes: {[key: string]: TermThemeType};
window: WindowSettingsType;
web: WebConfigType;
defaultmeta?: MetaType;
presets?: {[key: string]: MetaType};
};
@ -398,6 +399,7 @@ declare global {
type TerminalConfigType = {
fontsize?: number;
fontfamily?: string;
disablewebgl: boolean;
};
// wstore.UIContext
@ -545,6 +547,11 @@ declare global {
args: any[];
};
// wconfig.WebConfigType
type WebConfigType = {
openlinksinternally: boolean;
};
// service.WebReturnType
type WebReturnType = {
success?: boolean;

View File

@ -83,6 +83,8 @@
"@types/color": "^3.0.6",
"@xterm/addon-fit": "^0.10.0",
"@xterm/addon-serialize": "^0.13.0",
"@xterm/addon-web-links": "^0.11.0",
"@xterm/addon-webgl": "^0.18.0",
"@xterm/xterm": "^5.5.0",
"base64-js": "^1.5.1",
"clsx": "^2.1.1",

View File

@ -25,8 +25,13 @@ type WidgetsConfigType struct {
}
type TerminalConfigType struct {
FontSize int `json:"fontsize,omitempty"`
FontFamily string `json:"fontfamily,omitempty"`
FontSize int `json:"fontsize,omitempty"`
FontFamily string `json:"fontfamily,omitempty"`
DisableWebGl bool `json:"disablewebgl"`
}
type WebConfigType struct {
OpenLinksInternally bool `json:"openlinksinternally"`
}
type AiConfigType struct {
@ -99,6 +104,7 @@ type SettingsConfigType struct {
AutoUpdate *AutoUpdateOpts `json:"autoupdate"`
TermThemes TermThemesConfigType `json:"termthemes"`
WindowSettings WindowSettingsType `json:"window"`
Web WebConfigType `json:"web"`
DefaultMeta *waveobj.MetaMapType `json:"defaultmeta,omitempty"`
Presets map[string]*waveobj.MetaMapType `json:"presets,omitempty"`

View File

@ -4570,6 +4570,24 @@ __metadata:
languageName: node
linkType: hard
"@xterm/addon-web-links@npm:^0.11.0":
version: 0.11.0
resolution: "@xterm/addon-web-links@npm:0.11.0"
peerDependencies:
"@xterm/xterm": ^5.0.0
checksum: 10c0/9426bed80afa954b0ea97771d041eb44e77a64e560ce8b8ef507a5d3a763979af18ae9f74ed54007bb7e235d0daf035be2a33f90d8edfecb431caf8ba0b0664e
languageName: node
linkType: hard
"@xterm/addon-webgl@npm:^0.18.0":
version: 0.18.0
resolution: "@xterm/addon-webgl@npm:0.18.0"
peerDependencies:
"@xterm/xterm": ^5.0.0
checksum: 10c0/682a3f5f128ee09a0cf1b41cbb7b2f925a5e43056e12ba0c523b93a1f5f188045caef9e31f32db933b8a7a1b12d8f9babaddfa11e6f11df0c7b265009103476c
languageName: node
linkType: hard
"@xterm/xterm@npm:^5.5.0":
version: 5.5.0
resolution: "@xterm/xterm@npm:5.5.0"
@ -12352,6 +12370,8 @@ __metadata:
"@vitest/coverage-istanbul": "npm:^2.0.5"
"@xterm/addon-fit": "npm:^0.10.0"
"@xterm/addon-serialize": "npm:^0.13.0"
"@xterm/addon-web-links": "npm:^0.11.0"
"@xterm/addon-webgl": "npm:^0.18.0"
"@xterm/xterm": "npm:^5.5.0"
base64-js: "npm:^1.5.1"
clsx: "npm:^2.1.1"