summaryrefslogtreecommitdiff
path: root/internal/tui/dashboard/model.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-09 22:41:07 +0200
committerPaul Buetow <paul@buetow.org>2026-03-09 22:41:07 +0200
commit1af7cf5fe51fa13e828cdef6268348ec9cd7bd7c (patch)
treef8d28d4faa627b31175c0c39164c2ea84e022e90 /internal/tui/dashboard/model.go
parenta4c72ad2cbe4ca857a5880675563b2ab4d24e1b5 (diff)
tui: add sortable files dashboard table modes (task 364)
Diffstat (limited to 'internal/tui/dashboard/model.go')
-rw-r--r--internal/tui/dashboard/model.go147
1 files changed, 138 insertions, 9 deletions
diff --git a/internal/tui/dashboard/model.go b/internal/tui/dashboard/model.go
index 7fea1c4..e418367 100644
--- a/internal/tui/dashboard/model.go
+++ b/internal/tui/dashboard/model.go
@@ -75,9 +75,11 @@ type Model struct {
syscallsTreemapSelection int
filesOffset int
filesCol int
+ filesSort tableSortState[fileSortKey]
filesDirGrouped bool
filesDirOffset int
filesDirCol int
+ filesDirSort tableSortState[fileDirSortKey]
processesOffset int
processesCol int
syscallsVizMode tabVizMode
@@ -227,14 +229,24 @@ func (m Model) handleBubbleTick() (tea.Model, tea.Cmd) {
func (m Model) handleStatsTick(msg messages.StatsTickMsg) (tea.Model, tea.Cmd) {
selectedSyscall := ""
+ selectedFile := ""
+ selectedDir := ""
if m.syscallsSort.active {
selectedSyscall = m.selectedSyscallName()
}
+ if m.filesVizMode == tabVizModeTable {
+ if !m.filesDirGrouped && m.filesSort.active {
+ selectedFile = m.selectedFilePath()
+ }
+ if m.filesDirGrouped && m.filesDirSort.active {
+ selectedDir = m.selectedDirPath()
+ }
+ }
m.latest = msg.Snap
m.reanchorSyscallsOffset(selectedSyscall)
+ m.reanchorFilesOffset(selectedFile)
+ m.reanchorFilesDirOffset(selectedDir)
m.syscallsTreemapSelection = clampOffset(m.syscallsTreemapSelection, m.maxSyscallsRows())
- m.filesOffset = clampOffset(m.filesOffset, m.maxFilesRows())
- m.filesDirOffset = clampOffset(m.filesDirOffset, m.maxFilesDirRowsForMode())
m.processesOffset = clampOffset(m.processesOffset, m.maxProcessesRows())
m.clampTableColumns()
m.streamModel.Refresh()
@@ -363,7 +375,18 @@ func (m *Model) handleSortKey(msg tea.KeyPressMsg) (bool, tea.Cmd) {
if !key.Matches(msg, m.keys.Sort) {
return false, nil
}
- if m.activeTab != TabSyscalls || m.syscallsVizMode != tabVizModeTable {
+ switch m.activeTab {
+ case TabSyscalls:
+ return m.handleSyscallsSortKey()
+ case TabFiles:
+ return m.handleFilesSortKey()
+ default:
+ return false, nil
+ }
+}
+
+func (m *Model) handleSyscallsSortKey() (bool, tea.Cmd) {
+ if m.syscallsVizMode != tabVizModeTable {
return false, nil
}
key, ok := syscallSortKeyForColumn(m.width, m.syscallsCol)
@@ -376,6 +399,30 @@ func (m *Model) handleSortKey(msg tea.KeyPressMsg) (bool, tea.Cmd) {
return true, nil
}
+func (m *Model) handleFilesSortKey() (bool, tea.Cmd) {
+ if m.filesVizMode != tabVizModeTable {
+ return false, nil
+ }
+ if m.filesDirGrouped {
+ key, ok := fileDirSortKeyForColumn(m.filesDirCol)
+ if !ok {
+ return false, nil
+ }
+ selectedDir := m.selectedDirPath()
+ m.filesDirSort = m.filesDirSort.toggled(key)
+ m.reanchorFilesDirOffset(selectedDir)
+ return true, nil
+ }
+ key, ok := fileSortKeyForColumn(m.filesCol)
+ if !ok {
+ return false, nil
+ }
+ selectedPath := m.selectedFilePath()
+ m.filesSort = m.filesSort.toggled(key)
+ m.reanchorFilesOffset(selectedPath)
+ return true, nil
+}
+
func (m *Model) reanchorSyscallsOffset(selectedName string) {
rows := m.sortedSyscallRows()
if len(rows) == 0 {
@@ -391,28 +438,56 @@ func (m *Model) reanchorSyscallsOffset(selectedName string) {
m.syscallsOffset = clampOffset(m.syscallsOffset, len(rows))
}
+func (m *Model) reanchorFilesOffset(selectedPath string) {
+ rows := m.sortedFileRows()
+ if len(rows) == 0 {
+ m.filesOffset = 0
+ return
+ }
+ if selectedPath != "" {
+ if index, ok := findFileOffset(rows, selectedPath); ok {
+ m.filesOffset = index
+ return
+ }
+ }
+ m.filesOffset = clampOffset(m.filesOffset, len(rows))
+}
+
+func (m *Model) reanchorFilesDirOffset(selectedDir string) {
+ rows := m.sortedDirRows()
+ if len(rows) == 0 {
+ m.filesDirOffset = 0
+ return
+ }
+ if selectedDir != "" {
+ if index, ok := findDirOffset(rows, selectedDir); ok {
+ m.filesDirOffset = index
+ return
+ }
+ }
+ m.filesDirOffset = clampOffset(m.filesDirOffset, len(rows))
+}
+
func (m Model) selectedFileFilter() (globalfilter.Filter, string, bool) {
if m.latest == nil {
return globalfilter.Filter{}, "", false
}
filter := m.globalFilter.Clone()
if m.filesDirGrouped {
- dirs := aggregateFilesByDir(m.latest.Files())
- if len(dirs) == 0 {
+ selected, ok := m.selectedDirSnapshot()
+ if !ok {
return globalfilter.Filter{}, "", false
}
- selected := dirs[clampOffset(m.filesDirOffset, len(dirs))]
if strings.TrimSpace(selected.Dir) == "" {
return globalfilter.Filter{}, "", false
}
filter.File = &globalfilter.StringFilter{Pattern: selected.Dir}
return filter, "file~" + selected.Dir, true
}
- files := m.latest.Files()
- if len(files) == 0 {
+ selected, ok := m.selectedFileSnapshot()
+ if !ok {
return globalfilter.Filter{}, "", false
}
- selected := files[clampOffset(m.filesOffset, len(files))]
if strings.TrimSpace(selected.Path) == "" {
return globalfilter.Filter{}, "", false
}
@@ -420,6 +495,54 @@ func (m Model) selectedFileFilter() (globalfilter.Filter, string, bool) {
return filter, "file~" + selected.Path, true
}
+func (m Model) selectedFileSnapshot() (statsengine.FileSnapshot, bool) {
+ rows := m.sortedFileRows()
+ if len(rows) == 0 {
+ return statsengine.FileSnapshot{}, false
+ }
+ index := clampOffset(m.filesOffset, len(rows))
+ return rows[index], true
+}
+
+func (m Model) sortedFileRows() []statsengine.FileSnapshot {
+ if m.latest == nil {
+ return nil
+ }
+ return sortedFileSnapshots(m.latest.Files(), m.filesSort)
+}
+
+func (m Model) selectedFilePath() string {
+ selected, ok := m.selectedFileSnapshot()
+ if !ok {
+ return ""
+ }
+ return selected.Path
+}
+
+func (m Model) selectedDirSnapshot() (DirSnapshot, bool) {
+ rows := m.sortedDirRows()
+ if len(rows) == 0 {
+ return DirSnapshot{}, false
+ }
+ index := clampOffset(m.filesDirOffset, len(rows))
+ return rows[index], true
+}
+
+func (m Model) sortedDirRows() []DirSnapshot {
+ if m.latest == nil {
+ return nil
+ }
+ return sortedDirSnapshots(aggregateFilesByDir(m.latest.Files()), m.filesDirSort)
+}
+
+func (m Model) selectedDirPath() string {
+ selected, ok := m.selectedDirSnapshot()
+ if !ok {
+ return ""
+ }
+ return selected.Dir
+}
+
func (m Model) handleHelpToggleKey(msg tea.KeyPressMsg) (bool, tea.Model, tea.Cmd) {
if msg.String() != "H" {
return false, m, nil
@@ -917,6 +1040,12 @@ func (m Model) renderActiveContent(width, activeHeight int, streamModel *eventst
if m.activeTab == TabSyscalls && m.latest != nil {
return renderSyscallsWithSort(m.latest, width, activeHeight, m.syscallsOffset, m.syscallsCol, m.syscallsSort)
}
+ if m.activeTab == TabFiles && m.latest != nil && m.filesVizMode == tabVizModeTable {
+ if m.filesDirGrouped {
+ return renderFilesDirGroupedWithSort(m.latest, width, activeHeight, m.filesDirOffset, m.filesDirCol, m.filesDirSort)
+ }
+ return renderFilesWithSort(m.latest, width, activeHeight, m.filesOffset, m.filesCol, m.filesSort)
+ }
return renderActiveTab(
m.activeTab,
m.latest,