Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

transparent terminal themes #1561

Merged
merged 6 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/docs/config.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,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:copyonselect | bool | set to false to disable terminal copy-on-select |
| 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: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) |
Expand Down
8 changes: 8 additions & 0 deletions docs/docs/keybindings.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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 |

## 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

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).
Expand Down
59 changes: 57 additions & 2 deletions frontend/app/view/term/term.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
} from "@/store/global";
import * as services from "@/store/services";
import * as keyutil from "@/util/keyutil";
import { boundNumber } from "@/util/util";
import clsx from "clsx";
import debug from "debug";
import * as jotai from "jotai";
Expand Down Expand Up @@ -62,6 +63,7 @@ class TermViewModel implements ViewModel {
vdomToolbarTarget: jotai.PrimitiveAtom<VDomTargetToolbar>;
fontSizeAtom: jotai.Atom<number>;
termThemeNameAtom: jotai.Atom<string>;
termTransparencyAtom: jotai.Atom<number>;
noPadding: jotai.PrimitiveAtom<boolean>;
endIconButtons: jotai.Atom<IconButtonDecl[]>;
shellProcFullStatus: jotai.PrimitiveAtom<BlockControllerRuntimeStatus>;
Expand Down Expand Up @@ -203,10 +205,17 @@ class TermViewModel implements ViewModel {
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) => {
const fullConfig = get(atoms.fullConfigAtom);
const themeName = get(this.termThemeNameAtom);
const [_, bgcolor] = computeTheme(fullConfig, themeName);
const termTransparency = get(this.termTransparencyAtom);
const [_, bgcolor] = computeTheme(fullConfig, themeName, termTransparency);
if (bgcolor != null) {
return { bg: bgcolor };
}
Expand Down Expand Up @@ -407,6 +416,11 @@ class TermViewModel implements ViewModel {
event.preventDefault();
event.stopPropagation();
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);
if ((shellProcStatus == "done" || shellProcStatus == "init") && keyutil.checkKeyPressed(waveEvent, "Enter")) {
Expand Down Expand Up @@ -453,6 +467,7 @@ class TermViewModel implements ViewModel {
const termThemeKeys = Object.keys(termThemes);
const curThemeName = globalStore.get(getBlockMetaKeyAtom(this.blockId, "term:theme"));
const defaultFontSize = globalStore.get(getSettingsKeyAtom("term:fontsize")) ?? 12;
const transparencyMeta = globalStore.get(getBlockMetaKeyAtom(this.blockId, "term:transparency"));
const blockData = globalStore.get(this.blockAtom);
const overrideFontSize = blockData?.meta?.["term:fontsize"];

Expand All @@ -474,6 +489,41 @@ class TermViewModel implements ViewModel {
checked: curThemeName == 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(
(fontSize: number) => {
return {
Expand Down Expand Up @@ -508,6 +558,10 @@ class TermViewModel implements ViewModel {
label: "Font Size",
submenu: fontSizeSubMenu,
});
fullMenu.push({
label: "Transparency",
submenu: transparencySubMenu,
});
fullMenu.push({ type: "separator" });
fullMenu.push({
label: "Force Restart Controller",
Expand Down Expand Up @@ -734,7 +788,8 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => {
React.useEffect(() => {
const fullConfig = globalStore.get(atoms.fullConfigAtom);
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;
if (termSettings?.["term:scrollback"]) {
termScrollback = Math.floor(termSettings["term:scrollback"]);
Expand Down
3 changes: 2 additions & 1 deletion frontend/app/view/term/termtheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ interface TermThemeProps {
const TermThemeUpdater = ({ blockId, model, termRef }: TermThemeProps) => {
const fullConfig = useAtomValue(atoms.fullConfigAtom);
const blockTermTheme = useAtomValue(model.termThemeNameAtom);
const [theme, _] = computeTheme(fullConfig, blockTermTheme);
const transparency = useAtomValue(model.termTransparencyAtom);
const [theme, _] = computeTheme(fullConfig, blockTermTheme, transparency);
useEffect(() => {
if (termRef.current?.terminal) {
termRef.current.terminal.options.theme = theme;
Expand Down
22 changes: 20 additions & 2 deletions frontend/app/view/term/termutil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,32 @@
// SPDX-License-Identifier: Apache-2.0

export const DefaultTermTheme = "default-dark";
import { colord } from "colord";

// returns (theme, bgcolor)
function computeTheme(fullConfig: FullConfigType, themeName: string): [TermThemeType, string] {
function applyTransparencyToColor(hexColor: string, transparency: number): 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];
if (theme == null) {
theme = fullConfig?.termthemes?.[DefaultTermTheme] || ({} as any);
}
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);
}
}
Comment on lines +12 to +30
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Based on the search results from all scripts, I can now provide a final response:

Transparency implementation needs bounds validation

From the code analysis:

  1. Default transparency is set to 0.5 in term.tsx
  2. UI provides options for transparency: null (default), 0.5 (transparent), and 0 (no transparency)
  3. The computeTheme function only checks for termTransparency != null && termTransparency > 0
  4. No upper bound validation or value clamping is implemented

The current implementation could allow transparency values greater than 1.0, which might lead to unexpected behavior. Consider adding validation or clamping to ensure transparency stays within [0, 1.0].

🔗 Analysis chain

LGTM: Verify transparency behavior with extreme values

The transparency application logic is sound, with proper null checks and conditional application. The code correctly handles both background and selection background colors.

Let's verify the theme computation with various transparency values:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Search for transparency value assignments to ensure they're within bounds

# Look for direct assignments of transparency values
rg -g '*.{ts,tsx,js,jsx}' 'termTransparency.*=.*[0-9]' 

# Look for transparency configuration in JSON/YAML files
rg -g '*.{json,yaml,yml}' '"term:transparency":\s*[0-9]'

Length of output: 210


Script:

#!/bin/bash
# Let's search for all usages and assignments of termTransparency to understand its bounds
rg -g '*.{ts,tsx,js,jsx}' 'termTransparency' -B 2 -A 2

# Also search for any transparency-related configuration or validation
rg -g '*.{ts,tsx,js,jsx}' 'transparency.*=|transparency.*>|transparency.*<' -B 2 -A 2

# Look for any transparency-related types or interfaces
ast-grep --pattern 'interface $_ {
  $$$
  transparency
  $$$
}'

Length of output: 6610


Script:

#!/bin/bash
# Let's check the implementation of applyTransparencyToColor to verify how it handles transparency values
ast-grep --pattern 'function applyTransparencyToColor($_) {
  $$$
}'

# Also search for any transparency value validations or clamps
rg -g '*.{ts,tsx}' 'clamp.*transparency|Math\.(min|max).*transparency' -B 2 -A 2

Length of output: 153

let bgcolor = themeCopy.background;
themeCopy.background = "#00000000";
return [themeCopy, bgcolor];
Expand Down
2 changes: 2 additions & 0 deletions frontend/types/gotypes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,7 @@ declare global {
"term:scrollback"?: number;
"term:vdomblockid"?: string;
"term:vdomtoolbarblockid"?: string;
"term:transparency"?: number;
"web:zoom"?: number;
"markdown:fontsize"?: number;
"markdown:fixedfontsize"?: number;
Expand Down Expand Up @@ -640,6 +641,7 @@ declare global {
"term:localshellopts"?: string[];
"term:scrollback"?: number;
"term:copyonselect"?: boolean;
"term:transparency"?: number;
"editor:minimapenabled"?: boolean;
"editor:stickyscrollenabled"?: boolean;
"editor:wordwrap"?: boolean;
Expand Down
1 change: 1 addition & 0 deletions pkg/waveobj/metaconsts.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ const (
MetaKey_TermScrollback = "term:scrollback"
MetaKey_TermVDomSubBlockId = "term:vdomblockid"
MetaKey_TermVDomToolbarBlockId = "term:vdomtoolbarblockid"
MetaKey_TermTransparency = "term:transparency"

MetaKey_WebZoom = "web:zoom"

Expand Down
1 change: 1 addition & 0 deletions pkg/waveobj/wtypemeta.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ type MetaTSType struct {
TermScrollback *int `json:"term:scrollback,omitempty"`
TermVDomSubBlockId string `json:"term:vdomblockid,omitempty"`
TermVDomToolbarBlockId string `json:"term:vdomtoolbarblockid,omitempty"`
TermTransparency *float64 `json:"term:transparency,omitempty"` // default 0.5

WebZoom float64 `json:"web:zoom,omitempty"`

Expand Down
86 changes: 56 additions & 30 deletions pkg/wconfig/defaultconfig/termthemes.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,38 @@
"cmdtext": "#f0f0f0",
"foreground": "#c1c1c1",
"selectionBackground": "",
"background": "#00000077",
"background": "#000000",
"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": {
"display:name": "Dracula",
"display:order": 2,
"display:order": 3,
"black": "#21222C",
"red": "#FF5555",
"green": "#50FA7B",
Expand All @@ -53,7 +79,7 @@
},
"monokai": {
"display:name": "Monokai",
"display:order": 3,
"display:order": 4,
"black": "#1B1D1E",
"red": "#F92672",
"green": "#A6E22E",
Expand All @@ -79,7 +105,7 @@
},
"campbell": {
"display:name": "Campbell",
"display:order": 4,
"display:order": 5,
"black": "#0C0C0C",
"red": "#C50F1F",
"green": "#13A10E",
Expand All @@ -105,7 +131,7 @@
},
"warmyellow": {
"display:name": "Warm Yellow",
"display:order": 4,
"display:order": 6,
"black": "#3C3228",
"red": "#E67E22",
"green": "#A5D6A7",
Expand All @@ -127,30 +153,30 @@
"selectionBackground": "#B7950B",
"cursor": "#F9D784"
},
"onedarkpro": {
"display:name": "One Dark Pro",
"display:order": 1.5,
"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"
"rosepine": {
"display:name": "Rose Pine",
"display:order": 7,
"black": "#26233a",
"red": "#eb6f92",
"green": "#3e8fb0",
"yellow": "#f6c177",
"blue": "#9ccfd8",
"magenta": "#c4a7e7",
"cyan": "#ebbcba",
"white": "#e0def4",
"brightBlack": "#908caa",
"brightRed": "#ff8cab",
"brightGreen": "#9ccfb0",
"brightYellow": "#ffd196",
"brightBlue": "#bee6e0",
"brightMagenta": "#e2c4ff",
"brightCyan": "#ffd1d0",
"brightWhite": "#fffaf3",
"gray": "#908caa",
"cmdtext": "#e0def4",
"foreground": "#e0def4",
"selectionBackground": "#403d52",
"background": "#191724",
"cursor": "#524f67"
}
}
1 change: 1 addition & 0 deletions pkg/wconfig/metaconsts.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const (
ConfigKey_TermLocalShellOpts = "term:localshellopts"
ConfigKey_TermScrollback = "term:scrollback"
ConfigKey_TermCopyOnSelect = "term:copyonselect"
ConfigKey_TermTransparency = "term:transparency"

ConfigKey_EditorMinimapEnabled = "editor:minimapenabled"
ConfigKey_EditorStickyScrollEnabled = "editor:stickyscrollenabled"
Expand Down
1 change: 1 addition & 0 deletions pkg/wconfig/settingsconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ type SettingsType struct {
TermLocalShellOpts []string `json:"term:localshellopts,omitempty"`
TermScrollback *int64 `json:"term:scrollback,omitempty"`
TermCopyOnSelect *bool `json:"term:copyonselect,omitempty"`
TermTransparency *float64 `json:"term:transparency,omitempty"`

EditorMinimapEnabled bool `json:"editor:minimapenabled,omitempty"`
EditorStickyScrollEnabled bool `json:"editor:stickyscrollenabled,omitempty"`
Expand Down
Loading