diff options
| author | Paul Buetow <paul@buetow.org> | 2026-02-08 15:19:36 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-02-08 15:19:36 +0200 |
| commit | 887d7bc186db90c3903851b0f1db2d24df5d7a7b (patch) | |
| tree | 1cfb8055ddbb907bae9461b924dda1d2b3f15b46 /internal/tmuxedit/send_test.go | |
| parent | 6da37034708dc7d4dcb7c71e890478a68e6ae4a1 (diff) | |
fix hexai-tmux-edit agent detection, multi-line extraction, and clearing
- Reorder agents: cursor first to avoid false positive from "Claude 4.5
Sonnet" model name appearing in cursor panes
- Change cursor detect pattern to box-drawing UI elements instead of
name-based matching
- Change claude detect pattern to ❯ prompt instead of generic "claude"
- Support multi-line prompt extraction using last-contiguous-block
algorithm to ignore command-review and dialog boxes
- Fix deduplicateText to always return full edited text (clear + resend)
instead of stripping original prefix which caused double-removal
- Replace vim-style clear (Escape gg dG i) with universal End+BSpace*200
since cursor's prompt is not actually vim
- Add Key*N repeat syntax in ClearKeys (parsed by parseKeyRepeat, sent
via tmux send-keys -N)
- Add 300ms sleep after clear to let TUI drain queued backspaces before
new text arrives
- Add debug logging to /tmp/hexai-tmux-edit.log
- Add strip pattern for "ctrl+c to stop" noise in cursor prompt
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/tmuxedit/send_test.go')
| -rw-r--r-- | internal/tmuxedit/send_test.go | 81 |
1 files changed, 78 insertions, 3 deletions
diff --git a/internal/tmuxedit/send_test.go b/internal/tmuxedit/send_test.go index eeced35..e458282 100644 --- a/internal/tmuxedit/send_test.go +++ b/internal/tmuxedit/send_test.go @@ -17,10 +17,10 @@ func TestDeduplicateText(t *testing.T) { {"empty original", "", "new text", "new text"}, {"empty edited", "original", "", ""}, {"unchanged", "hello world", "hello world", ""}, - {"appended", "hello", "hello world", "world"}, + {"appended", "hello", "hello world", "hello world"}, {"rewritten", "hello world", "goodbye world", "goodbye world"}, - {"whitespace handling", " hello ", " hello world ", "world"}, - {"prefix match with newlines", "line1\nline2", "line1\nline2\nline3", "line3"}, + {"whitespace handling", " hello ", " hello world ", "hello world"}, + {"appended with newlines", "line1\nline2", "line1\nline2\nline3", "line1\nline2\nline3"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -33,7 +33,16 @@ func TestDeduplicateText(t *testing.T) { } } +// noSleep disables the post-clear sleep in tests and restores it on cleanup. +func noSleep(t *testing.T) { + t.Helper() + old := sleepAfterClear + sleepAfterClear = func() {} + t.Cleanup(func() { sleepAfterClear = old }) +} + func TestSendTextToPane_SingleLine(t *testing.T) { + noSleep(t) var calls []string oldSend := sendKeys defer func() { sendKeys = oldSend }() @@ -116,6 +125,7 @@ func TestSendTextToPane_Empty(t *testing.T) { } func TestSendTextToPane_ClearError(t *testing.T) { + noSleep(t) oldSend := sendKeys defer func() { sendKeys = oldSend }() sendKeys = func(paneID string, keys ...string) error { @@ -129,6 +139,7 @@ func TestSendTextToPane_ClearError(t *testing.T) { } func TestSendTextToPane_SendError(t *testing.T) { + noSleep(t) callCount := 0 oldSend := sendKeys defer func() { sendKeys = oldSend }() @@ -146,6 +157,70 @@ func TestSendTextToPane_SendError(t *testing.T) { } } +func TestSendTextToPane_BulkClear(t *testing.T) { + noSleep(t) + var calls []string + oldSend := sendKeys + oldRepeat := sendRepeatedKey + defer func() { + sendKeys = oldSend + sendRepeatedKey = oldRepeat + }() + sendKeys = func(paneID string, keys ...string) error { + calls = append(calls, fmt.Sprintf("send:%s:%s", paneID, strings.Join(keys, ","))) + return nil + } + sendRepeatedKey = func(paneID, key string, count int) error { + calls = append(calls, fmt.Sprintf("repeat:%s:%s*%d", paneID, key, count)) + return nil + } + // "End BSpace*200" should send End normally, then BSpace 200 times via -N + agent := AgentConfig{ClearFirst: true, ClearKeys: "End BSpace*200", NewlineKeys: "S-Enter"} + err := sendTextToPane("%5", "new text", agent) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + want := []string{ + "send:%5:End", + "repeat:%5:BSpace*200", + "send:%5:new text", + } + if len(calls) != len(want) { + t.Fatalf("got %d calls, want %d: %v", len(calls), len(want), calls) + } + for i, w := range want { + if calls[i] != w { + t.Errorf("call[%d] = %q, want %q", i, calls[i], w) + } + } +} + +func TestParseKeyRepeat(t *testing.T) { + tests := []struct { + token string + wantKey string + wantCount int + }{ + {"BSpace*200", "BSpace", 200}, + {"End", "End", 1}, + {"C-u", "C-u", 1}, + {"BSpace*1", "BSpace", 1}, + {"BSpace*0", "BSpace*0", 1}, // invalid count + {"BSpace*abc", "BSpace*abc", 1}, // non-numeric + {"*200", "*200", 1}, // no key name + {"x*3", "x", 3}, + } + for _, tt := range tests { + t.Run(tt.token, func(t *testing.T) { + key, count := parseKeyRepeat(tt.token) + if key != tt.wantKey || count != tt.wantCount { + t.Errorf("parseKeyRepeat(%q) = (%q, %d), want (%q, %d)", + tt.token, key, count, tt.wantKey, tt.wantCount) + } + }) + } +} + func TestSendTextToPane_FallbackNewline(t *testing.T) { var calls []string oldSend := sendKeys |
