diff --git a/docs/docs/config.mdx b/docs/docs/config.mdx index c97ef839b..b1c7b89c5 100644 --- a/docs/docs/config.mdx +++ b/docs/docs/config.mdx @@ -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: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) | diff --git a/docs/docs/keybindings.mdx b/docs/docs/keybindings.mdx index cfb1569e5..7bd2775d6 100644 --- a/docs/docs/keybindings.mdx +++ b/docs/docs/keybindings.mdx @@ -71,6 +71,14 @@ replace "Cmd" with "Alt" (note that "Ctrl" is "Ctrl" on both Mac, Windows, and L | ---------------- | ------------- | | | Clear AI Chat | +## Terminal Keybindings + +| Key | Function | +| ----------------------- | -------------- | +| | Copy | +| | Paste | +| | 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). diff --git a/frontend/app/view/term/term.tsx b/frontend/app/view/term/term.tsx index a834adfd5..d6ad8eec9 100644 --- a/frontend/app/view/term/term.tsx +++ b/frontend/app/view/term/term.tsx @@ -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"; @@ -62,6 +63,7 @@ class TermViewModel implements ViewModel { vdomToolbarTarget: jotai.PrimitiveAtom; fontSizeAtom: jotai.Atom; termThemeNameAtom: jotai.Atom; + termTransparencyAtom: jotai.Atom; noPadding: jotai.PrimitiveAtom; endIconButtons: jotai.Atom; shellProcFullStatus: jotai.PrimitiveAtom; @@ -203,10 +205,17 @@ class TermViewModel implements ViewModel { return get(getOverrideConfigAtom(this.blockId, "term:theme")) ?? DefaultTermTheme; }); }); + this.termTransparencyAtom = useBlockAtom(blockId, "termtransparencyatom", () => { + return jotai.atom((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 }; } @@ -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")) { @@ -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"]; @@ -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 { @@ -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", @@ -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"]); diff --git a/frontend/app/view/term/termtheme.ts b/frontend/app/view/term/termtheme.ts index 8852ae15a..32937b5be 100644 --- a/frontend/app/view/term/termtheme.ts +++ b/frontend/app/view/term/termtheme.ts @@ -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; diff --git a/frontend/app/view/term/termutil.ts b/frontend/app/view/term/termutil.ts index 1bed0e6d5..6b2eb357c 100644 --- a/frontend/app/view/term/termutil.ts +++ b/frontend/app/view/term/termutil.ts @@ -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); + } + } let bgcolor = themeCopy.background; themeCopy.background = "#00000000"; return [themeCopy, bgcolor]; diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index 831c9e734..15838e1ab 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -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; @@ -641,6 +642,7 @@ declare global { "term:localshellopts"?: string[]; "term:scrollback"?: number; "term:copyonselect"?: boolean; + "term:transparency"?: number; "editor:minimapenabled"?: boolean; "editor:stickyscrollenabled"?: boolean; "editor:wordwrap"?: boolean; diff --git a/pkg/waveobj/metaconsts.go b/pkg/waveobj/metaconsts.go index 9071bf326..6b65249b6 100644 --- a/pkg/waveobj/metaconsts.go +++ b/pkg/waveobj/metaconsts.go @@ -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" diff --git a/pkg/waveobj/wtypemeta.go b/pkg/waveobj/wtypemeta.go index 41f0b7be5..bbf30cffb 100644 --- a/pkg/waveobj/wtypemeta.go +++ b/pkg/waveobj/wtypemeta.go @@ -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"` diff --git a/pkg/wconfig/defaultconfig/termthemes.json b/pkg/wconfig/defaultconfig/termthemes.json index ea8a5f1a0..6ac212f31 100644 --- a/pkg/wconfig/defaultconfig/termthemes.json +++ b/pkg/wconfig/defaultconfig/termthemes.json @@ -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", @@ -53,7 +79,7 @@ }, "monokai": { "display:name": "Monokai", - "display:order": 3, + "display:order": 4, "black": "#1B1D1E", "red": "#F92672", "green": "#A6E22E", @@ -79,7 +105,7 @@ }, "campbell": { "display:name": "Campbell", - "display:order": 4, + "display:order": 5, "black": "#0C0C0C", "red": "#C50F1F", "green": "#13A10E", @@ -105,7 +131,7 @@ }, "warmyellow": { "display:name": "Warm Yellow", - "display:order": 4, + "display:order": 6, "black": "#3C3228", "red": "#E67E22", "green": "#A5D6A7", @@ -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" } } diff --git a/pkg/wconfig/metaconsts.go b/pkg/wconfig/metaconsts.go index a1ddaeb4a..3197b9ad0 100644 --- a/pkg/wconfig/metaconsts.go +++ b/pkg/wconfig/metaconsts.go @@ -33,6 +33,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" diff --git a/pkg/wconfig/settingsconfig.go b/pkg/wconfig/settingsconfig.go index b1461eb6f..b65a73a05 100644 --- a/pkg/wconfig/settingsconfig.go +++ b/pkg/wconfig/settingsconfig.go @@ -60,6 +60,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"`