summaryrefslogtreecommitdiff
path: root/internal/tmuxedit/agentutil.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-07-02 09:38:04 +0300
committerPaul Buetow <paul@buetow.org>2026-07-02 09:38:04 +0300
commita68228bfa12f4d8a51fe53e244fcd2e66c1ef692 (patch)
tree94e257b21b93419fef655c9813f68190204c3d0d /internal/tmuxedit/agentutil.go
parent9f0e96ce62339ddefa8771891e0864ede9af5064 (diff)
Remove hexai-tmux-edit popup editor featurev0.42.0
The tmux popup editor and its per-agent detection (Cursor/Amp/Aider) added maintenance surface without enough use to justify it; Codex and Claude Code already support external-editor mode natively via Ctrl+G. Drops internal/tmuxedit, cmd/hexai-tmux-edit, the [tmux_edit] config schema, the Mage build target, and all related docs/README mentions. Bump version to 0.42.0. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Diffstat (limited to 'internal/tmuxedit/agentutil.go')
-rw-r--r--internal/tmuxedit/agentutil.go183
1 files changed, 0 insertions, 183 deletions
diff --git a/internal/tmuxedit/agentutil.go b/internal/tmuxedit/agentutil.go
deleted file mode 100644
index bf1a723..0000000
--- a/internal/tmuxedit/agentutil.go
+++ /dev/null
@@ -1,183 +0,0 @@
-// Package tmuxedit implements a tmux popup editor for composing AI agent prompts.
-// agentutil.go provides shared helpers for prompt extraction and tmux key sending
-// used by individual agent implementations.
-package tmuxedit
-
-import (
- "fmt"
- "regexp"
- "strconv"
- "strings"
- "time"
-)
-
-const escapeKeyDelay = 150 * time.Millisecond
-
-// promptMatch holds a regex match result with its line number in the pane.
-type promptMatch struct {
- lineNum int
- text string // capture group 1
-}
-
-// matchPromptLines runs the prompt regex against each pane line, returning
-// matches with their line numbers for contiguity analysis.
-func matchPromptLines(re *regexp.Regexp, paneContent string) []promptMatch {
- paneLines := strings.Split(paneContent, "\n")
- var matches []promptMatch
- for i, line := range paneLines {
- m := re.FindStringSubmatch(line)
- if len(m) >= 2 {
- matches = append(matches, promptMatch{lineNum: i, text: m[1]})
- }
- }
- return matches
-}
-
-// joinAllMatches strips noise from all matches and joins the non-empty results
-// with newlines. Used when SectionPattern has already scoped to the prompt area.
-func joinAllMatches(matches []promptMatch, strips []string) string {
- var lines []string
- for _, m := range matches {
- line := stripNoise(m.text, strips)
- if line != "" {
- lines = append(lines, line)
- }
- }
- return strings.Join(lines, "\n")
-}
-
-// joinLastContiguousBlock takes the last group of matches on consecutive line
-// numbers, strips noise from each, and joins the non-empty results with
-// newlines. This ensures that only the bottom-most box (the input prompt)
-// is captured when multiple box-drawing sections exist in the pane.
-func joinLastContiguousBlock(matches []promptMatch, strips []string) string {
- last := len(matches) - 1
- start := last
- for start > 0 && matches[start].lineNum-matches[start-1].lineNum == 1 {
- start--
- }
- var lines []string
- for i := start; i <= last; i++ {
- line := stripNoise(matches[i].text, strips)
- if line != "" {
- lines = append(lines, line)
- }
- }
- return strings.Join(lines, "\n")
-}
-
-// scopeToLastSection extracts the content between the last two lines matching
-// the section delimiter pattern. This isolates the prompt area from previous
-// conversation content. Returns the full content if no pattern is set or
-// fewer than two delimiters are found.
-func scopeToLastSection(paneContent, sectionPattern string) string {
- if sectionPattern == "" {
- return paneContent
- }
- re, err := regexp.Compile(sectionPattern)
- if err != nil {
- return paneContent
- }
- lines := strings.Split(paneContent, "\n")
- var delimLines []int
- for i, line := range lines {
- if re.MatchString(line) {
- delimLines = append(delimLines, i)
- }
- }
- if len(delimLines) < 2 {
- return paneContent
- }
- start := delimLines[len(delimLines)-2] + 1
- end := delimLines[len(delimLines)-1]
- if start >= end {
- return paneContent
- }
- return strings.Join(lines[start:end], "\n")
-}
-
-// stripNoise removes each of the agent's StripPatterns from text and trims
-// whitespace.
-func stripNoise(text string, patterns []string) string {
- for _, p := range patterns {
- text = strings.ReplaceAll(text, p, "")
- }
- return strings.TrimSpace(text)
-}
-
-// sendClearSequence parses a space-separated key sequence and sends each
-// token individually. Tokens with a "*N" suffix (e.g. "BSpace*200") are
-// sent N times using tmux send-keys -N for efficient bulk repeats.
-func sendClearSequence(paneID, clearKeys string) error {
- return tmuxEditDeps{}.sendClearSequence(paneID, clearKeys)
-}
-
-func (d tmuxEditDeps) sendClearSequence(paneID, clearKeys string) error {
- for _, token := range strings.Fields(clearKeys) {
- key, count := parseKeyRepeat(token)
- if count > 1 {
- if err := d.sendRepeated(paneID, key, count); err != nil {
- return fmt.Errorf("clear key %q*%d failed: %w", key, count, err)
- }
- } else {
- if err := d.send(paneID, key); err != nil {
- return fmt.Errorf("clear key %q failed: %w", key, err)
- }
- }
- // Add delay after Escape to let Vim-based agents exit INSERT mode
- if key == "Escape" {
- d.sleepEscape()
- }
- }
- return nil
-}
-
-func (d tmuxEditDeps) sleepEscape() {
- if d.sleepAfterEscape != nil {
- d.sleepAfterEscape()
- return
- }
- time.Sleep(escapeKeyDelay)
-}
-
-// parseKeyRepeat splits "Key*N" into (Key, N). Returns (token, 1) if no
-// repeat suffix is present or the suffix is invalid.
-func parseKeyRepeat(token string) (string, int) {
- idx := strings.LastIndex(token, "*")
- if idx < 1 || idx >= len(token)-1 {
- return token, 1
- }
- n, err := strconv.Atoi(token[idx+1:])
- if err != nil || n < 1 {
- return token, 1
- }
- return token[:idx], n
-}
-
-// sendLines sends text line-by-line to a tmux pane, inserting the specified
-// newline key between lines. If newlineKeys is empty, "Enter" is used as
-// fallback. This is the shared text-sending logic used by agent SendText
-// implementations.
-func sendLines(paneID, text, newlineKeys string) error {
- return tmuxEditDeps{}.sendLines(paneID, text, newlineKeys)
-}
-
-func (d tmuxEditDeps) sendLines(paneID, text, newlineKeys string) error {
- lines := strings.Split(text, "\n")
- for i, line := range lines {
- if err := d.send(paneID, line); err != nil {
- return fmt.Errorf("send line %d failed: %w", i, err)
- }
- // Insert inter-line newline (except after the last line)
- if i < len(lines)-1 {
- nlKey := newlineKeys
- if nlKey == "" {
- nlKey = "Enter"
- }
- if err := d.send(paneID, nlKey); err != nil {
- return fmt.Errorf("newline after line %d failed: %w", i, err)
- }
- }
- }
- return nil
-}