fix: word-wrapping on notes when terminal is small (#8)

This commit is contained in:
Rasmus Wejlgaard 2025-11-09 18:28:55 +00:00 committed by GitHub
parent aaa0ad0f55
commit 5a6fede2d8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 130 additions and 5 deletions

View file

@ -1,6 +1,7 @@
package ui package ui
import ( import (
"strings"
"time" "time"
"github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/help"
@ -104,8 +105,13 @@ func (m *uiModel) updateScrollOffset(availableHeight int) {
for i, item := range items { for i, item := range items {
lineCount := 1 // The item itself lineCount := 1 // The item itself
if !item.Folded && len(item.Notes) > 0 && m.mode == modeList { if !item.Folded && len(item.Notes) > 0 && m.mode == modeList {
// Count note lines (simplified - just count notes) // Count note lines with wrapping
lineCount += len(item.Notes) indent := strings.Repeat(" ", item.Level)
noteIndent := indent + " "
filteredNotes := filterLogbookDrawer(item.Notes)
wrappedNotes := wrapNoteLines(filteredNotes, m.width, noteIndent)
highlightedNotes := renderNotesWithHighlighting(wrappedNotes)
lineCount += len(highlightedNotes)
} }
itemLineCount[i] = lineCount itemLineCount[i] = lineCount
} }

View file

@ -146,8 +146,11 @@ func (m uiModel) View() string {
for i, item := range items { for i, item := range items {
lineCount := 1 // The item itself lineCount := 1 // The item itself
if !item.Folded && len(item.Notes) > 0 && m.mode == modeList { if !item.Folded && len(item.Notes) > 0 && m.mode == modeList {
indent := strings.Repeat(" ", item.Level)
noteIndent := indent + " "
filteredNotes := filterLogbookDrawer(item.Notes) filteredNotes := filterLogbookDrawer(item.Notes)
highlightedNotes := renderNotesWithHighlighting(filteredNotes) wrappedNotes := wrapNoteLines(filteredNotes, m.width, noteIndent)
highlightedNotes := renderNotesWithHighlighting(wrappedNotes)
lineCount += len(highlightedNotes) lineCount += len(highlightedNotes)
} }
itemLineCount[i] = lineCount itemLineCount[i] = lineCount
@ -205,8 +208,10 @@ func (m uiModel) View() string {
// Render remaining notes // Render remaining notes
if !item.Folded && len(item.Notes) > 0 && m.mode == modeList { if !item.Folded && len(item.Notes) > 0 && m.mode == modeList {
indent := strings.Repeat(" ", item.Level) indent := strings.Repeat(" ", item.Level)
noteIndent := indent + " "
filteredNotes := filterLogbookDrawer(item.Notes) filteredNotes := filterLogbookDrawer(item.Notes)
highlightedNotes := renderNotesWithHighlighting(filteredNotes) wrappedNotes := wrapNoteLines(filteredNotes, m.width, noteIndent)
highlightedNotes := renderNotesWithHighlighting(wrappedNotes)
for noteIdx := linesToSkip - 1; noteIdx < len(highlightedNotes) && itemLines < availableHeight; noteIdx++ { for noteIdx := linesToSkip - 1; noteIdx < len(highlightedNotes) && itemLines < availableHeight; noteIdx++ {
content.WriteString(indent) content.WriteString(indent)
content.WriteString(" " + highlightedNotes[noteIdx]) content.WriteString(" " + highlightedNotes[noteIdx])
@ -227,8 +232,10 @@ func (m uiModel) View() string {
// Show notes if not folded // Show notes if not folded
if !item.Folded && len(item.Notes) > 0 && m.mode == modeList { if !item.Folded && len(item.Notes) > 0 && m.mode == modeList {
indent := strings.Repeat(" ", item.Level) indent := strings.Repeat(" ", item.Level)
noteIndent := indent + " "
filteredNotes := filterLogbookDrawer(item.Notes) filteredNotes := filterLogbookDrawer(item.Notes)
highlightedNotes := renderNotesWithHighlighting(filteredNotes) wrappedNotes := wrapNoteLines(filteredNotes, m.width, noteIndent)
highlightedNotes := renderNotesWithHighlighting(wrappedNotes)
for _, note := range highlightedNotes { for _, note := range highlightedNotes {
if itemLines >= availableHeight { if itemLines >= availableHeight {
break break
@ -610,6 +617,29 @@ func filterLogbookDrawer(notes []string) []string {
return filtered return filtered
} }
// wrapNoteLines wraps note lines to fit within the specified width
func wrapNoteLines(notes []string, width int, indent string) []string {
var wrapped []string
for _, note := range notes {
// Don't wrap code block delimiters or drawer markers
trimmed := strings.TrimSpace(note)
if strings.HasPrefix(trimmed, "#+BEGIN_SRC") ||
strings.HasPrefix(trimmed, "#+END_SRC") ||
strings.HasPrefix(trimmed, "```") ||
trimmed == ":LOGBOOK:" ||
trimmed == ":PROPERTIES:" ||
trimmed == ":END:" {
wrapped = append(wrapped, note)
continue
}
// Wrap the note line
wrappedLines := wrapText(note, width, indent)
wrapped = append(wrapped, wrappedLines...)
}
return wrapped
}
// renderNotesWithHighlighting renders notes with syntax highlighting for code blocks // renderNotesWithHighlighting renders notes with syntax highlighting for code blocks
func renderNotesWithHighlighting(notes []string) []string { func renderNotesWithHighlighting(notes []string) []string {
if len(notes) == 0 { if len(notes) == 0 {
@ -751,6 +781,95 @@ func highlightCode(code, language string) string {
return strings.TrimRight(buf.String(), "\n") return strings.TrimRight(buf.String(), "\n")
} }
// wrapText wraps text to fit within the specified width, accounting for indent
func wrapText(text string, width int, indent string) []string {
if width <= 0 {
return []string{text}
}
// Calculate available width after indent
indentWidth := lipgloss.Width(indent)
availableWidth := width - indentWidth
if availableWidth <= 10 {
// If very little space, just return the original text
return []string{text}
}
var result []string
var currentLine strings.Builder
currentWidth := 0
// Split by whitespace while preserving leading/trailing spaces
words := strings.Fields(text)
if len(words) == 0 {
// Preserve empty lines
return []string{text}
}
for i, word := range words {
wordWidth := lipgloss.Width(word)
// If this is the first word on the line
if currentWidth == 0 {
// Handle words longer than available width
if wordWidth > availableWidth {
// Split the word across multiple lines
for len(word) > 0 {
if availableWidth <= 0 {
availableWidth = 10 // Fallback
}
chunkSize := availableWidth
if chunkSize > len(word) {
chunkSize = len(word)
}
result = append(result, word[:chunkSize])
word = word[chunkSize:]
}
continue
}
currentLine.WriteString(word)
currentWidth = wordWidth
} else {
// Check if adding this word (plus a space) would exceed the width
spaceAndWordWidth := currentWidth + 1 + wordWidth
if spaceAndWordWidth > availableWidth {
// Start a new line
result = append(result, currentLine.String())
currentLine.Reset()
// Handle words longer than available width
if wordWidth > availableWidth {
for len(word) > 0 {
chunkSize := availableWidth
if chunkSize > len(word) {
chunkSize = len(word)
}
result = append(result, word[:chunkSize])
word = word[chunkSize:]
}
currentWidth = 0
continue
}
currentLine.WriteString(word)
currentWidth = wordWidth
} else {
// Add word to current line
currentLine.WriteString(" ")
currentLine.WriteString(word)
currentWidth = spaceAndWordWidth
}
}
// If this is the last word, add the line
if i == len(words)-1 && currentLine.Len() > 0 {
result = append(result, currentLine.String())
}
}
return result
}
func (m uiModel) renderItem(item *model.Item, isCursor bool) string { func (m uiModel) renderItem(item *model.Item, isCursor bool) string {
var b strings.Builder var b strings.Builder