Skip to content

Commit b5e358e

Browse files
committed
Merge remote-tracking branch 'origin/main' into copilot/add-new-tsunami-component
2 parents e035595 + 0ab26ef commit b5e358e

23 files changed

Lines changed: 468 additions & 100 deletions

.github/FUNDING.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
github: wavetermdev

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,14 @@ Find more information in our [Contributions Guide](CONTRIBUTING.md), which inclu
104104
- [Ways to contribute](CONTRIBUTING.md#contributing-to-wave-terminal)
105105
- [Contribution guidelines](CONTRIBUTING.md#before-you-start)
106106

107+
### Sponsoring Wave ❤️
108+
109+
If Wave Terminal is useful to you or your company, consider sponsoring development.
110+
111+
Sponsorship helps support the time spent building and maintaining the project.
112+
113+
- https://github.com/sponsors/wavetermdev
114+
107115
## License
108116

109117
Wave Terminal is licensed under the Apache-2.0 License. For more information on our dependencies, see [here](./ACKNOWLEDGEMENTS.md).

docs/docs/config.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ wsh editconfig
7373
| term:cursorblink <VersionBadge version="v0.14" /> | bool | when enabled, terminal cursor blinks (default false) |
7474
| term:bellsound <VersionBadge version="v0.14" /> | bool | when enabled, plays the system beep sound when the terminal bell (BEL character) is received (default false) |
7575
| term:bellindicator <VersionBadge version="v0.14" /> | bool | when enabled, shows a visual indicator in the tab when the terminal bell is received (default false) |
76+
| term:osc52 <VersionBadge version="v0.14" /> | string | controls OSC 52 clipboard behavior: `always` (default, allows OSC 52 at any time) or `focus` (requires focused window and focused block) |
7677
| term:durable <VersionBadge version="v0.14" /> | bool | makes remote terminal sessions durable across network disconnects (defaults to false) |
7778
| editor:minimapenabled | bool | set to false to disable editor minimap |
7879
| editor:stickyscrollenabled | bool | enables monaco editor's stickyScroll feature (pinning headers of current context, e.g. class names, method names, etc.), defaults to false |
@@ -147,6 +148,7 @@ For reference, this is the current default configuration (v0.14.0):
147148
"telemetry:enabled": true,
148149
"term:bellsound": false,
149150
"term:bellindicator": false,
151+
"term:osc52": "always",
150152
"term:cursor": "block",
151153
"term:cursorblink": false,
152154
"term:copyonselect": true,

emain/emain-ipc.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
incrementTermCommandsRemote,
1818
incrementTermCommandsRun,
1919
incrementTermCommandsWsl,
20+
setWasActive,
2021
} from "./emain-activity";
2122
import { createBuilderWindow, getAllBuilderWindows, getBuilderWindowByWebContentsId } from "./emain-builder";
2223
import { callWithOriginalXdgCurrentDesktopAsync, unamePlatform } from "./emain-platform";
@@ -317,6 +318,10 @@ export function initIpcHandlers() {
317318
tabView?.setKeyboardChordMode(true);
318319
});
319320

321+
electron.ipcMain.handle("set-is-active", () => {
322+
setWasActive(true);
323+
});
324+
320325
const fac = new FastAverageColor();
321326
electron.ipcMain.on("update-window-controls-overlay", async (event, rect: Dimensions) => {
322327
if (unamePlatform === "darwin") return;

emain/preload.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ contextBridge.exposeInMainWorld("api", {
7171
setBuilderWindowAppId: (appId: string) => ipcRenderer.send("set-builder-window-appid", appId),
7272
doRefresh: () => ipcRenderer.send("do-refresh"),
7373
saveTextFile: (fileName: string, content: string) => ipcRenderer.invoke("save-text-file", fileName, content),
74+
setIsActive: () => ipcRenderer.invoke("set-is-active"),
7475
});
7576

7677
// Custom event for "new-window"

frontend/app/aipanel/aipanel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ const AIPanelComponentInner = memo(() => {
306306
};
307307

308308
useEffect(() => {
309-
globalStore.set(model.isAIStreaming, status == "streaming");
309+
globalStore.set(model.isAIStreaming, status === "streaming" || status === "submitted");
310310
}, [status]);
311311

312312
useEffect(() => {

frontend/app/app.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,12 +200,16 @@ function AppFocusHandler() {
200200
const AppKeyHandlers = () => {
201201
useEffect(() => {
202202
const staticKeyDownHandler = keyutil.keydownWrapper(appHandleKeyDown);
203+
const staticMouseDownHandler = (e: MouseEvent) => {
204+
keyboardMouseDownHandler(e);
205+
GlobalModel.getInstance().setIsActive();
206+
};
203207
document.addEventListener("keydown", staticKeyDownHandler);
204-
document.addEventListener("mousedown", keyboardMouseDownHandler);
208+
document.addEventListener("mousedown", staticMouseDownHandler);
205209

206210
return () => {
207211
document.removeEventListener("keydown", staticKeyDownHandler);
208-
document.removeEventListener("mousedown", keyboardMouseDownHandler);
212+
document.removeEventListener("mousedown", staticMouseDownHandler);
209213
};
210214
}, []);
211215
return null;

frontend/app/store/global-model.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@
33

44
import * as WOS from "@/app/store/wos";
55
import { ClientModel } from "@/app/store/client-model";
6+
import { getApi } from "@/store/global";
7+
import * as util from "@/util/util";
68
import { atom, Atom } from "jotai";
79

810
class GlobalModel {
911
private static instance: GlobalModel;
12+
static readonly IsActiveThrottleMs = 5000;
1013

1114
windowId: string;
1215
builderId: string;
1316
platform: NodeJS.Platform;
17+
lastSetIsActiveTs = 0;
1418

1519
windowDataAtom!: Atom<WaveWindow>;
1620
workspaceAtom!: Atom<Workspace>;
@@ -47,6 +51,15 @@ class GlobalModel {
4751
return WOS.getObjectValue(WOS.makeORef("workspace", windowData.workspaceid), get);
4852
});
4953
}
54+
55+
setIsActive(): void {
56+
const now = Date.now();
57+
if (now - this.lastSetIsActiveTs < GlobalModel.IsActiveThrottleMs) {
58+
return;
59+
}
60+
this.lastSetIsActiveTs = now;
61+
util.fireAndForget(() => getApi().setIsActive());
62+
}
5063
}
5164

52-
export { GlobalModel };
65+
export { GlobalModel };

frontend/app/view/term/osc-handlers.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,15 @@
33

44
import { RpcApi } from "@/app/store/wshclientapi";
55
import { TabRpcClient } from "@/app/store/wshrpcutil";
6-
import { getApi, getBlockMetaKeyAtom, getBlockTermDurableAtom, globalStore, recordTEvent, WOS } from "@/store/global";
6+
import {
7+
getApi,
8+
getBlockMetaKeyAtom,
9+
getBlockTermDurableAtom,
10+
getOverrideConfigAtom,
11+
globalStore,
12+
recordTEvent,
13+
WOS,
14+
} from "@/store/global";
715
import * as services from "@/store/services";
816
import { base64ToString, fireAndForget, isSshConnName, isWslConnName } from "@/util/util";
917
import debug from "debug";
@@ -114,10 +122,13 @@ export function handleOsc52Command(data: string, blockId: string, loaded: boolea
114122
if (!loaded) {
115123
return true;
116124
}
117-
const isBlockFocused = termWrap.nodeModel ? globalStore.get(termWrap.nodeModel.isFocused) : false;
118-
if (!document.hasFocus() || !isBlockFocused) {
119-
console.log("OSC 52: rejected, window or block not focused");
120-
return true;
125+
const osc52Mode = globalStore.get(getOverrideConfigAtom(blockId, "term:osc52")) ?? "always";
126+
if (osc52Mode === "focus") {
127+
const isBlockFocused = termWrap.nodeModel ? globalStore.get(termWrap.nodeModel.isFocused) : false;
128+
if (!document.hasFocus() || !isBlockFocused) {
129+
console.log("OSC 52: rejected, window or block not focused");
130+
return true;
131+
}
121132
}
122133
if (!data || data.length === 0) {
123134
console.log("OSC 52: empty data received");

frontend/app/workspace/widgets.tsx

Lines changed: 62 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { ContextMenuModel } from "@/app/store/contextmenu";
66
import { RpcApi } from "@/app/store/wshclientapi";
77
import { TabRpcClient } from "@/app/store/wshrpcutil";
88
import { shouldIncludeWidgetForWorkspace } from "@/app/workspace/widgetfilter";
9-
import { atoms, createBlock, isDev } from "@/store/global";
9+
import { atoms, createBlock, getApi, isDev } from "@/store/global";
1010
import { fireAndForget, isBlank, makeIconClass } from "@/util/util";
1111
import {
1212
FloatingPortal,
@@ -111,6 +111,10 @@ const AppsFloatingWindow = memo(
111111

112112
const dismiss = useDismiss(context);
113113
const { getFloatingProps } = useInteractions([dismiss]);
114+
const handleOpenBuilder = useCallback(() => {
115+
getApi().openBuilder(null);
116+
onClose();
117+
}, [onClose]);
114118

115119
useEffect(() => {
116120
if (!isOpen) return;
@@ -148,55 +152,65 @@ const AppsFloatingWindow = memo(
148152
ref={refs.setFloating}
149153
style={floatingStyles}
150154
{...getFloatingProps()}
151-
className="bg-modalbg border border-border rounded-lg shadow-xl p-4 z-50"
155+
className="bg-modalbg border border-border rounded-lg shadow-xl z-50 overflow-hidden"
152156
>
153-
{loading ? (
154-
<div className="flex items-center justify-center p-8">
155-
<i className="fa fa-solid fa-spinner fa-spin text-2xl text-muted"></i>
156-
</div>
157-
) : apps.length === 0 ? (
158-
<div className="text-muted text-sm p-4 text-center">No local apps found</div>
159-
) : (
160-
<div
161-
className="grid gap-3"
162-
style={{
163-
gridTemplateColumns: `repeat(${gridSize}, minmax(0, 1fr))`,
164-
maxWidth: `${gridSize * 80}px`,
165-
}}
166-
>
167-
{apps.map((app) => {
168-
const appMeta = app.manifest?.appmeta;
169-
const displayName = app.appid.replace(/^local\//, "");
170-
const icon = appMeta?.icon || "cube";
171-
const iconColor = appMeta?.iconcolor || "white";
172-
173-
return (
174-
<div
175-
key={app.appid}
176-
className="flex flex-col items-center justify-center p-2 rounded hover:bg-hoverbg cursor-pointer transition-colors"
177-
onClick={() => {
178-
const blockDef: BlockDef = {
179-
meta: {
180-
view: "tsunami",
181-
controller: "tsunami",
182-
"tsunami:appid": app.appid,
183-
},
184-
};
185-
createBlock(blockDef);
186-
onClose();
187-
}}
188-
>
189-
<div style={{ color: iconColor }} className="text-3xl mb-1">
190-
<i className={makeIconClass(icon, false)}></i>
191-
</div>
192-
<div className="text-xxs text-center text-secondary break-words w-full px-1">
193-
{displayName}
157+
<div className="p-4">
158+
{loading ? (
159+
<div className="flex items-center justify-center p-8">
160+
<i className="fa fa-solid fa-spinner fa-spin text-2xl text-muted"></i>
161+
</div>
162+
) : apps.length === 0 ? (
163+
<div className="text-muted text-sm p-4 text-center">No local apps found</div>
164+
) : (
165+
<div
166+
className="grid gap-3"
167+
style={{
168+
gridTemplateColumns: `repeat(${gridSize}, minmax(0, 1fr))`,
169+
maxWidth: `${gridSize * 80}px`,
170+
}}
171+
>
172+
{apps.map((app) => {
173+
const appMeta = app.manifest?.appmeta;
174+
const displayName = app.appid.replace(/^local\//, "");
175+
const icon = appMeta?.icon || "cube";
176+
const iconColor = appMeta?.iconcolor || "white";
177+
178+
return (
179+
<div
180+
key={app.appid}
181+
className="flex flex-col items-center justify-center p-2 rounded hover:bg-hoverbg cursor-pointer transition-colors"
182+
onClick={() => {
183+
const blockDef: BlockDef = {
184+
meta: {
185+
view: "tsunami",
186+
controller: "tsunami",
187+
"tsunami:appid": app.appid,
188+
},
189+
};
190+
createBlock(blockDef);
191+
onClose();
192+
}}
193+
>
194+
<div style={{ color: iconColor }} className="text-3xl mb-1">
195+
<i className={makeIconClass(icon, false)}></i>
196+
</div>
197+
<div className="text-xxs text-center text-secondary break-words w-full px-1">
198+
{displayName}
199+
</div>
194200
</div>
195-
</div>
196-
);
197-
})}
198-
</div>
199-
)}
201+
);
202+
})}
203+
</div>
204+
)}
205+
</div>
206+
<button
207+
type="button"
208+
className="w-full px-4 py-2 border-t border-border text-xs text-secondary text-center hover:bg-hoverbg hover:text-white transition-colors cursor-pointer flex items-center justify-center gap-2"
209+
onClick={handleOpenBuilder}
210+
>
211+
<i className="fa fa-solid fa-hammer"></i>
212+
Build/Edit Apps
213+
</button>
200214
</div>
201215
</FloatingPortal>
202216
);

0 commit comments

Comments
 (0)