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
6 changes: 6 additions & 0 deletions internal/tui/modals.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,12 @@ func (m *DashboardModel) renderModalStatusBar() string {
if m.aiClient != nil {
statusItems = append(statusItems, "i: AI Analysis")
}
// Add wrapping toggle for log details modal
if m.attributeWrappingEnabled {
statusItems = append(statusItems, "w: Disable wrapping")
} else {
statusItems = append(statusItems, "w: Enable wrapping")
}
statusItems = append(statusItems, "↑↓/Wheel: Scroll", "PgUp/PgDn: Page")
}
} else {
Expand Down
4 changes: 4 additions & 0 deletions internal/tui/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ type DashboardModel struct {
logAutoScroll bool // Auto-scroll to latest logs in log viewer
instructionsScrollOffset int // Scroll position for instructions/filter status screen

// Modal display options
attributeWrappingEnabled bool // Whether to wrap attribute values instead of truncating them

// Update interval management
availableIntervals []time.Duration
currentIntervalIdx int
Expand Down Expand Up @@ -283,6 +286,7 @@ func NewDashboardModel(maxLogBuffer int, updateInterval time.Duration, aiModel s
logAutoScroll: true, // Start with auto-scroll enabled
showColumns: true, // Show Host/Service columns by default
instructionsScrollOffset: 0, // Start at top of instructions
attributeWrappingEnabled: false, // Default to truncating (not wrapping)
// Initialize statistics tracking
statsStartTime: time.Now(),
statsTotalBytes: 0,
Expand Down
18 changes: 18 additions & 0 deletions internal/tui/navigation.go
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,16 @@ func (m *DashboardModel) handleKeyPress(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
}
return m, nil
}
case "w":
// Toggle attribute wrapping - only when not in chat mode
if !m.chatActive {
m.attributeWrappingEnabled = !m.attributeWrappingEnabled
// Refresh the modal content with new wrapping setting
if m.currentLogEntry != nil {
m.modalContent = m.formatLogDetails(*m.currentLogEntry, 60)
}
return m, nil
}
case "escape", "esc": // escape to close modal (only if not in chat mode)
m.showModal = false
m.modalContent = ""
Expand Down Expand Up @@ -899,6 +909,14 @@ func (m *DashboardModel) handleKeyPress(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
case "pgdown":
m.infoViewport.HalfPageDown()
return m, nil
case "w":
// Toggle attribute wrapping
m.attributeWrappingEnabled = !m.attributeWrappingEnabled
// Refresh the modal content with new wrapping setting
if m.currentLogEntry != nil {
m.modalContent = m.formatLogDetails(*m.currentLogEntry, 60)
}
return m, nil
case "escape", "esc":
m.showModal = false
m.modalContent = ""
Expand Down
71 changes: 64 additions & 7 deletions internal/tui/tables.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,25 +44,45 @@ func (m *DashboardModel) formatAttributesTable(attributes map[string]string, max

// Create table rows
rows := []table.Row{}
totalRows := 0
for _, key := range keys {
value := attributes[key]
// Truncate long values to fit
if len(value) > valueWidth-3 {
value = value[:valueWidth-3] + "..."
}
// Truncate long keys to fit

// Always truncate long keys to fit (keys are less important to see in full)
displayKey := key
if len(displayKey) > keyWidth-3 {
displayKey = displayKey[:keyWidth-3] + "..."
}
rows = append(rows, table.Row{displayKey, value})

// Handle value display based on wrapping setting
if m.attributeWrappingEnabled && len(value) > valueWidth {
// Wrap long values across multiple rows
wrappedLines := wrapText(value, valueWidth)
for i, line := range wrappedLines {
if i == 0 {
// First line shows the key
rows = append(rows, table.Row{displayKey, line})
} else {
// Subsequent lines have empty key column
rows = append(rows, table.Row{"", line})
}
totalRows++
}
} else {
// Truncate long values to fit (default behavior)
if len(value) > valueWidth-3 {
value = value[:valueWidth-3] + "..."
}
rows = append(rows, table.Row{displayKey, value})
totalRows++
}
}

// Create and configure table
t := table.New(
table.WithColumns(columns),
table.WithRows(rows),
table.WithHeight(len(rows)+2), // +2 for header and padding
table.WithHeight(totalRows+2), // +2 for header and padding
table.WithWidth(maxWidth),
table.WithFocused(false), // Disable focus to prevent selection
)
Expand Down Expand Up @@ -155,6 +175,43 @@ func (m *DashboardModel) formatLogDetails(entry LogEntry, maxWidth int) string {
return details.String()
}

// wrapText wraps text to fit within the specified width
func wrapText(text string, width int) []string {
if len(text) <= width {
return []string{text}
}

var lines []string
for len(text) > width {
// Find the best break point (prefer spaces)
breakPoint := width

// Look for a space near the end to break on word boundary
for i := width - 1; i > width/2 && i < len(text); i-- {
if text[i] == ' ' {
breakPoint = i
break
}
}

// Add the line (excluding the space if we broke on one)
if breakPoint < len(text) && text[breakPoint] == ' ' {
lines = append(lines, text[:breakPoint])
text = text[breakPoint+1:] // Skip the space
} else {
lines = append(lines, text[:breakPoint])
text = text[breakPoint:]
}
}

// Add the remaining text
if len(text) > 0 {
lines = append(lines, text)
}

return lines
}

// formatAttributeValuesModal formats the attribute values modal showing individual values and their counts with full width layout
func (m *DashboardModel) formatAttributeValuesModal(entry *memory.AttributeStatsEntry, maxWidth int) string {
var modal strings.Builder
Expand Down
Loading