Skip to content

Commit ac9fba5

Browse files
authored
Merge pull request #1821 from xKevIsDev/main
feat: add terminal detachment
2 parents d0d9818 + 7ce263e commit ac9fba5

File tree

9 files changed

+81
-246
lines changed

9 files changed

+81
-246
lines changed

app/components/chat/BaseChat.tsx

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import ChatAlert from './ChatAlert';
2525
import type { ModelInfo } from '~/lib/modules/llm/types';
2626
import ProgressCompilation from './ProgressCompilation';
2727
import type { ProgressAnnotation } from '~/types/context';
28-
import type { ActionRunner } from '~/lib/runtime/action-runner';
2928
import { SupabaseChatAlert } from '~/components/chat/SupabaseAlert';
3029
import { expoUrlAtom } from '~/lib/stores/qrCodeStore';
3130
import { useStore } from '@nanostores/react';
@@ -71,7 +70,6 @@ interface BaseChatProps {
7170
deployAlert?: DeployAlert;
7271
clearDeployAlert?: () => void;
7372
data?: JSONValue[] | undefined;
74-
actionRunner?: ActionRunner;
7573
chatMode?: 'discuss' | 'build';
7674
setChatMode?: (mode: 'discuss' | 'build') => void;
7775
append?: (message: Message) => void;
@@ -116,7 +114,6 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
116114
supabaseAlert,
117115
clearSupabaseAlert,
118116
data,
119-
actionRunner,
120117
chatMode,
121118
setChatMode,
122119
append,
@@ -483,12 +480,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
483480
</div>
484481
<ClientOnly>
485482
{() => (
486-
<Workbench
487-
actionRunner={actionRunner ?? ({} as ActionRunner)}
488-
chatStarted={chatStarted}
489-
isStreaming={isStreaming}
490-
setSelectedElement={setSelectedElement}
491-
/>
483+
<Workbench chatStarted={chatStarted} isStreaming={isStreaming} setSelectedElement={setSelectedElement} />
492484
)}
493485
</ClientOnly>
494486
</div>

app/components/workbench/DiffView.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { diffLines, type Change } from 'diff';
77
import { getHighlighter } from 'shiki';
88
import '~/styles/diff-view.css';
99
import { diffFiles, extractRelativePath } from '~/utils/diff';
10-
import { ActionRunner } from '~/lib/runtime/action-runner';
1110
import type { FileHistory } from '~/types/actions';
1211
import { getLanguageFromExtension } from '~/utils/getLanguageFromExtension';
1312
import { themeStore } from '~/lib/stores/theme';
@@ -664,7 +663,6 @@ const InlineDiffComparison = memo(({ beforeCode, afterCode, filename, language }
664663
interface DiffViewProps {
665664
fileHistory: Record<string, FileHistory>;
666665
setFileHistory: React.Dispatch<React.SetStateAction<Record<string, FileHistory>>>;
667-
actionRunner: ActionRunner;
668666
}
669667

670668
export const DiffView = memo(({ fileHistory, setFileHistory }: DiffViewProps) => {

app/components/workbench/Workbench.client.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { memo, useCallback, useEffect, useState, useMemo } from 'react';
55
import { toast } from 'react-toastify';
66
import { Popover, Transition } from '@headlessui/react';
77
import { diffLines, type Change } from 'diff';
8-
import { ActionRunner } from '~/lib/runtime/action-runner';
98
import { getLanguageFromExtension } from '~/utils/getLanguageFromExtension';
109
import type { FileHistory } from '~/types/actions';
1110
import { DiffView } from './DiffView';
@@ -32,7 +31,6 @@ import type { ElementInfo } from './Inspector';
3231
interface WorkspaceProps {
3332
chatStarted?: boolean;
3433
isStreaming?: boolean;
35-
actionRunner: ActionRunner;
3634
metadata?: {
3735
gitUrl?: string;
3836
};
@@ -281,7 +279,7 @@ const FileModifiedDropdown = memo(
281279
);
282280

283281
export const Workbench = memo(
284-
({ chatStarted, isStreaming, actionRunner, metadata, updateChatMestaData, setSelectedElement }: WorkspaceProps) => {
282+
({ chatStarted, isStreaming, metadata, updateChatMestaData, setSelectedElement }: WorkspaceProps) => {
285283
renderLogger.trace('Workbench');
286284

287285
const [isSyncing, setIsSyncing] = useState(false);
@@ -486,7 +484,7 @@ export const Workbench = memo(
486484
initial={{ x: '100%' }}
487485
animate={{ x: selectedView === 'diff' ? '0%' : selectedView === 'code' ? '100%' : '-100%' }}
488486
>
489-
<DiffView fileHistory={fileHistory} setFileHistory={setFileHistory} actionRunner={actionRunner} />
487+
<DiffView fileHistory={fileHistory} setFileHistory={setFileHistory} />
490488
</View>
491489
<View initial={{ x: '100%' }} animate={{ x: selectedView === 'preview' ? '0%' : '100%' }}>
492490
<Preview setSelectedElement={setSelectedElement} />

app/components/workbench/terminal/Terminal.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const logger = createScopedLogger('Terminal');
1010

1111
export interface TerminalRef {
1212
reloadStyles: () => void;
13+
getTerminal: () => XTerm | undefined;
1314
}
1415

1516
export interface TerminalProps {
@@ -80,6 +81,9 @@ export const Terminal = memo(
8081
const terminal = terminalRef.current!;
8182
terminal.options.theme = getTerminalTheme(readonly ? { cursor: '#00000000' } : {});
8283
},
84+
getTerminal: () => {
85+
return terminalRef.current;
86+
},
8387
};
8488
}, []);
8589

app/components/workbench/terminal/TerminalTabs.tsx

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export const TerminalTabs = memo(() => {
2323
const terminalToggledByShortcut = useRef(false);
2424

2525
const [activeTerminal, setActiveTerminal] = useState(0);
26-
const [terminalCount, setTerminalCount] = useState(1);
26+
const [terminalCount, setTerminalCount] = useState(0);
2727

2828
const addTerminal = () => {
2929
if (terminalCount < MAX_TERMINALS) {
@@ -32,6 +32,48 @@ export const TerminalTabs = memo(() => {
3232
}
3333
};
3434

35+
const closeTerminal = (index: number) => {
36+
if (index === 0) {
37+
return;
38+
} // Can't close bolt terminal
39+
40+
const terminalRef = terminalRefs.current[index];
41+
42+
if (terminalRef?.getTerminal) {
43+
const terminal = terminalRef.getTerminal();
44+
45+
if (terminal) {
46+
workbenchStore.detachTerminal(terminal);
47+
}
48+
}
49+
50+
// Remove the terminal from refs
51+
terminalRefs.current.splice(index, 1);
52+
53+
// Adjust terminal count and active terminal
54+
setTerminalCount(terminalCount - 1);
55+
56+
if (activeTerminal === index) {
57+
setActiveTerminal(Math.max(0, index - 1));
58+
} else if (activeTerminal > index) {
59+
setActiveTerminal(activeTerminal - 1);
60+
}
61+
};
62+
63+
useEffect(() => {
64+
return () => {
65+
terminalRefs.current.forEach((ref, index) => {
66+
if (index > 0 && ref?.getTerminal) {
67+
const terminal = ref.getTerminal();
68+
69+
if (terminal) {
70+
workbenchStore.detachTerminal(terminal);
71+
}
72+
}
73+
});
74+
};
75+
}, []);
76+
3577
useEffect(() => {
3678
const { current: terminal } = terminalPanelRef;
3779

@@ -125,6 +167,15 @@ export const TerminalTabs = memo(() => {
125167
>
126168
<div className="i-ph:terminal-window-duotone text-lg" />
127169
Terminal {terminalCount > 1 && index}
170+
<button
171+
className="bg-transparent text-bolt-elements-textTertiary hover:text-bolt-elements-textPrimary hover:bg-transparent rounded"
172+
onClick={(e) => {
173+
e.stopPropagation();
174+
closeTerminal(index);
175+
}}
176+
>
177+
<div className="i-ph:x text-xs" />
178+
</button>
128179
</button>
129180
</React.Fragment>
130181
)}

app/lib/stores/terminal.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,19 @@ export class TerminalStore {
5050
process.resize({ cols, rows });
5151
}
5252
}
53+
54+
async detachTerminal(terminal: ITerminal) {
55+
const terminalIndex = this.#terminals.findIndex((t) => t.terminal === terminal);
56+
57+
if (terminalIndex !== -1) {
58+
const { process } = this.#terminals[terminalIndex];
59+
60+
try {
61+
process.kill();
62+
} catch (error) {
63+
console.warn('Failed to kill terminal process:', error);
64+
}
65+
this.#terminals.splice(terminalIndex, 1);
66+
}
67+
}
5368
}

app/lib/stores/workbench.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,10 @@ export class WorkbenchStore {
147147
this.#terminalStore.attachBoltTerminal(terminal);
148148
}
149149

150+
detachTerminal(terminal: ITerminal) {
151+
this.#terminalStore.detachTerminal(terminal);
152+
}
153+
150154
onTerminalResize(cols: number, rows: number) {
151155
this.#terminalStore.onTerminalResize(cols, rows);
152156
}

0 commit comments

Comments
 (0)