Skip to content

Commit 8e67db0

Browse files
committed
terminal process naming, bug fix
1 parent fcb7111 commit 8e67db0

File tree

7 files changed

+138
-9
lines changed

7 files changed

+138
-9
lines changed

apps/array/src/main/preload.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,8 @@ contextBridge.exposeInMainWorld("electronAPI", {
311311
ipcRenderer.invoke("shell:check", sessionId),
312312
shellDestroy: (sessionId: string): Promise<void> =>
313313
ipcRenderer.invoke("shell:destroy", sessionId),
314+
shellGetProcess: (sessionId: string): Promise<string | null> =>
315+
ipcRenderer.invoke("shell:get-process", sessionId),
314316
onShellData: (
315317
sessionId: string,
316318
listener: (data: string) => void,

apps/array/src/main/services/shell.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,4 +163,19 @@ export function registerShellIpc(): void {
163163
sessions.delete(sessionId);
164164
},
165165
);
166+
167+
// Get foreground process name
168+
ipcMain.handle(
169+
"shell:get-process",
170+
async (
171+
_event: IpcMainInvokeEvent,
172+
sessionId: string,
173+
): Promise<string | null> => {
174+
const session = sessions.get(sessionId);
175+
if (!session) {
176+
return null;
177+
}
178+
return session.pty.process;
179+
},
180+
);
166181
}

apps/array/src/renderer/components/ui/sidebar/SidebarTreeItem.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,10 @@ export function SidebarTreeItem({
7171
)}
7272
{line.tooltip ? (
7373
<Tooltip content={line.tooltip}>
74-
<span style={{ color: line.customColor, fontSize: "13px" }}>
75-
{line.label}
76-
</span>
74+
<span style={{ color: line.customColor }}>{line.label}</span>
7775
</Tooltip>
7876
) : (
79-
<span style={{ color: line.customColor, fontSize: "13px" }}>
80-
{line.label}
81-
</span>
77+
<span style={{ color: line.customColor }}>{line.label}</span>
8278
)}
8379
{line.hasChildren && (
8480
<span style={{ display: "flex", alignItems: "center" }}>

apps/array/src/renderer/features/panels/store/panelLayoutStore.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ export interface PanelLayoutStore {
8484
tabId: string,
8585
metadata: Partial<Pick<Tab, "hasUnsavedChanges">>,
8686
) => void;
87+
updateTabLabel: (taskId: string, tabId: string, label: string) => void;
8788
setFocusedPanel: (taskId: string, panelId: string) => void;
8889
addTerminalTab: (taskId: string, panelId: string) => void;
8990
clearAllLayouts: () => void;
@@ -111,7 +112,7 @@ function createDefaultPanelTree(): PanelNode {
111112
},
112113
{
113114
id: DEFAULT_TAB_IDS.SHELL,
114-
label: "Shell",
115+
label: "Terminal",
115116
component: null,
116117
closeable: true,
117118
draggable: true,
@@ -375,6 +376,7 @@ export const usePanelLayoutStore = createWithEqualityFn<PanelLayoutStore>()(
375376
content: {
376377
...panel.content,
377378
tabs: remainingTabs,
379+
activeTabId: tabId,
378380
},
379381
};
380382
},
@@ -615,6 +617,37 @@ export const usePanelLayoutStore = createWithEqualityFn<PanelLayoutStore>()(
615617
);
616618
},
617619

620+
updateTabLabel: (taskId, tabId, label) => {
621+
set((state) =>
622+
updateTaskLayout(state, taskId, (layout) => {
623+
const tabLocation = findTabInTree(layout.panelTree, tabId);
624+
if (!tabLocation) return {};
625+
626+
const updatedTree = updateTreeNode(
627+
layout.panelTree,
628+
tabLocation.panelId,
629+
(panel) => {
630+
if (panel.type !== "leaf") return panel;
631+
632+
const updatedTabs = panel.content.tabs.map((tab) =>
633+
tab.id === tabId ? { ...tab, label } : tab,
634+
);
635+
636+
return {
637+
...panel,
638+
content: {
639+
...panel.content,
640+
tabs: updatedTabs,
641+
},
642+
};
643+
},
644+
);
645+
646+
return { panelTree: updatedTree };
647+
}),
648+
);
649+
},
650+
618651
setFocusedPanel: (taskId, panelId) => {
619652
set((state) =>
620653
updateTaskLayout(state, taskId, () => ({
@@ -634,7 +667,7 @@ export const usePanelLayoutStore = createWithEqualityFn<PanelLayoutStore>()(
634667
if (panel.type !== "leaf") return panel;
635668
return addTabToPanel(panel, {
636669
id: tabId,
637-
label: "Shell",
670+
label: "Terminal",
638671
component: null,
639672
draggable: true,
640673
closeable: true,

apps/array/src/renderer/features/task-detail/components/TaskShellPanel.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import { usePanelLayoutStore } from "@features/panels/store/panelLayoutStore";
12
import { useTaskData } from "@features/task-detail/hooks/useTaskData";
23
import { ShellTerminal } from "@features/terminal/components/ShellTerminal";
4+
import { useTerminalStore } from "@features/terminal/stores/terminalStore";
35
import { Box } from "@radix-ui/themes";
46
import type { Task } from "@shared/types";
7+
import { useEffect } from "react";
58

69
interface TaskShellPanelProps {
710
taskId: string;
@@ -12,6 +15,25 @@ interface TaskShellPanelProps {
1215
export function TaskShellPanel({ taskId, task, shellId }: TaskShellPanelProps) {
1316
const taskData = useTaskData({ taskId, initialTask: task });
1417
const stateKey = shellId ? `${taskId}-${shellId}` : taskId;
18+
const tabId = shellId || "shell";
19+
20+
const processName = useTerminalStore(
21+
(state) => state.terminalStates[stateKey]?.processName,
22+
);
23+
const startPolling = useTerminalStore((state) => state.startPolling);
24+
const stopPolling = useTerminalStore((state) => state.stopPolling);
25+
const updateTabLabel = usePanelLayoutStore((state) => state.updateTabLabel);
26+
27+
useEffect(() => {
28+
startPolling(stateKey);
29+
return () => stopPolling(stateKey);
30+
}, [stateKey, startPolling, stopPolling]);
31+
32+
useEffect(() => {
33+
if (processName) {
34+
updateTabLabel(taskId, tabId, processName);
35+
}
36+
}, [processName, taskId, tabId, updateTabLabel]);
1537

1638
return (
1739
<Box height="100%">

apps/array/src/renderer/features/terminal/stores/terminalStore.ts

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,32 @@ import { persist } from "zustand/middleware";
44
interface TerminalState {
55
serializedState: string | null;
66
sessionId: string | null;
7+
processName: string | null;
78
}
89

910
interface TerminalStoreState {
1011
terminalStates: Record<string, TerminalState>;
12+
pollingIntervals: Record<string, number>;
1113
getTerminalState: (key: string) => TerminalState | undefined;
1214
setSerializedState: (key: string, state: string) => void;
1315
setSessionId: (key: string, sessionId: string) => void;
16+
setProcessName: (key: string, processName: string | null) => void;
1417
clearTerminalState: (key: string) => void;
18+
startPolling: (key: string) => void;
19+
stopPolling: (key: string) => void;
1520
}
1621

1722
const DEFAULT_TERMINAL_STATE: TerminalState = {
1823
serializedState: null,
1924
sessionId: null,
25+
processName: null,
2026
};
2127

2228
export const useTerminalStore = create<TerminalStoreState>()(
2329
persist(
2430
(set, get) => ({
2531
terminalStates: {},
32+
pollingIntervals: {},
2633

2734
getTerminalState: (key: string) => {
2835
return get().terminalStates[key] || DEFAULT_TERMINAL_STATE;
@@ -52,18 +59,71 @@ export const useTerminalStore = create<TerminalStoreState>()(
5259
}));
5360
},
5461

62+
setProcessName: (key: string, processName: string | null) => {
63+
set((prev) => ({
64+
terminalStates: {
65+
...prev.terminalStates,
66+
[key]: {
67+
...prev.terminalStates[key],
68+
processName,
69+
},
70+
},
71+
}));
72+
},
73+
5574
clearTerminalState: (key: string) => {
5675
set((prev) => {
5776
const newStates = { ...prev.terminalStates };
5877
delete newStates[key];
5978
return { terminalStates: newStates };
6079
});
6180
},
81+
82+
startPolling: (key: string) => {
83+
const { pollingIntervals } = get();
84+
if (pollingIntervals[key]) return;
85+
86+
const poll = async () => {
87+
const state = get().terminalStates[key];
88+
if (!state?.sessionId) return;
89+
90+
const processName = await window.electronAPI?.shellGetProcess(
91+
state.sessionId,
92+
);
93+
if (processName !== state.processName) {
94+
get().setProcessName(key, processName ?? null);
95+
}
96+
};
97+
98+
poll();
99+
const interval = window.setInterval(poll, 500);
100+
set((prev) => ({
101+
pollingIntervals: { ...prev.pollingIntervals, [key]: interval },
102+
}));
103+
},
104+
105+
stopPolling: (key: string) => {
106+
const { pollingIntervals } = get();
107+
const interval = pollingIntervals[key];
108+
if (interval) {
109+
clearInterval(interval);
110+
set((prev) => {
111+
const newIntervals = { ...prev.pollingIntervals };
112+
delete newIntervals[key];
113+
return { pollingIntervals: newIntervals };
114+
});
115+
}
116+
},
62117
}),
63118
{
64119
name: "terminal-store",
65120
partialize: (state) => ({
66-
terminalStates: state.terminalStates,
121+
terminalStates: Object.fromEntries(
122+
Object.entries(state.terminalStates).map(([k, v]) => [
123+
k,
124+
{ serializedState: v.serializedState, sessionId: v.sessionId },
125+
]),
126+
),
67127
}),
68128
},
69129
),

apps/array/src/renderer/types/electron.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ declare global {
198198
) => Promise<void>;
199199
shellCheck: (sessionId: string) => Promise<boolean>;
200200
shellDestroy: (sessionId: string) => Promise<void>;
201+
shellGetProcess: (sessionId: string) => Promise<string | null>;
201202
onShellData: (
202203
sessionId: string,
203204
listener: (data: string) => void,

0 commit comments

Comments
 (0)