diff --git a/internal/tmux/client.go b/internal/tmux/client.go index 52e91c0..e37ae01 100644 --- a/internal/tmux/client.go +++ b/internal/tmux/client.go @@ -179,14 +179,19 @@ func (c *Client) SendKeysToPane(session string, pane *Pane, keys string) error { if err := cmd.Run(); err != nil { return err } - time.Sleep(10 * time.Millisecond) - // Send double Enter for multi-line content (3+ lines) to signal paste completion - args := []string{"send-keys", "-t", target, "Enter"} - if strings.Count(keys, "\n") >= 2 { - args = append(args, "Enter") + time.Sleep(50 * time.Millisecond) + // Send first Enter + cmd = exec.Command("tmux", "send-keys", "-t", target, "Enter") + if err := cmd.Run(); err != nil { + return err } - cmd = exec.Command("tmux", args...) - return cmd.Run() + // Send second Enter for multi-line content to signal paste completion + if strings.Count(keys, "\n") >= 1 { + time.Sleep(30 * time.Millisecond) + cmd = exec.Command("tmux", "send-keys", "-t", target, "Enter") + return cmd.Run() + } + return nil } // SendKeysRaw sends keys to a tmux session without auto-Enter diff --git a/internal/tui/model.go b/internal/tui/model.go index 41985a7..6104418 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -284,7 +284,11 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } m.promptField.SetWidth(promptWidth) if m.pathPickerMode { - m.pathPickerList.SetSize(msg.Width-18, msg.Height-18) + listHeight := msg.Height - 10 + if listHeight < 5 { + listHeight = 5 + } + m.pathPickerList.SetSize(msg.Width-10, listHeight) } } @@ -625,6 +629,16 @@ func (m *Model) handleKey(msg tea.KeyMsg) tea.Cmd { m.confirmQuit = true return nil + case "esc": + if m.selected < len(m.sessions) { + session := m.sessions[m.selected] + if session.State == claude.StateUrgent || session.State == claude.StateActive || session.State == claude.StateThinking { + _ = m.tmux.SendKeysToPaneRaw(session.Name, session.ClaudePane, "Escape") + m.addActivity(session.Name, "Sent Escape") + return nil + } + } + case "?": m.showHelp = true @@ -865,12 +879,16 @@ func (m *Model) buildPathList() list.Model { items = append(items, pathItem{path: cwd, source: "cwd"}) } - l := list.New(items, newPathDelegate(), 0, 0) + // Calculate list size: container(height-4) - border(2) - padding(2) - help+empty(2) = height-10 + listHeight := m.height - 10 + if listHeight < 5 { + listHeight = 5 + } + l := list.New(items, newPathDelegate(), m.width-10, listHeight) l.Title = m.pathPickerTitle() l.SetShowStatusBar(false) l.SetFilteringEnabled(true) l.Styles.Title = l.Styles.Title.Foreground(colorPrimary).Bold(true) - l.SetSize(m.width-18, m.height-18) return l } diff --git a/internal/tui/view.go b/internal/tui/view.go index eedbc95..a058bf9 100644 --- a/internal/tui/view.go +++ b/internal/tui/view.go @@ -91,10 +91,10 @@ func (m *Model) View() string { borderOverhead = 4 // horizontal dividers ) - // Calculate prompt panel height (dynamic based on textarea content) + // Calculate prompt panel height (use textarea's actual configured height) promptLines := 1 // header if m.promptMode { - promptLines += strings.Count(m.promptField.Value(), "\n") + 1 + promptLines += m.promptField.Height() } else { promptLines += 1 // hint line } @@ -467,7 +467,23 @@ func (m *Model) viewPreview(width, height int) string { content = m.previewCache[sess.Name] } if content != "" { - contentLines := strings.Split(strings.TrimSpace(content), "\n") + // Wrap lines to fit preview width + rawLines := strings.Split(strings.TrimSpace(content), "\n") + var contentLines []string + maxLineWidth := width - 2 + if maxLineWidth < 10 { + maxLineWidth = 10 + } + for _, line := range rawLines { + if ansi.StringWidth(line) <= maxLineWidth { + contentLines = append(contentLines, line) + } else { + // Wrap long lines + wrapped := ansi.Hardwrap(line, maxLineWidth, false) + contentLines = append(contentLines, strings.Split(wrapped, "\n")...) + } + } + availableHeight := height - len(lines) - 1 scrollPos := m.previewScrollPos[sess.Name] @@ -490,9 +506,6 @@ func (m *Model) viewPreview(width, height int) string { } for _, line := range contentLines[start:end] { - if ansi.StringWidth(line) > width-2 { - line = ansi.Truncate(line, width-2, "") - } lines = append(lines, " "+line) if len(lines) >= height-1 { break @@ -756,12 +769,18 @@ func (m *Model) viewPathPicker() string { listView := m.pathPickerList.View() content := lipgloss.JoinVertical(lipgloss.Left, listView, "", help) + // Calculate available height: total - border(2) - padding(2) + availableHeight := m.height - 4 + if availableHeight < 10 { + availableHeight = 10 + } + return lipgloss.NewStyle(). Border(lipgloss.RoundedBorder()). BorderForeground(colorPrimary). Padding(1, 2). Width(m.width - 4). - Height(m.height - 4). + MaxHeight(availableHeight). Render(content) }