diff --git a/pkg/tui/util/util.go b/pkg/tui/util/util.go index 1cdd974..b807ae7 100644 --- a/pkg/tui/util/util.go +++ b/pkg/tui/util/util.go @@ -54,6 +54,13 @@ func Height(str string) int { return strings.Count(str, "\n") + 1 } +func Width(str string) int { + if str == "" { + return 0 + } + return ansi.PrintableRuneWidth(str) +} + // truncate a string until its rendered cell width + the provided tail fits // within the given width func TruncateToCellWidth(str string, width int, tail string) string { diff --git a/pkg/tui/views/chat/view.go b/pkg/tui/views/chat/view.go index 0c40a41..f7adc9e 100644 --- a/pkg/tui/views/chat/view.go +++ b/pkg/tui/views/chat/view.go @@ -239,7 +239,7 @@ func (m *Model) Content(width, height int) string { input := m.input.View() // remaining height towards content - m.content.Width, m.content.Height = width, height - tuiutil.Height(input) + m.content.Width, m.content.Height = width, height-tuiutil.Height(input) content := m.content.View() return lipgloss.JoinVertical(lipgloss.Left, content, input) } @@ -261,6 +261,10 @@ func (m *Model) Footer(width int) string { segmentStyle := lipgloss.NewStyle().PaddingLeft(1).PaddingRight(1).Faint(true) segmentSeparator := "|" + // Left segments + + leftSegments := []string{} + savingStyle := segmentStyle.Bold(true) saving := "" if m.persistence { @@ -268,6 +272,20 @@ func (m *Model) Footer(width int) string { } else { saving = savingStyle.Foreground(lipgloss.Color("1")).Render("❌💾") } + leftSegments = append(leftSegments, saving) + + // Right segments + + rightSegments := []string{} + + if m.elapsed > 0 && m.tokenCount > 0 { + throughput := fmt.Sprintf("%.0f t/sec", float64(m.tokenCount)/m.elapsed.Seconds()) + rightSegments = append(rightSegments, segmentStyle.Render(throughput)) + } + model := segmentStyle.Render(m.App.ActiveModel(lipgloss.NewStyle())) + rightSegments = append(rightSegments, model) + + // Status var status string switch m.state { @@ -277,39 +295,62 @@ func (m *Model) Footer(width int) string { status = "Press ctrl+s to send" } - leftSegments := []string{ - saving, - segmentStyle.Render(status), - } - rightSegments := []string{} + return m.layoutFooter(width, leftSegments, status, rightSegments, segmentStyle, segmentSeparator) +} - if m.elapsed > 0 && m.tokenCount > 0 { - throughput := fmt.Sprintf("%.0f t/sec", float64(m.tokenCount)/m.elapsed.Seconds()) - rightSegments = append(rightSegments, segmentStyle.Render(throughput)) +func (m *Model) layoutFooter( + width int, + leftSegments []string, + status string, + rightSegments []string, + segmentStyle lipgloss.Style, + segmentSeparator string, +) string { + truncate := func(s string, w int) string { + return tuiutil.TruncateToCellWidth(s, w, "...") } - - if m.App.ProviderName != "" { - provider := fmt.Sprintf("Provider: %s", m.App.ProviderName) - rightSegments = append(rightSegments, segmentStyle.Render(provider)) - } - - model := fmt.Sprintf("Model: %s", m.App.Model) - rightSegments = append(rightSegments, segmentStyle.Render(model)) + padWidth := segmentStyle.GetHorizontalPadding() left := strings.Join(leftSegments, segmentSeparator) right := strings.Join(rightSegments, segmentSeparator) - totalWidth := lipgloss.Width(left) + lipgloss.Width(right) - remaining := width - totalWidth + leftWidth := tuiutil.Width(left) + rightWidth := tuiutil.Width(right) - var padding string - if remaining > 0 { - padding = strings.Repeat(" ", remaining) + availableWidth := width - leftWidth - rightWidth - tuiutil.Width(segmentSeparator) + + statusWidth := tuiutil.Width(status) + if availableWidth >= statusWidth+padWidth { + // Everything fits + availableWidth -= statusWidth + padWidth + padding := "" + if availableWidth > 0 { + padding = strings.Repeat(" ", availableWidth) + } + return footerStyle.Render(left + segmentSeparator + segmentStyle.Render(status) + padding + right) } - footer := left + padding + right - if remaining < 0 { - footer = tuiutil.TruncateToCellWidth(footer, width, "...") + if availableWidth > 4 { + // There is some space left for a truncated status + truncatedStatus := truncate(status, availableWidth-padWidth) + return footerStyle.Width(width).Render(left + segmentSeparator + segmentStyle.Render(truncatedStatus) + right) } - return footerStyle.Width(width).Render(footer) + + if availableWidth >= 0 { + // Draw some dots... + dots := "" + if availableWidth == 1 { + dots = " " + } else if availableWidth > 1 { + dots = " " + strings.Repeat(".", availableWidth-1) + dots = lipgloss.NewStyle().Faint(true).Render(dots) + } + + return footerStyle.Width(width).Render(left + segmentSeparator + dots + right) + } + + // Trucate right segment so it fits + right = truncate(right, tuiutil.Width(right)+availableWidth-1) + padding := "" + return footerStyle.Width(width).Render(left + segmentSeparator + padding + right) }