Skip to content

Commit df497ae

Browse files
committed
Add /fork slash command to duplicate current session into a new tab
1 parent 170d4d7 commit df497ae

File tree

5 files changed

+63
-1
lines changed

5 files changed

+63
-1
lines changed

pkg/session/branch.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ func BranchSession(parent *Session, branchAtPosition int) (*Session, error) {
1616
if parent == nil {
1717
return nil, errors.New("parent session is nil")
1818
}
19-
if branchAtPosition < 0 || branchAtPosition >= len(parent.Messages) {
19+
if branchAtPosition < 0 || branchAtPosition > len(parent.Messages) {
2020
return nil, fmt.Errorf("branch position %d out of range", branchAtPosition)
2121
}
2222

pkg/tui/commands/commands.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,17 @@ func builtInSessionCommands() []Item {
117117
return core.CmdHandler(messages.EvalSessionMsg{Filename: arg})
118118
},
119119
},
120+
{
121+
ID: "session.fork",
122+
Label: "Fork",
123+
SlashCommand: "/fork",
124+
Description: "Fork the current session into a new tab",
125+
Category: "Session",
126+
Immediate: true,
127+
Execute: func(string) tea.Cmd {
128+
return core.CmdHandler(messages.ForkSessionMsg{})
129+
},
130+
},
120131
{
121132
ID: "session.exit",
122133
Label: "Exit",

pkg/tui/handlers.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,51 @@ func (m *appModel) handleBranchFromEdit(msg messages.BranchFromEditMsg) (tea.Mod
9797
)
9898
}
9999

100+
func (m *appModel) handleForkSession() (tea.Model, tea.Cmd) {
101+
currentSession := m.application.Session()
102+
if currentSession == nil {
103+
return m, notification.ErrorCmd("No active session to fork")
104+
}
105+
106+
store := m.application.SessionStore()
107+
if store == nil {
108+
return m, notification.ErrorCmd("No session store configured")
109+
}
110+
111+
spawner := m.supervisor.Spawner()
112+
if spawner == nil {
113+
return m, notification.ErrorCmd("Session spawning not available")
114+
}
115+
116+
ctx := context.Background()
117+
118+
// Fork the session and clone all messages.
119+
forkedSession, err := session.BranchSession(currentSession, len(currentSession.Messages))
120+
if err != nil {
121+
return m, notification.ErrorCmd(fmt.Sprintf("Failed to fork session: %v", err))
122+
}
123+
124+
if err := store.AddSession(ctx, forkedSession); err != nil {
125+
return m, notification.ErrorCmd(fmt.Sprintf("Failed to save forked session: %v", err))
126+
}
127+
128+
a, _, cleanup, err := spawner(ctx, forkedSession.WorkingDir)
129+
if err != nil {
130+
return m, notification.ErrorCmd(fmt.Sprintf("Failed to create runtime for fork: %v", err))
131+
}
132+
133+
a.ReplaceSession(ctx, forkedSession)
134+
m.supervisor.AddSession(ctx, a, forkedSession, forkedSession.WorkingDir, cleanup)
135+
136+
if m.tuiStore != nil {
137+
if err := m.tuiStore.AddTab(ctx, forkedSession.ID, forkedSession.WorkingDir); err != nil {
138+
slog.Warn("Failed to persist forked tab", "error", err)
139+
}
140+
}
141+
142+
return m.handleSwitchTab(forkedSession.ID)
143+
}
144+
100145
func (m *appModel) handleToggleSessionStar(sessionID string) (tea.Model, tea.Cmd) {
101146
store := m.application.SessionStore()
102147
if store == nil {

pkg/tui/messages/session.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ type (
6868
// RegenerateTitleMsg regenerates the session title using the AI.
6969
RegenerateTitleMsg struct{}
7070

71+
// ForkSessionMsg requests forking the current session into a new tab.
72+
ForkSessionMsg struct{}
73+
7174
// StreamCancelledMsg notifies components that the stream has been cancelled.
7275
StreamCancelledMsg struct{ ShowMessage bool }
7376

pkg/tui/tui.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -855,6 +855,9 @@ func (m *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
855855
case messages.BranchFromEditMsg:
856856
return m.handleBranchFromEdit(msg)
857857

858+
case messages.ForkSessionMsg:
859+
return m.handleForkSession()
860+
858861
// --- Session commands (slash commands, command palette) ---
859862

860863
case messages.ToggleYoloMsg:

0 commit comments

Comments
 (0)