on main help sign in
feat(pills): add toggle todos/pills menu item (#2202)
8ccb3c7Christian Rocha
internal/session/session.go+10
@@ -28,6 +28,16 @@ type Todo struct {
28 ActiveForm string `json:"active_form"`
28 ActiveForm string `json:"active_form"`
29}
29}
30
30
31+// HasIncompleteTodos returns true if there are any non-completed todos.
32+func HasIncompleteTodos(todos []Todo) bool {
33+ for _, todo := range todos {
34+ if todo.Status != TodoStatusCompleted {
35+ return true
36+ }
37+ }
38+ return false
39+}
40+
31type Session struct {
41type Session struct {
32 ID string
42 ID string
33 ParentSessionID string
43 ParentSessionID string
internal/ui/dialog/actions.go+1
@@ -48,6 +48,7 @@ type (
48 ActionToggleHelp struct{}
48 ActionToggleHelp struct{}
49 ActionToggleCompactMode struct{}
49 ActionToggleCompactMode struct{}
50 ActionToggleThinking struct{}
50 ActionToggleThinking struct{}
51+ ActionTogglePills struct{}
51 ActionExternalEditor struct{}
52 ActionExternalEditor struct{}
52 ActionToggleYoloMode struct{}
53 ActionToggleYoloMode struct{}
53 // ActionInitializeProject is a message to initialize a project.
54 // ActionInitializeProject is a message to initialize a project.
internal/ui/dialog/commands.go+28−7
@@ -49,8 +49,11 @@ type Commands struct {
49 Close key.Binding
49 Close key.Binding
50 }
50 }
51
51
52− sessionID string // can be empty for non-session-specific commands
52+ sessionID string
53+ hasSession bool
54+ hasTodos bool
55+ hasQueue bool
53− selected CommandType
56+ selected CommandType
54
57
55 spinner spinner.Model
58 spinner spinner.Model
56 loading bool
59 loading bool
@@ -68,11 +71,14 @@ type Commands struct {
68var _ Dialog = (*Commands)(nil)
71var _ Dialog = (*Commands)(nil)
69
72
70// NewCommands creates a new commands dialog.
73// NewCommands creates a new commands dialog.
71−func NewCommands(com *common.Common, sessionID string, customCommands []commands.CustomCommand, mcpPrompts []commands.MCPPrompt) (*Commands, error) {
74+func NewCommands(com *common.Common, sessionID string, hasSession, hasTodos, hasQueue bool, customCommands []commands.CustomCommand, mcpPrompts []commands.MCPPrompt) (*Commands, error) {
72 c := &Commands{
75 c := &Commands{
73 com: com,
76 com: com,
74 selected: SystemCommands,
77 selected: SystemCommands,
75 sessionID: sessionID,
78 sessionID: sessionID,
79+ hasSession: hasSession,
80+ hasTodos: hasTodos,
81+ hasQueue: hasQueue,
76 customCommands: customCommands,
82 customCommands: customCommands,
77 mcpPrompts: mcpPrompts,
83 mcpPrompts: mcpPrompts,
78 }
84 }
@@ -387,7 +393,7 @@ func (c *Commands) defaultCommands() []*CommandItem {
387 }
393 }
388
394
389 // Only show compact command if there's an active session
395 // Only show compact command if there's an active session
390− if c.sessionID != "" {
396+ if c.hasSession {
391 commands = append(commands, NewCommandItem(c.com.Styles, "summarize", "Summarize Session", "", ActionSummarize{SessionID: c.sessionID}))
397 commands = append(commands, NewCommandItem(c.com.Styles, "summarize", "Summarize Session", "", ActionSummarize{SessionID: c.sessionID}))
392 }
398 }
393
399
@@ -417,10 +423,10 @@ func (c *Commands) defaultCommands() []*CommandItem {
417 }
423 }
418 }
424 }
419 // Only show toggle compact mode command if window width is larger than compact breakpoint (120)
425 // Only show toggle compact mode command if window width is larger than compact breakpoint (120)
420− if c.windowWidth >= sidebarCompactModeBreakpoint && c.sessionID != "" {
426+ if c.windowWidth >= sidebarCompactModeBreakpoint && c.hasSession {
421 commands = append(commands, NewCommandItem(c.com.Styles, "toggle_sidebar", "Toggle Sidebar", "", ActionToggleCompactMode{}))
427 commands = append(commands, NewCommandItem(c.com.Styles, "toggle_sidebar", "Toggle Sidebar", "", ActionToggleCompactMode{}))
422 }
428 }
423− if c.sessionID != "" {
429+ if c.hasSession {
424 cfg := c.com.Config()
430 cfg := c.com.Config()
425 agentCfg := cfg.Agents[config.AgentCoder]
431 agentCfg := cfg.Agents[config.AgentCoder]
426 model := cfg.GetModelByType(agentCfg.Model)
432 model := cfg.GetModelByType(agentCfg.Model)
@@ -437,12 +443,27 @@ func (c *Commands) defaultCommands() []*CommandItem {
437 commands = append(commands, NewCommandItem(c.com.Styles, "open_external_editor", "Open External Editor", "ctrl+o", ActionExternalEditor{}))
443 commands = append(commands, NewCommandItem(c.com.Styles, "open_external_editor", "Open External Editor", "ctrl+o", ActionExternalEditor{}))
438 }
444 }
439
445
446+ if c.hasTodos || c.hasQueue {
447+ var label string
448+ switch {
449+ case c.hasTodos && c.hasQueue:
450+ label = "Toggle To-Dos/Queue"
451+ case c.hasQueue:
452+ label = "Toggle Queue"
453+ default:
454+ label = "Toggle To-Dos"
455+ }
456+ commands = append(commands, NewCommandItem(c.com.Styles, "toggle_pills", label, "ctrl+t", ActionTogglePills{}))
457+ }
458+
440− return append(commands,
459+ commands = append(commands,
441 NewCommandItem(c.com.Styles, "toggle_yolo", "Toggle Yolo Mode", "", ActionToggleYoloMode{}),
460 NewCommandItem(c.com.Styles, "toggle_yolo", "Toggle Yolo Mode", "", ActionToggleYoloMode{}),
442 NewCommandItem(c.com.Styles, "toggle_help", "Toggle Help", "ctrl+g", ActionToggleHelp{}),
461 NewCommandItem(c.com.Styles, "toggle_help", "Toggle Help", "ctrl+g", ActionToggleHelp{}),
443 NewCommandItem(c.com.Styles, "init", "Initialize Project", "", ActionInitializeProject{}),
462 NewCommandItem(c.com.Styles, "init", "Initialize Project", "", ActionInitializeProject{}),
444 NewCommandItem(c.com.Styles, "quit", "Quit", "ctrl+c", tea.QuitMsg{}),
463 NewCommandItem(c.com.Styles, "quit", "Quit", "ctrl+c", tea.QuitMsg{}),
445 )
464 )
465+
466+ return commands
446}
467}
447
468
448// SetCustomCommands sets the custom commands and refreshes the view if user commands are currently displayed.
469// SetCustomCommands sets the custom commands and refreshes the view if user commands are currently displayed.
internal/ui/model/pills.go+1−6
@@ -38,12 +38,7 @@ const (
38
38
39// hasIncompleteTodos returns true if there are any non-completed todos.
39// hasIncompleteTodos returns true if there are any non-completed todos.
40func hasIncompleteTodos(todos []session.Todo) bool {
40func hasIncompleteTodos(todos []session.Todo) bool {
41− for _, todo := range todos {
41+ return session.HasIncompleteTodos(todos)
42− if todo.Status != session.TodoStatusCompleted {
43− return true
44− }
45− }
46− return false
47}
42}
48
43
49// hasInProgressTodo returns true if there is at least one in-progress todo.
44// hasInProgressTodo returns true if there is at least one in-progress todo.
internal/ui/model/ui.go+11−3
@@ -1213,6 +1213,11 @@ func (m *UI) handleDialogMsg(msg tea.Msg) tea.Cmd {
1213 case dialog.ActionToggleCompactMode:
1213 case dialog.ActionToggleCompactMode:
1214 cmds = append(cmds, m.toggleCompactMode())
1214 cmds = append(cmds, m.toggleCompactMode())
1215 m.dialog.CloseDialog(dialog.CommandsID)
1215 m.dialog.CloseDialog(dialog.CommandsID)
1216+ case dialog.ActionTogglePills:
1217+ if cmd := m.togglePillsExpanded(); cmd != nil {
1218+ cmds = append(cmds, cmd)
1219+ }
1220+ m.dialog.CloseDialog(dialog.CommandsID)
1216 case dialog.ActionToggleThinking:
1221 case dialog.ActionToggleThinking:
1217 cmds = append(cmds, func() tea.Msg {
1222 cmds = append(cmds, func() tea.Msg {
1218 cfg := m.com.Config()
1223 cfg := m.com.Config()
@@ -2858,12 +2863,15 @@ func (m *UI) openCommandsDialog() tea.Cmd {
2858 return nil
2863 return nil
2859 }
2864 }
2860
2865
2861− sessionID := ""
2866+ var sessionID string
2862− if m.session != nil {
2867+ hasSession := m.session != nil
2868+ if hasSession {
2863 sessionID = m.session.ID
2869 sessionID = m.session.ID
2864 }
2870 }
2871+ hasTodos := hasSession && hasIncompleteTodos(m.session.Todos)
2872+ hasQueue := m.promptQueue > 0
2865
2873
2866− commands, err := dialog.NewCommands(m.com, sessionID, m.customCommands, m.mcpPrompts)
2874+ commands, err := dialog.NewCommands(m.com, sessionID, hasSession, hasTodos, hasQueue, m.customCommands, m.mcpPrompts)
2867 if err != nil {
2875 if err != nil {
2868 return util.ReportError(err)
2876 return util.ReportError(err)
2869 }
2877 }