Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions internal/tmux/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 21 additions & 3 deletions internal/tui/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
}

Expand Down
33 changes: 26 additions & 7 deletions internal/tui/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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]

Expand All @@ -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
Expand Down Expand Up @@ -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)
}

Expand Down