Allow opening the editor while creating a ticket with Ctrl+e.

Draft headline from the create input is passed into $EDITOR so title and description can be written before the ticket is saved.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Skruecken
2026-10-19 10:35:34 +02:00
co-authored by Cursor
parent 28cd2628e4
commit 6527d91b4f
3 changed files with 66 additions and 16 deletions
+44 -3
View File
@@ -21,9 +21,10 @@ import (
// editorFinishedMsg is sent after the external $EDITOR exits. // editorFinishedMsg is sent after the external $EDITOR exits.
type editorFinishedMsg struct { type editorFinishedMsg struct {
ticketID string ticketID string // empty when create==true
path string path string
err error err error
create bool // true: create ticket from edited file
} }
// Mode is the active UI mode. // Mode is the active UI mode.
@@ -374,6 +375,20 @@ func (m Model) updateInput(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
} }
return m, nil return m, nil
case key.Matches(msg, m.keys.EditInEditor):
if m.mode != ModeCreate || m.creatingProject {
break
}
m.clearStatus()
cmd, err := m.openEditorForNewTicket()
if err != nil {
m.setError(err)
return m, nil
}
m.input.Blur()
m.mode = ModeList
return m, cmd
case key.Matches(msg, m.keys.Enter): case key.Matches(msg, m.keys.Enter):
value := strings.TrimSpace(m.input.Value()) value := strings.TrimSpace(m.input.Value())
if m.mode == ModeCreate { if m.mode == ModeCreate {
@@ -397,7 +412,7 @@ func (m Model) updateInput(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
m.setStatus(fmt.Sprintf("created project %s", id)) m.setStatus(fmt.Sprintf("created project %s", id))
return m, nil return m, nil
} }
if err := m.createTicket(value); err != nil { if err := m.createTicket(value, ""); err != nil {
m.setError(err) m.setError(err)
return m, nil return m, nil
} }
@@ -474,6 +489,21 @@ func (m Model) openEditorForSelected() (tea.Cmd, error) {
}), nil }), nil
} }
func (m Model) openEditorForNewTicket() (tea.Cmd, error) {
title := strings.TrimSpace(m.input.Value())
if title == "" {
title = "New ticket"
}
path, err := ticket.WriteEditFile(ticket.Ticket{Title: title})
if err != nil {
return nil, err
}
c := ticket.EditorCmd(path)
return tea.ExecProcess(c, func(err error) tea.Msg {
return editorFinishedMsg{path: path, err: err, create: true}
}), nil
}
func (m Model) handleEditorFinished(msg editorFinishedMsg) (tea.Model, tea.Cmd) { func (m Model) handleEditorFinished(msg editorFinishedMsg) (tea.Model, tea.Cmd) {
defer func() { _ = os.Remove(msg.path) }() defer func() { _ = os.Remove(msg.path) }()
@@ -493,6 +523,16 @@ func (m Model) handleEditorFinished(msg editorFinishedMsg) (tea.Model, tea.Cmd)
return m, nil return m, nil
} }
if msg.create {
if err := m.createTicket(title, description); err != nil {
m.setError(err)
return m, nil
}
m.mode = ModeList
m.setStatus("created")
return m, nil
}
before, err := m.store.Get(msg.ticketID) before, err := m.store.Get(msg.ticketID)
if err != nil { if err != nil {
m.setError(err) m.setError(err)
@@ -521,11 +561,12 @@ func (m Model) handleEditorFinished(msg editorFinishedMsg) (tea.Model, tea.Cmd)
return m, nil return m, nil
} }
func (m *Model) createTicket(title string) error { func (m *Model) createTicket(title, description string) error {
cmd := &command.CreateTicket{ cmd := &command.CreateTicket{
Store: m.store, Store: m.store,
Ticket: ticket.Ticket{ Ticket: ticket.Ticket{
Title: title, Title: title,
Description: description,
ProjectID: m.cfg.ActiveProject, ProjectID: m.cfg.ActiveProject,
Status: m.boardList.DefaultStatus(), Status: m.boardList.DefaultStatus(),
}, },
+5 -1
View File
@@ -14,10 +14,14 @@ type helpEntry struct {
func (m Model) helpEntries() []helpEntry { func (m Model) helpEntries() []helpEntry {
switch m.mode { switch m.mode {
case ModeCreate: case ModeCreate:
return []helpEntry{ entries := []helpEntry{
{"enter", "save"}, {"enter", "save"},
{"esc", "cancel"}, {"esc", "cancel"},
} }
if !m.creatingProject {
entries = append([]helpEntry{{"ctrl+e", "editor"}}, entries...)
}
return entries
case ModeSearch: case ModeSearch:
return []helpEntry{ return []helpEntry{
{"enter", "apply"}, {"enter", "apply"},
+5
View File
@@ -12,6 +12,7 @@ type KeyMap struct {
Board key.Binding Board key.Binding
Status key.Binding Status key.Binding
Edit key.Binding Edit key.Binding
EditInEditor key.Binding // ctrl+e — open $EDITOR while creating
Delete key.Binding // x — confirm first Delete key.Binding // x — confirm first
DeleteForce key.Binding // X — immediate DeleteForce key.Binding // X — immediate
ConfirmYes key.Binding ConfirmYes key.Binding
@@ -57,6 +58,10 @@ func DefaultKeyMap() KeyMap {
key.WithKeys("e"), key.WithKeys("e"),
key.WithHelp("e", "edit"), key.WithHelp("e", "edit"),
), ),
EditInEditor: key.NewBinding(
key.WithKeys("ctrl+e"),
key.WithHelp("ctrl+e", "editor"),
),
Delete: key.NewBinding( Delete: key.NewBinding(
key.WithKeys("x"), key.WithKeys("x"),
key.WithHelp("x", "delete"), key.WithHelp("x", "delete"),