@@ -2,8 +2,10 @@ package chat
8+ "github.com/charmbracelet/bubbles/v2/help"
7 "github.com/charmbracelet/bubbles/v2/key"
9 "github.com/charmbracelet/bubbles/v2/key"
8 "github.com/charmbracelet/bubbles/v2/spinner"
10 "github.com/charmbracelet/bubbles/v2/spinner"
9 tea "github.com/charmbracelet/bubbletea/v2"
11 tea "github.com/charmbracelet/bubbletea/v2"
@@ -20,6 +22,7 @@ import (
20 "github.com/charmbracelet/crush/internal/tui/components/chat/sidebar"
22 "github.com/charmbracelet/crush/internal/tui/components/chat/sidebar"
21 "github.com/charmbracelet/crush/internal/tui/components/chat/splash"
23 "github.com/charmbracelet/crush/internal/tui/components/chat/splash"
22 "github.com/charmbracelet/crush/internal/tui/components/completions"
24 "github.com/charmbracelet/crush/internal/tui/components/completions"
25+ "github.com/charmbracelet/crush/internal/tui/components/core"
23 "github.com/charmbracelet/crush/internal/tui/components/core/layout"
26 "github.com/charmbracelet/crush/internal/tui/components/core/layout"
24 "github.com/charmbracelet/crush/internal/tui/components/dialogs/commands"
27 "github.com/charmbracelet/crush/internal/tui/components/dialogs/commands"
25 "github.com/charmbracelet/crush/internal/tui/components/dialogs/filepicker"
28 "github.com/charmbracelet/crush/internal/tui/components/dialogs/filepicker"
@@ -82,6 +85,7 @@ type chatPage struct {
82 width, height int
85 width, height int
83 detailsWidth, detailsHeight int
86 detailsWidth, detailsHeight int
84 app *app.App
87 app *app.App
88+ keyboardEnhancements tea.KeyboardEnhancementsMsg
86 // Layout state
90 // Layout state
87 compact bool
91 compact bool
@@ -103,6 +107,8 @@ type chatPage struct {
103 showingDetails bool
107 showingDetails bool
104 isCanceling bool
108 isCanceling bool
105 splashFullScreen bool
109 splashFullScreen bool
108func New(app *app.App) ChatPage {
114func New(app *app.App) ChatPage {
@@ -129,10 +135,12 @@ func (p *chatPage) Init() tea.Cmd {
129 if !config.HasInitialDataConfig() {
135 if !config.HasInitialDataConfig() {
130 // First-time setup: show model selection
136 // First-time setup: show model selection
131 p.splash.SetOnboarding(true)
137 p.splash.SetOnboarding(true)
138+ p.isOnboarding = true
132 p.splashFullScreen = true
139 p.splashFullScreen = true
133 } else if b, _ := config.ProjectNeedsInitialization(); b {
140 } else if b, _ := config.ProjectNeedsInitialization(); b {
134 // Project needs CRUSH.md initialization
141 // Project needs CRUSH.md initialization
135 p.splash.SetProjectInit(true)
142 p.splash.SetProjectInit(true)
143+ p.isProjectInit = true
136 p.splashFullScreen = true
144 p.splashFullScreen = true
138 // Ready to chat: focus editor, splash in background
146 // Ready to chat: focus editor, splash in background
@@ -153,9 +161,8 @@ func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
153 var cmds []tea.Cmd
161 var cmds []tea.Cmd
154 switch msg := msg.(type) {
162 switch msg := msg.(type) {
155 case tea.KeyboardEnhancementsMsg:
163 case tea.KeyboardEnhancementsMsg:
156− m, cmd := p.editor.Update(msg)
164+ p.keyboardEnhancements = msg
157− p.editor = m.(editor.Editor)
158− return p, cmd
165+ return p, nil
159 case tea.WindowSizeMsg:
166 case tea.WindowSizeMsg:
160 return p, p.SetSize(msg.Width, msg.Height)
167 return p, p.SetSize(msg.Width, msg.Height)
161 case CancelTimerExpiredMsg:
168 case CancelTimerExpiredMsg:
@@ -237,6 +244,8 @@ func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
237 if err != nil {
244 if err != nil {
238 return p, util.ReportError(err)
245 return p, util.ReportError(err)
247+ p.isOnboarding = false
248+ p.isProjectInit = false
240 p.focusedPane = PanelTypeEditor
249 p.focusedPane = PanelTypeEditor
241 return p, p.SetSize(p.width, p.height)
250 return p, p.SetSize(p.width, p.height)
242 case tea.KeyPressMsg:
251 case tea.KeyPressMsg:
@@ -579,6 +588,231 @@ func (p *chatPage) Bindings() []key.Binding {
579 return bindings
588 return bindings
591+func (a *chatPage) Help() help.KeyMap {
592+ var shortList []key.Binding
593+ var fullList [][]key.Binding
595+ case a.isOnboarding && !a.splash.IsShowingAPIKey():
596+ shortList = append(shortList,
599+ key.WithKeys("up", "down"),
600+ key.WithHelp("↑/↓", "choose"),
604+ key.WithKeys("enter", "ctrl+y"),
605+ key.WithHelp("enter", "accept"),
609+ key.WithKeys("ctrl+c"),
610+ key.WithHelp("ctrl+c", "quit"),
613+ // keep them the same
614+ for _, v := range shortList {
615+ fullList = append(fullList, []key.Binding{v})
617+ case a.isOnboarding && a.splash.IsShowingAPIKey():
618+ var pasteKey key.Binding
619+ if runtime.GOOS != "darwin" {
620+ pasteKey = key.NewBinding(
621+ key.WithKeys("ctrl+v"),
622+ key.WithHelp("ctrl+v", "paste API key"),
625+ pasteKey = key.NewBinding(
626+ key.WithKeys("cmd+v"),
627+ key.WithHelp("cmd+v", "paste API key"),
630+ shortList = append(shortList,
633+ key.WithKeys("esc"),
634+ key.WithHelp("esc", "back"),
640+ key.WithKeys("ctrl+c"),
641+ key.WithHelp("ctrl+c", "quit"),
644+ // keep them the same
645+ for _, v := range shortList {
646+ fullList = append(fullList, []key.Binding{v})
648+ case a.isProjectInit:
649+ shortList = append(shortList,
651+ key.WithKeys("ctrl+c"),
652+ key.WithHelp("ctrl+c", "quit"),
655+ // keep them the same
656+ for _, v := range shortList {
657+ fullList = append(fullList, []key.Binding{v})
660+ if a.app.CoderAgent != nil && a.app.CoderAgent.IsBusy() {
661+ cancelBinding := key.NewBinding(
662+ key.WithKeys("esc"),
663+ key.WithHelp("esc", "cancel"),
666+ cancelBinding = key.NewBinding(
667+ key.WithKeys("esc"),
668+ key.WithHelp("esc", "press again to cancel"),
671+ shortList = append(shortList, cancelBinding)
672+ fullList = append(fullList,
678+ globalBindings := []key.Binding{}
679+ // we are in a session
680+ if a.session.ID != "" {
681+ tabKey := key.NewBinding(
682+ key.WithKeys("tab"),
683+ key.WithHelp("tab", "focus chat"),
685+ if a.focusedPane == PanelTypeChat {
686+ tabKey = key.NewBinding(
687+ key.WithKeys("tab"),
688+ key.WithHelp("tab", "focus editor"),
691+ shortList = append(shortList, tabKey)
692+ globalBindings = append(globalBindings, tabKey)
694+ commandsBinding := key.NewBinding(
695+ key.WithKeys("ctrl+p"),
696+ key.WithHelp("ctrl+p", "commands"),
698+ helpBinding := key.NewBinding(
699+ key.WithKeys("ctrl+g"),
700+ key.WithHelp("ctrl+g", "more"),
702+ globalBindings = append(globalBindings, commandsBinding)
703+ globalBindings = append(globalBindings,
705+ key.WithKeys("ctrl+s"),
706+ key.WithHelp("ctrl+s", "sessions"),
709+ if a.session.ID != "" {
710+ globalBindings = append(globalBindings,
712+ key.WithKeys("ctrl+n"),
713+ key.WithHelp("ctrl+n", "new sessions"),
716+ shortList = append(shortList,
720+ fullList = append(fullList, globalBindings)
722+ if a.focusedPane == PanelTypeChat {
723+ shortList = append(shortList,
725+ key.WithKeys("up", "down"),
726+ key.WithHelp("↑↓", "scroll"),
729+ fullList = append(fullList,
732+ key.WithKeys("up", "down"),
733+ key.WithHelp("↑↓", "scroll"),
736+ key.WithKeys("shift+up", "shift+down"),
737+ key.WithHelp("shift+↑↓", "next/prev item"),
740+ key.WithKeys("pgup", "b"),
741+ key.WithHelp("b/pgup", "page up"),
744+ key.WithKeys("pgdown", " ", "f"),
745+ key.WithHelp("f/pgdn", "page down"),
751+ key.WithHelp("u", "half page up"),
755+ key.WithHelp("d", "half page down"),
758+ key.WithKeys("g", "home"),
759+ key.WithHelp("g", "hone"),
762+ key.WithKeys("G", "end"),
763+ key.WithHelp("G", "end"),
767+ } else if a.focusedPane == PanelTypeEditor {
768+ newLineBinding := key.NewBinding(
769+ key.WithKeys("shift+enter", "ctrl+j"),
770+ // "ctrl+j" is a common keybinding for newline in many editors. If
771+ // the terminal supports "shift+enter", we substitute the help text
773+ key.WithHelp("ctrl+j", "newline"),
775+ if a.keyboardEnhancements.SupportsKeyDisambiguation() {
776+ newLineBinding.SetHelp("shift+enter", newLineBinding.Help().Desc)
778+ shortList = append(shortList, newLineBinding)
779+ fullList = append(fullList,
783+ key.WithKeys("ctrl+f"),
784+ key.WithHelp("ctrl+f", "add image"),
788+ key.WithHelp("/", "add file"),
791+ key.WithKeys("ctrl+e"),
792+ key.WithHelp("ctrl+e", "open editor"),
796+ shortList = append(shortList,
799+ key.WithKeys("ctrl+c"),
800+ key.WithHelp("ctrl+c", "quit"),
805+ fullList = append(fullList, []key.Binding{
807+ key.WithKeys("ctrl+g"),
808+ key.WithHelp("ctrl+g", "less"),
813+ return core.NewSimpleHelp(shortList, fullList)
582func (p *chatPage) IsChatFocused() bool {
816func (p *chatPage) IsChatFocused() bool {
583 return p.focusedPane == PanelTypeChat
817 return p.focusedPane == PanelTypeChat