transparent terminal themes (#1561)

This commit is contained in:
Mike Sawka 2024-12-19 10:41:28 -08:00 committed by GitHub
parent 8ae6e47d9b
commit eff12635d7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 151 additions and 35 deletions

View File

@ -45,6 +45,8 @@ wsh editconfig
| term:localshellopts | string[] | set to pass additional parameters to the term:localshellpath (example: `["-NoLogo"]` for PowerShell will remove the copyright notice) | | term:localshellopts | string[] | set to pass additional parameters to the term:localshellpath (example: `["-NoLogo"]` for PowerShell will remove the copyright notice) |
| term:copyonselect | bool | set to false to disable terminal copy-on-select | | term:copyonselect | bool | set to false to disable terminal copy-on-select |
| term:scrollback | int | size of terminal scrollback buffer, max is 10000 | | term:scrollback | int | size of terminal scrollback buffer, max is 10000 |
| term:theme | string | preset name of terminal theme to apply by default (default is "default-dark") |
| term:transparency | float64 | set the background transparency of terminal theme (default 0.5, 0 = not transparent, 1.0 = fully transparent) |
| editor:minimapenabled | bool | set to false to disable editor minimap | | editor:minimapenabled | bool | set to false to disable editor minimap |
| editor:stickyscrollenabled | bool | enables monaco editor's stickyScroll feature (pinning headers of current context, e.g. class names, method names, etc.), defaults to false | | editor:stickyscrollenabled | bool | enables monaco editor's stickyScroll feature (pinning headers of current context, e.g. class names, method names, etc.), defaults to false |
| editor:wordwrap | bool | set to true to enable word wrapping in the editor (defaults to false) | | editor:wordwrap | bool | set to true to enable word wrapping in the editor (defaults to false) |

View File

@ -71,6 +71,14 @@ replace "Cmd" with "Alt" (note that "Ctrl" is "Ctrl" on both Mac, Windows, and L
| ---------------- | ------------- | | ---------------- | ------------- |
| <Kbd k="Cmd:l"/> | Clear AI Chat | | <Kbd k="Cmd:l"/> | Clear AI Chat |
## Terminal Keybindings
| Key | Function |
| ----------------------- | -------------- |
| <Kbd k="Ctrl:Shift:c"/> | Copy |
| <Kbd k="Ctrl:Shift:v"/> | Paste |
| <Kbd k="Cmd:k"/> | Clear Terminal |
## Customizeable Systemwide Global Hotkey ## Customizeable Systemwide Global Hotkey
Wave allows setting a custom global hotkey to focus your most recent window from anywhere in your computer. For more information on this, see [the config docs](./config#customizable-systemwide-global-hotkey). Wave allows setting a custom global hotkey to focus your most recent window from anywhere in your computer. For more information on this, see [the config docs](./config#customizable-systemwide-global-hotkey).

View File

@ -24,6 +24,7 @@ import {
} from "@/store/global"; } from "@/store/global";
import * as services from "@/store/services"; import * as services from "@/store/services";
import * as keyutil from "@/util/keyutil"; import * as keyutil from "@/util/keyutil";
import { boundNumber } from "@/util/util";
import clsx from "clsx"; import clsx from "clsx";
import debug from "debug"; import debug from "debug";
import * as jotai from "jotai"; import * as jotai from "jotai";
@ -62,6 +63,7 @@ class TermViewModel implements ViewModel {
vdomToolbarTarget: jotai.PrimitiveAtom<VDomTargetToolbar>; vdomToolbarTarget: jotai.PrimitiveAtom<VDomTargetToolbar>;
fontSizeAtom: jotai.Atom<number>; fontSizeAtom: jotai.Atom<number>;
termThemeNameAtom: jotai.Atom<string>; termThemeNameAtom: jotai.Atom<string>;
termTransparencyAtom: jotai.Atom<number>;
noPadding: jotai.PrimitiveAtom<boolean>; noPadding: jotai.PrimitiveAtom<boolean>;
endIconButtons: jotai.Atom<IconButtonDecl[]>; endIconButtons: jotai.Atom<IconButtonDecl[]>;
shellProcFullStatus: jotai.PrimitiveAtom<BlockControllerRuntimeStatus>; shellProcFullStatus: jotai.PrimitiveAtom<BlockControllerRuntimeStatus>;
@ -203,10 +205,17 @@ class TermViewModel implements ViewModel {
return get(getOverrideConfigAtom(this.blockId, "term:theme")) ?? DefaultTermTheme; return get(getOverrideConfigAtom(this.blockId, "term:theme")) ?? DefaultTermTheme;
}); });
}); });
this.termTransparencyAtom = useBlockAtom(blockId, "termtransparencyatom", () => {
return jotai.atom<number>((get) => {
let value = get(getOverrideConfigAtom(this.blockId, "term:transparency")) ?? 0.5;
return boundNumber(value, 0, 1);
});
});
this.blockBg = jotai.atom((get) => { this.blockBg = jotai.atom((get) => {
const fullConfig = get(atoms.fullConfigAtom); const fullConfig = get(atoms.fullConfigAtom);
const themeName = get(this.termThemeNameAtom); const themeName = get(this.termThemeNameAtom);
const [_, bgcolor] = computeTheme(fullConfig, themeName); const termTransparency = get(this.termTransparencyAtom);
const [_, bgcolor] = computeTheme(fullConfig, themeName, termTransparency);
if (bgcolor != null) { if (bgcolor != null) {
return { bg: bgcolor }; return { bg: bgcolor };
} }
@ -407,6 +416,11 @@ class TermViewModel implements ViewModel {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
return false; return false;
} else if (keyutil.checkKeyPressed(waveEvent, "Cmd:k")) {
event.preventDefault();
event.stopPropagation();
this.termRef.current?.terminal?.clear();
return false;
} }
const shellProcStatus = globalStore.get(this.shellProcStatus); const shellProcStatus = globalStore.get(this.shellProcStatus);
if ((shellProcStatus == "done" || shellProcStatus == "init") && keyutil.checkKeyPressed(waveEvent, "Enter")) { if ((shellProcStatus == "done" || shellProcStatus == "init") && keyutil.checkKeyPressed(waveEvent, "Enter")) {
@ -453,6 +467,7 @@ class TermViewModel implements ViewModel {
const termThemeKeys = Object.keys(termThemes); const termThemeKeys = Object.keys(termThemes);
const curThemeName = globalStore.get(getBlockMetaKeyAtom(this.blockId, "term:theme")); const curThemeName = globalStore.get(getBlockMetaKeyAtom(this.blockId, "term:theme"));
const defaultFontSize = globalStore.get(getSettingsKeyAtom("term:fontsize")) ?? 12; const defaultFontSize = globalStore.get(getSettingsKeyAtom("term:fontsize")) ?? 12;
const transparencyMeta = globalStore.get(getBlockMetaKeyAtom(this.blockId, "term:transparency"));
const blockData = globalStore.get(this.blockAtom); const blockData = globalStore.get(this.blockAtom);
const overrideFontSize = blockData?.meta?.["term:fontsize"]; const overrideFontSize = blockData?.meta?.["term:fontsize"];
@ -474,6 +489,41 @@ class TermViewModel implements ViewModel {
checked: curThemeName == null, checked: curThemeName == null,
click: () => this.setTerminalTheme(null), click: () => this.setTerminalTheme(null),
}); });
const transparencySubMenu: ContextMenuItem[] = [];
transparencySubMenu.push({
label: "Default",
type: "checkbox",
checked: transparencyMeta == null,
click: () => {
RpcApi.SetMetaCommand(TabRpcClient, {
oref: WOS.makeORef("block", this.blockId),
meta: { "term:transparency": null },
});
},
});
transparencySubMenu.push({
label: "Transparent Background",
type: "checkbox",
checked: transparencyMeta == 0.5,
click: () => {
RpcApi.SetMetaCommand(TabRpcClient, {
oref: WOS.makeORef("block", this.blockId),
meta: { "term:transparency": 0.5 },
});
},
});
transparencySubMenu.push({
label: "No Transparency",
type: "checkbox",
checked: transparencyMeta == 0,
click: () => {
RpcApi.SetMetaCommand(TabRpcClient, {
oref: WOS.makeORef("block", this.blockId),
meta: { "term:transparency": 0 },
});
},
});
const fontSizeSubMenu: ContextMenuItem[] = [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18].map( const fontSizeSubMenu: ContextMenuItem[] = [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18].map(
(fontSize: number) => { (fontSize: number) => {
return { return {
@ -508,6 +558,10 @@ class TermViewModel implements ViewModel {
label: "Font Size", label: "Font Size",
submenu: fontSizeSubMenu, submenu: fontSizeSubMenu,
}); });
fullMenu.push({
label: "Transparency",
submenu: transparencySubMenu,
});
fullMenu.push({ type: "separator" }); fullMenu.push({ type: "separator" });
fullMenu.push({ fullMenu.push({
label: "Force Restart Controller", label: "Force Restart Controller",
@ -734,7 +788,8 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => {
React.useEffect(() => { React.useEffect(() => {
const fullConfig = globalStore.get(atoms.fullConfigAtom); const fullConfig = globalStore.get(atoms.fullConfigAtom);
const termThemeName = globalStore.get(model.termThemeNameAtom); const termThemeName = globalStore.get(model.termThemeNameAtom);
const [termTheme, _] = computeTheme(fullConfig, termThemeName); const termTransparency = globalStore.get(model.termTransparencyAtom);
const [termTheme, _] = computeTheme(fullConfig, termThemeName, termTransparency);
let termScrollback = 1000; let termScrollback = 1000;
if (termSettings?.["term:scrollback"]) { if (termSettings?.["term:scrollback"]) {
termScrollback = Math.floor(termSettings["term:scrollback"]); termScrollback = Math.floor(termSettings["term:scrollback"]);

View File

@ -17,7 +17,8 @@ interface TermThemeProps {
const TermThemeUpdater = ({ blockId, model, termRef }: TermThemeProps) => { const TermThemeUpdater = ({ blockId, model, termRef }: TermThemeProps) => {
const fullConfig = useAtomValue(atoms.fullConfigAtom); const fullConfig = useAtomValue(atoms.fullConfigAtom);
const blockTermTheme = useAtomValue(model.termThemeNameAtom); const blockTermTheme = useAtomValue(model.termThemeNameAtom);
const [theme, _] = computeTheme(fullConfig, blockTermTheme); const transparency = useAtomValue(model.termTransparencyAtom);
const [theme, _] = computeTheme(fullConfig, blockTermTheme, transparency);
useEffect(() => { useEffect(() => {
if (termRef.current?.terminal) { if (termRef.current?.terminal) {
termRef.current.terminal.options.theme = theme; termRef.current.terminal.options.theme = theme;

View File

@ -2,14 +2,32 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
export const DefaultTermTheme = "default-dark"; export const DefaultTermTheme = "default-dark";
import { colord } from "colord";
// returns (theme, bgcolor) function applyTransparencyToColor(hexColor: string, transparency: number): string {
function computeTheme(fullConfig: FullConfigType, themeName: string): [TermThemeType, string] { const alpha = 1 - transparency; // transparency is already 0-1
return colord(hexColor).alpha(alpha).toHex();
}
// returns (theme, bgcolor, transparency (0 - 1.0))
function computeTheme(
fullConfig: FullConfigType,
themeName: string,
termTransparency: number
): [TermThemeType, string] {
let theme: TermThemeType = fullConfig?.termthemes?.[themeName]; let theme: TermThemeType = fullConfig?.termthemes?.[themeName];
if (theme == null) { if (theme == null) {
theme = fullConfig?.termthemes?.[DefaultTermTheme] || ({} as any); theme = fullConfig?.termthemes?.[DefaultTermTheme] || ({} as any);
} }
const themeCopy = { ...theme }; const themeCopy = { ...theme };
if (termTransparency != null && termTransparency > 0) {
if (themeCopy.background) {
themeCopy.background = applyTransparencyToColor(themeCopy.background, termTransparency);
}
if (themeCopy.selectionBackground) {
themeCopy.selectionBackground = applyTransparencyToColor(themeCopy.selectionBackground, termTransparency);
}
}
let bgcolor = themeCopy.background; let bgcolor = themeCopy.background;
themeCopy.background = "#00000000"; themeCopy.background = "#00000000";
return [themeCopy, bgcolor]; return [themeCopy, bgcolor];

View File

@ -491,6 +491,7 @@ declare global {
"term:scrollback"?: number; "term:scrollback"?: number;
"term:vdomblockid"?: string; "term:vdomblockid"?: string;
"term:vdomtoolbarblockid"?: string; "term:vdomtoolbarblockid"?: string;
"term:transparency"?: number;
"web:zoom"?: number; "web:zoom"?: number;
"markdown:fontsize"?: number; "markdown:fontsize"?: number;
"markdown:fixedfontsize"?: number; "markdown:fixedfontsize"?: number;
@ -641,6 +642,7 @@ declare global {
"term:localshellopts"?: string[]; "term:localshellopts"?: string[];
"term:scrollback"?: number; "term:scrollback"?: number;
"term:copyonselect"?: boolean; "term:copyonselect"?: boolean;
"term:transparency"?: number;
"editor:minimapenabled"?: boolean; "editor:minimapenabled"?: boolean;
"editor:stickyscrollenabled"?: boolean; "editor:stickyscrollenabled"?: boolean;
"editor:wordwrap"?: boolean; "editor:wordwrap"?: boolean;

View File

@ -93,6 +93,7 @@ const (
MetaKey_TermScrollback = "term:scrollback" MetaKey_TermScrollback = "term:scrollback"
MetaKey_TermVDomSubBlockId = "term:vdomblockid" MetaKey_TermVDomSubBlockId = "term:vdomblockid"
MetaKey_TermVDomToolbarBlockId = "term:vdomtoolbarblockid" MetaKey_TermVDomToolbarBlockId = "term:vdomtoolbarblockid"
MetaKey_TermTransparency = "term:transparency"
MetaKey_WebZoom = "web:zoom" MetaKey_WebZoom = "web:zoom"

View File

@ -94,6 +94,7 @@ type MetaTSType struct {
TermScrollback *int `json:"term:scrollback,omitempty"` TermScrollback *int `json:"term:scrollback,omitempty"`
TermVDomSubBlockId string `json:"term:vdomblockid,omitempty"` TermVDomSubBlockId string `json:"term:vdomblockid,omitempty"`
TermVDomToolbarBlockId string `json:"term:vdomtoolbarblockid,omitempty"` TermVDomToolbarBlockId string `json:"term:vdomtoolbarblockid,omitempty"`
TermTransparency *float64 `json:"term:transparency,omitempty"` // default 0.5
WebZoom float64 `json:"web:zoom,omitempty"` WebZoom float64 `json:"web:zoom,omitempty"`

View File

@ -22,12 +22,38 @@
"cmdtext": "#f0f0f0", "cmdtext": "#f0f0f0",
"foreground": "#c1c1c1", "foreground": "#c1c1c1",
"selectionBackground": "", "selectionBackground": "",
"background": "#00000077", "background": "#000000",
"cursor": "" "cursor": ""
}, },
"onedarkpro": {
"display:name": "One Dark Pro",
"display:order": 2,
"background": "#282C34",
"foreground": "#ABB2BF",
"cursor": "#D7DAE0",
"selectionBackground": "#528BFF",
"black": "#3F4451",
"red": "#E05561",
"green": "#8CC265",
"yellow": "#D18F52",
"blue": "#4AA5F0",
"magenta": "#C162DE",
"cyan": "#42B3C2",
"white": "#D7DAE0",
"brightBlack": "#4F5666",
"brightRed": "#FF616E",
"brightGreen": "#A5E075",
"brightYellow": "#F0A45D",
"brightBlue": "#4DC4FF",
"brightMagenta": "#DE73FF",
"brightCyan": "#4CD1E0",
"brightWhite": "#E6E6E6",
"gray": "#495162",
"cmdtext": "#ABB2BF"
},
"dracula": { "dracula": {
"display:name": "Dracula", "display:name": "Dracula",
"display:order": 2, "display:order": 3,
"black": "#21222C", "black": "#21222C",
"red": "#FF5555", "red": "#FF5555",
"green": "#50FA7B", "green": "#50FA7B",
@ -53,7 +79,7 @@
}, },
"monokai": { "monokai": {
"display:name": "Monokai", "display:name": "Monokai",
"display:order": 3, "display:order": 4,
"black": "#1B1D1E", "black": "#1B1D1E",
"red": "#F92672", "red": "#F92672",
"green": "#A6E22E", "green": "#A6E22E",
@ -79,7 +105,7 @@
}, },
"campbell": { "campbell": {
"display:name": "Campbell", "display:name": "Campbell",
"display:order": 4, "display:order": 5,
"black": "#0C0C0C", "black": "#0C0C0C",
"red": "#C50F1F", "red": "#C50F1F",
"green": "#13A10E", "green": "#13A10E",
@ -105,7 +131,7 @@
}, },
"warmyellow": { "warmyellow": {
"display:name": "Warm Yellow", "display:name": "Warm Yellow",
"display:order": 4, "display:order": 6,
"black": "#3C3228", "black": "#3C3228",
"red": "#E67E22", "red": "#E67E22",
"green": "#A5D6A7", "green": "#A5D6A7",
@ -127,30 +153,30 @@
"selectionBackground": "#B7950B", "selectionBackground": "#B7950B",
"cursor": "#F9D784" "cursor": "#F9D784"
}, },
"onedarkpro": { "rosepine": {
"display:name": "One Dark Pro", "display:name": "Rose Pine",
"display:order": 1.5, "display:order": 7,
"background": "#282C34", "black": "#26233a",
"foreground": "#ABB2BF", "red": "#eb6f92",
"cursor": "#D7DAE0", "green": "#3e8fb0",
"selectionBackground": "#528BFF", "yellow": "#f6c177",
"black": "#3F4451", "blue": "#9ccfd8",
"red": "#E05561", "magenta": "#c4a7e7",
"green": "#8CC265", "cyan": "#ebbcba",
"yellow": "#D18F52", "white": "#e0def4",
"blue": "#4AA5F0", "brightBlack": "#908caa",
"magenta": "#C162DE", "brightRed": "#ff8cab",
"cyan": "#42B3C2", "brightGreen": "#9ccfb0",
"white": "#D7DAE0", "brightYellow": "#ffd196",
"brightBlack": "#4F5666", "brightBlue": "#bee6e0",
"brightRed": "#FF616E", "brightMagenta": "#e2c4ff",
"brightGreen": "#A5E075", "brightCyan": "#ffd1d0",
"brightYellow": "#F0A45D", "brightWhite": "#fffaf3",
"brightBlue": "#4DC4FF", "gray": "#908caa",
"brightMagenta": "#DE73FF", "cmdtext": "#e0def4",
"brightCyan": "#4CD1E0", "foreground": "#e0def4",
"brightWhite": "#E6E6E6", "selectionBackground": "#403d52",
"gray": "#495162", "background": "#191724",
"cmdtext": "#ABB2BF" "cursor": "#524f67"
} }
} }

View File

@ -33,6 +33,7 @@ const (
ConfigKey_TermLocalShellOpts = "term:localshellopts" ConfigKey_TermLocalShellOpts = "term:localshellopts"
ConfigKey_TermScrollback = "term:scrollback" ConfigKey_TermScrollback = "term:scrollback"
ConfigKey_TermCopyOnSelect = "term:copyonselect" ConfigKey_TermCopyOnSelect = "term:copyonselect"
ConfigKey_TermTransparency = "term:transparency"
ConfigKey_EditorMinimapEnabled = "editor:minimapenabled" ConfigKey_EditorMinimapEnabled = "editor:minimapenabled"
ConfigKey_EditorStickyScrollEnabled = "editor:stickyscrollenabled" ConfigKey_EditorStickyScrollEnabled = "editor:stickyscrollenabled"

View File

@ -60,6 +60,7 @@ type SettingsType struct {
TermLocalShellOpts []string `json:"term:localshellopts,omitempty"` TermLocalShellOpts []string `json:"term:localshellopts,omitempty"`
TermScrollback *int64 `json:"term:scrollback,omitempty"` TermScrollback *int64 `json:"term:scrollback,omitempty"`
TermCopyOnSelect *bool `json:"term:copyonselect,omitempty"` TermCopyOnSelect *bool `json:"term:copyonselect,omitempty"`
TermTransparency *float64 `json:"term:transparency,omitempty"`
EditorMinimapEnabled bool `json:"editor:minimapenabled,omitempty"` EditorMinimapEnabled bool `json:"editor:minimapenabled,omitempty"`
EditorStickyScrollEnabled bool `json:"editor:stickyscrollenabled,omitempty"` EditorStickyScrollEnabled bool `json:"editor:stickyscrollenabled,omitempty"`