diff --git a/internal/tui/components/chat/editor/editor.go b/internal/tui/components/chat/editor/editor.go index 427e46304a91921fe0be82d4402bc5c3a9cedd51..a3f5fbe560e0d2da2b1754a4f31cc9a94ab7ca29 100644 --- a/internal/tui/components/chat/editor/editor.go +++ b/internal/tui/components/chat/editor/editor.go @@ -22,6 +22,7 @@ import ( "github.com/charmbracelet/crush/internal/tui/components/completions" "github.com/charmbracelet/crush/internal/tui/components/core/layout" "github.com/charmbracelet/crush/internal/tui/components/dialogs" + "github.com/charmbracelet/crush/internal/tui/components/dialogs/commands" "github.com/charmbracelet/crush/internal/tui/components/dialogs/filepicker" "github.com/charmbracelet/crush/internal/tui/components/dialogs/quit" "github.com/charmbracelet/crush/internal/tui/styles" @@ -38,6 +39,7 @@ type Editor interface { SetSession(session session.Session) tea.Cmd IsCompletionsOpen() bool + HasAttachments() bool Cursor() *tea.Cursor } @@ -206,6 +208,12 @@ func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.completionsStartIndex = 0 } } + + case commands.OpenExternalEditorMsg: + if m.app.CoderAgent.IsSessionBusy(m.session.ID) { + return m, util.ReportWarn("Agent is working, please wait...") + } + return m, m.openEditor(m.textarea.Value()) case OpenEditorMsg: m.textarea.SetValue(msg.Text) m.textarea.MoveToEnd() @@ -482,6 +490,10 @@ func (c *editorCmp) IsCompletionsOpen() bool { return c.isCompletionsOpen } +func (c *editorCmp) HasAttachments() bool { + return len(c.attachments) > 0 +} + func New(app *app.App) Editor { t := styles.CurrentTheme() ta := textarea.New() diff --git a/internal/tui/components/chat/editor/keys.go b/internal/tui/components/chat/editor/keys.go index 2f464833bd67b81cd105aeddeb69d2e950971bbe..9d2274753b4667031bb43a76f54fce18c1decf51 100644 --- a/internal/tui/components/chat/editor/keys.go +++ b/internal/tui/components/chat/editor/keys.go @@ -22,8 +22,8 @@ func DefaultEditorKeyMap() EditorKeyMap { key.WithHelp("enter", "send"), ), OpenEditor: key.NewBinding( - key.WithKeys("ctrl+v"), - key.WithHelp("ctrl+v", "open editor"), + key.WithKeys("ctrl+o"), + key.WithHelp("ctrl+o", "open editor"), ), Newline: key.NewBinding( key.WithKeys("shift+enter", "ctrl+j"), diff --git a/internal/tui/components/dialogs/commands/commands.go b/internal/tui/components/dialogs/commands/commands.go index 3319d0134888ad1e65656fb64cf4352ecd178229..bca75a2b22f3f31f4d3a0cadd47a427ef1ae84fc 100644 --- a/internal/tui/components/dialogs/commands/commands.go +++ b/internal/tui/components/dialogs/commands/commands.go @@ -1,6 +1,8 @@ package commands import ( + "os" + "github.com/charmbracelet/bubbles/v2/help" "github.com/charmbracelet/bubbles/v2/key" tea "github.com/charmbracelet/bubbletea/v2" @@ -66,6 +68,7 @@ type ( ToggleHelpMsg struct{} ToggleCompactModeMsg struct{} ToggleThinkingMsg struct{} + OpenExternalEditorMsg struct{} CompactMsg struct { SessionID string } @@ -343,6 +346,19 @@ func (c *commandDialogCmp) defaultCommands() []Command { } } + // Add external editor command if $EDITOR is available + if os.Getenv("EDITOR") != "" { + commands = append(commands, Command{ + ID: "open_external_editor", + Title: "Open External Editor", + Shortcut: "ctrl+o", + Description: "Open external editor to compose message", + Handler: func(cmd Command) tea.Cmd { + return util.CmdHandler(OpenExternalEditorMsg{}) + }, + }) + } + return append(commands, []Command{ { ID: "toggle_help", diff --git a/internal/tui/page/chat/chat.go b/internal/tui/page/chat/chat.go index 66060d047e07dc96d3ce7542ffdc63ab8fa80686..6b4469046966c9f9a31d6cc3b70adf3a2afa4f7a 100644 --- a/internal/tui/page/chat/chat.go +++ b/internal/tui/page/chat/chat.go @@ -204,6 +204,10 @@ func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return p, tea.Batch(p.SetSize(p.width, p.height), cmd) case commands.ToggleThinkingMsg: return p, p.toggleThinking() + case commands.OpenExternalEditorMsg: + u, cmd := p.editor.Update(msg) + p.editor = u.(editor.Editor) + return p, cmd case pubsub.Event[session.Session]: u, cmd := p.header.Update(msg) p.header = u.(header.Header) @@ -896,9 +900,13 @@ func (p *chatPage) Help() help.KeyMap { key.WithHelp("/", "add file"), ), key.NewBinding( - key.WithKeys("ctrl+v"), - key.WithHelp("ctrl+v", "open editor"), + key.WithKeys("ctrl+o"), + key.WithHelp("ctrl+o", "open editor"), ), + }) + + if p.editor.HasAttachments() { + fullList = append(fullList, []key.Binding{ key.NewBinding( key.WithKeys("ctrl+r"), key.WithHelp("ctrl+r+{i}", "delete attachment at index i"), @@ -912,6 +920,7 @@ func (p *chatPage) Help() help.KeyMap { key.WithHelp("esc", "cancel delete mode"), ), }) + } } shortList = append(shortList, // Quit diff --git a/internal/tui/tui.go b/internal/tui/tui.go index b96fe26c9f80d565fa58b9228edee89d013117da..b0ba10a4ddf5cf35a8cc163574279955750fd198 100644 --- a/internal/tui/tui.go +++ b/internal/tui/tui.go @@ -3,6 +3,7 @@ package tui import ( "context" "fmt" + "log/slog" "strings" "time" @@ -258,6 +259,8 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return a, tea.Batch(cmds...) // Key Press Messages case tea.KeyPressMsg: + + slog.Info("TUI Update", "msg", msg, "key", msg.String()) return a, a.handleKeyPressMsg(msg) case tea.MouseWheelMsg: