Skip to content

Commit 1cc2ace

Browse files
committed
Re-enable mouse tracking after the external editor closes
Opening the description in $EDITOR with ^e and then quitting left the mouse unresponsive: clicks and wheel scrolling stopped working while keyboard navigation still did. The editor is launched with tea.ExecProcess, which releases the terminal before running the command and calls Bubble Tea's RestoreTerminal when it returns. RestoreTerminal re-enables the alt-screen, bracketed paste, and focus reporting, but it does not re-enable mouse tracking. The editor (e.g. vim) disables mouse reporting on exit, so once control returns to the TUI the terminal no longer emits mouse events. Re-arm mouse mode when the editor-finished message arrives by batching tea.EnableMouseCellMotion with the handler's command. That re-enables cell-motion and SGR mouse reporting, matching the WithMouseCellMotion option the program starts with, on every editor-return path (success or error).
1 parent e7d9173 commit 1cc2ace

2 files changed

Lines changed: 57 additions & 1 deletion

File tree

‎internal/tui/submitview/model.go‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,13 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
214214
return m.handleMouse(msg)
215215

216216
case editorFinishedMsg:
217-
return m.handleEditorFinished(msg)
217+
updated, cmd := m.handleEditorFinished(msg)
218+
// After tea.ExecProcess runs the external editor, Bubble Tea's
219+
// RestoreTerminal re-enables the alt-screen, bracketed paste, and focus
220+
// reporting but NOT mouse tracking, so the terminal stops emitting mouse
221+
// events once the editor closes. Re-enable cell-motion mouse mode (which
222+
// also re-arms SGR mode) to match the program's startup options.
223+
return updated, tea.Batch(cmd, tea.EnableMouseCellMotion)
218224
}
219225

220226
return m, nil

‎internal/tui/submitview/preview_test.go‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package submitview
22

33
import (
44
"os"
5+
"reflect"
56
"testing"
67

78
tea "github.com/charmbracelet/bubbletea"
@@ -66,6 +67,55 @@ func TestHandleEditorFinished_UpdatesDescription(t *testing.T) {
6667
assert.True(t, os.IsNotExist(statErr))
6768
}
6869

70+
// cmdMessages flattens the message(s) a command (possibly a tea.Batch) emits.
71+
func cmdMessages(cmd tea.Cmd) []tea.Msg {
72+
if cmd == nil {
73+
return nil
74+
}
75+
switch msg := cmd().(type) {
76+
case tea.BatchMsg:
77+
var out []tea.Msg
78+
for _, c := range msg {
79+
out = append(out, cmdMessages(c)...)
80+
}
81+
return out
82+
default:
83+
return []tea.Msg{msg}
84+
}
85+
}
86+
87+
func containsType(msgs []tea.Msg, want tea.Msg) bool {
88+
wt := reflect.TypeOf(want)
89+
for _, m := range msgs {
90+
if reflect.TypeOf(m) == wt {
91+
return true
92+
}
93+
}
94+
return false
95+
}
96+
97+
func TestEditorFinished_ReenablesMouse(t *testing.T) {
98+
// Bubble Tea's RestoreTerminal (run after the external editor exits) does not
99+
// re-enable mouse tracking, so the editor-finished handler must re-arm it or
100+
// the mouse stops working after the editor closes.
101+
want := tea.EnableMouseCellMotion()
102+
103+
t.Run("success", func(t *testing.T) {
104+
m := testModel(t, newNodes())
105+
f, err := os.CreateTemp(t.TempDir(), "ed-*.md")
106+
require.NoError(t, err)
107+
require.NoError(t, f.Close())
108+
_, cmd := m.Update(editorFinishedMsg{path: f.Name()})
109+
assert.True(t, containsType(cmdMessages(cmd), want), "mouse tracking is re-enabled after a clean editor exit")
110+
})
111+
112+
t.Run("editor error", func(t *testing.T) {
113+
m := testModel(t, newNodes())
114+
_, cmd := m.Update(editorFinishedMsg{path: "/no/such/file", err: assert.AnError})
115+
assert.True(t, containsType(cmdMessages(cmd), want), "mouse tracking is re-enabled even when the editor errored")
116+
})
117+
}
118+
69119
func TestRenderMarkdown(t *testing.T) {
70120
assert.Contains(t, renderMarkdown("", 40), "no description")
71121
// Glamour styles each word as a separate ANSI span, so assert on the words

0 commit comments

Comments
 (0)