Add automatic wrapping of the content based on terminal width

This commit is contained in:
2025-11-29 01:59:23 +00:00
parent 1b832e2fcf
commit 9598444d56
6 changed files with 149 additions and 23 deletions
+71 -16
View File
@@ -920,8 +920,15 @@ func (m *Model) View() string {
currentContent := sb.String()
currentLines := strings.Count(currentContent, "\n") + 1
// Fill space to push footer to bottom (reserve 3 lines for footer)
footerHeight := 3
// Calculate footer height dynamically (help bar lines + status bar + spacing)
footerHeight := 2 // status bar + newline before it
var helpBarContent string
if m.mode == ViewList {
helpBarContent = m.helpBar()
helpBarLines := strings.Count(helpBarContent, "\n") + 1
footerHeight += helpBarLines + 1 // +1 for newline before help bar
}
remainingLines := m.height - currentLines - footerHeight
if remainingLines > 0 {
sb.WriteString(strings.Repeat("\n", remainingLines))
@@ -930,7 +937,7 @@ func (m *Model) View() string {
// Footer (help bar + status bar)
if m.mode == ViewList {
sb.WriteString("\n")
sb.WriteString(m.helpBar())
sb.WriteString(helpBarContent)
}
sb.WriteString("\n")
sb.WriteString(m.statusBar())
@@ -939,19 +946,67 @@ func (m *Model) View() string {
}
func (m *Model) helpBar() string {
return helpBarStyle.Render(fmt.Sprintf("%s/%s: Navigate %s: Toggle %s: New %s: Edit %s: Delete %s: Presets %s: Groups %s: Backups %s: Search %s: Help %s: Quit",
helpKeyStyle.Render("↑↓"),
helpKeyStyle.Render("jk"),
helpKeyStyle.Render("Space"),
helpKeyStyle.Render("n"),
helpKeyStyle.Render("e"),
helpKeyStyle.Render("d"),
helpKeyStyle.Render("p"),
helpKeyStyle.Render("g"),
helpKeyStyle.Render("b"),
helpKeyStyle.Render("/"),
helpKeyStyle.Render("?"),
helpKeyStyle.Render("q")))
// Define help items with their display widths (without ANSI codes)
type helpItem struct {
key string
desc string
rawWidth int // width without ANSI escape codes
}
items := []helpItem{
{"↑↓/jk", "Navigate", 13},
{"Space", "Toggle", 13},
{"n", "New", 6},
{"e", "Edit", 7},
{"d", "Delete", 9},
{"p", "Presets", 10},
{"g", "Groups", 9},
{"b", "Backups", 10},
{"/", "Search", 9},
{"?", "Help", 7},
{"q", "Quit", 7},
}
separator := " "
sepWidth := 2
var lines []string
var currentLine string
var currentWidth int
for i, item := range items {
rendered := helpKeyStyle.Render(item.key) + ": " + item.desc
// Check if adding this item would exceed width
newWidth := currentWidth + item.rawWidth
if currentWidth > 0 {
newWidth += sepWidth
}
if m.width > 0 && newWidth > m.width && currentWidth > 0 {
// Start a new line
lines = append(lines, currentLine)
currentLine = rendered
currentWidth = item.rawWidth
} else {
// Add to current line
if currentWidth > 0 {
currentLine += separator
}
currentLine += rendered
currentWidth = newWidth
if i == 0 {
currentWidth = item.rawWidth
}
}
}
// Add the last line
if currentLine != "" {
lines = append(lines, currentLine)
}
return helpBarStyle.Render(strings.Join(lines, "\n"))
}
func (m *Model) statusBar() string {
+3 -2
View File
@@ -174,7 +174,7 @@ func (b *BackupPicker) selectView() string {
}
leftSb.WriteString("\n")
leftSb.WriteString(helpDescStyle.Render("↑↓ navigate • Enter restore • Esc cancel"))
leftSb.WriteString(WrapHelpText("↑↓ navigate • Enter restore • Esc cancel", 40))
// Build right panel (preview)
var rightSb strings.Builder
@@ -219,7 +219,8 @@ func (b *BackupPicker) selectView() string {
// Show scroll indicator
if len(lines) > previewHeight {
rightSb.WriteString("\n")
rightSb.WriteString(helpDescStyle.Render(fmt.Sprintf("Lines %d-%d of %d (Shift+↑↓ scroll)", b.previewScroll+1, endLine, len(lines))))
scrollText := fmt.Sprintf("%d-%d of %d (↑↓ scroll)", b.previewScroll+1, endLine, len(lines))
rightSb.WriteString(helpDescStyle.Render(scrollText))
}
}
+1 -1
View File
@@ -291,7 +291,7 @@ func (f *Form) View() string {
sb.WriteString("\n\n")
sb.WriteString("\n")
sb.WriteString(helpDescStyle.Render("Tab/↓ next • Shift+Tab/↑ prev • ←→ select group • Enter save • Esc cancel"))
sb.WriteString(WrapHelpText("Tab/↓ next • Shift+Tab/↑ prev • ←→ select group • Enter save • Esc cancel", f.width-6))
return dialogStyle.Render(sb.String())
}
+1 -1
View File
@@ -190,7 +190,7 @@ func (g *GroupPicker) selectView() string {
}
sb.WriteString("\n\n")
sb.WriteString(helpDescStyle.Render("↑↓ navigate • n new • r rename • d delete • Esc back"))
sb.WriteString(WrapHelpText("↑↓ navigate • n new • r rename • d delete • Esc back", g.width-6))
return dialogStyle.Render(sb.String())
}
+3 -3
View File
@@ -411,7 +411,7 @@ func (p *PresetPicker) selectView() string {
}
sb.WriteString("\n")
sb.WriteString(helpDescStyle.Render("↑↓ navigate • Enter apply • n new • e edit • d delete • Esc cancel"))
sb.WriteString(WrapHelpText("↑↓ navigate • Enter apply • n new • e edit • d delete • Esc cancel", p.width-6))
return dialogStyle.Render(sb.String())
}
@@ -483,7 +483,7 @@ func (p *PresetPicker) formView() string {
}
sb.WriteString("\n\n")
sb.WriteString(helpDescStyle.Render("Tab/↓ next • Enter select/save • Esc cancel"))
sb.WriteString(WrapHelpText("Tab/↓ next • Enter select/save • Esc cancel", p.width-6))
return dialogStyle.Render(sb.String())
}
@@ -536,7 +536,7 @@ func (p *PresetPicker) pickerView() string {
sb.WriteString(titleStyle.Render(title))
sb.WriteString("\n")
sb.WriteString(helpDescStyle.Render("Space to toggle • Enter to confirm • Esc to cancel"))
sb.WriteString(WrapHelpText("Space to toggle • Enter to confirm • Esc to cancel", p.width-6))
sb.WriteString("\n\n")
filtered := p.getFilteredAliases()
+70
View File
@@ -2,6 +2,8 @@
package tui
import (
"strings"
"github.com/charmbracelet/lipgloss"
)
@@ -148,3 +150,71 @@ func StatusText(enabled bool, pending bool, hasError bool) string {
func HelpItem(key, desc string) string {
return helpKeyStyle.Render(key) + " " + helpDescStyle.Render(desc)
}
// WrapHelpText wraps help text to fit within maxWidth, splitting on bullet separators.
// If maxWidth is 0 or negative, returns the original text.
func WrapHelpText(text string, maxWidth int) string {
if maxWidth <= 0 {
return helpDescStyle.Render(text)
}
separator := " • "
parts := splitOnSeparator(text, separator)
var lines []string
var currentLine string
var currentWidth int
for i, part := range parts {
partWidth := len(part)
sepWidth := 3 // len(" • ")
newWidth := currentWidth + partWidth
if currentWidth > 0 {
newWidth += sepWidth
}
if newWidth > maxWidth && currentWidth > 0 {
lines = append(lines, currentLine)
currentLine = part
currentWidth = partWidth
} else {
if currentWidth > 0 {
currentLine += separator
}
currentLine += part
if i == 0 {
currentWidth = partWidth
} else {
currentWidth = newWidth
}
}
}
if currentLine != "" {
lines = append(lines, currentLine)
}
// Apply style to each line and join
var result []string
for _, line := range lines {
result = append(result, helpDescStyle.Render(line))
}
return strings.Join(result, "\n")
}
// splitOnSeparator splits a string on the given separator.
func splitOnSeparator(s, sep string) []string {
var parts []string
for {
idx := strings.Index(s, sep)
if idx == -1 {
parts = append(parts, s)
break
}
parts = append(parts, s[:idx])
s = s[idx+len(sep):]
}
return parts
}