summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Milde <daniel@milde.cz>2021-08-01 12:01:06 +0200
committerDaniel Milde <daniel@milde.cz>2021-08-01 12:01:06 +0200
commit8ff4249b9faad4a9b343568e01cb2879c3126529 (patch)
tree50c78bd5b60e4694d2a2f65b57faa432e56cb192
parent91acb896ab2e74490cb222f88924c6a866524ab2 (diff)
search items by namev5.5.0search
closes #80
-rw-r--r--tui/actions.go4
-rw-r--r--tui/filter.go50
-rw-r--r--tui/filter_test.go115
-rw-r--r--tui/keys.go12
-rw-r--r--tui/tui.go51
-rw-r--r--tui/tui_test.go21
6 files changed, 227 insertions, 26 deletions
diff --git a/tui/actions.go b/tui/actions.go
index 6e7122c..417ff14 100644
--- a/tui/actions.go
+++ b/tui/actions.go
@@ -53,7 +53,7 @@ func (ui *UI) ListDevices(getter device.DevicesInfoGetter) error {
}
ui.table.Select(1, 0)
- ui.footer.SetText("")
+ ui.footerLabel.SetText("")
ui.table.SetSelectedFunc(ui.deviceItemSelected)
return nil
@@ -292,7 +292,7 @@ func (ui *UI) showFile() *tview.TextView {
grid.AddItem(ui.header, 0, 0, 1, 1, 0, 0, false).
AddItem(ui.currentDirLabel, 1, 0, 1, 1, 0, 0, false).
AddItem(file, 2, 0, 1, 1, 0, 0, true).
- AddItem(ui.footer, 3, 0, 1, 1, 0, 0, false)
+ AddItem(ui.footerLabel, 3, 0, 1, 1, 0, 0, false)
ui.pages.HidePage("background")
ui.pages.AddPage("file", grid, true, true)
diff --git a/tui/filter.go b/tui/filter.go
new file mode 100644
index 0000000..3e8d310
--- /dev/null
+++ b/tui/filter.go
@@ -0,0 +1,50 @@
+package tui
+
+import (
+ "github.com/gdamore/tcell/v2"
+ "github.com/rivo/tview"
+)
+
+func (ui *UI) hideFilterInput() {
+ ui.filterValue = ""
+ ui.footer.Clear()
+ ui.footer.AddItem(ui.footerLabel, 0, 1, false)
+ ui.app.SetFocus(ui.table)
+ ui.filteringInput = nil
+ ui.filtering = false
+}
+
+func (ui *UI) showFilterInput() {
+ if ui.filteringInput == nil {
+ ui.filteringInput = tview.NewInputField()
+
+ if !ui.UseColors {
+ ui.filteringInput.SetFieldBackgroundColor(
+ tcell.NewRGBColor(100, 100, 100),
+ )
+ ui.filteringInput.SetFieldTextColor(
+ tcell.NewRGBColor(255, 255, 255),
+ )
+ }
+
+ ui.filteringInput.SetChangedFunc(func(text string) {
+ ui.filterValue = text
+ ui.showDir()
+ })
+ ui.filteringInput.SetDoneFunc(func(key tcell.Key) {
+ if key == tcell.KeyESC {
+ ui.hideFilterInput()
+ ui.showDir()
+ } else {
+ ui.app.SetFocus(ui.table)
+ ui.filtering = false
+ }
+ })
+
+ ui.footer.Clear()
+ ui.footer.AddItem(ui.filteringInput, 0, 1, true)
+ ui.footer.AddItem(ui.footerLabel, 0, 5, false)
+ }
+ ui.app.SetFocus(ui.filteringInput)
+ ui.filtering = true
+}
diff --git a/tui/filter_test.go b/tui/filter_test.go
new file mode 100644
index 0000000..4e4d160
--- /dev/null
+++ b/tui/filter_test.go
@@ -0,0 +1,115 @@
+package tui
+
+import (
+ "testing"
+
+ "github.com/dundee/gdu/v5/internal/testanalyze"
+ "github.com/dundee/gdu/v5/internal/testapp"
+ "github.com/dundee/gdu/v5/internal/testdir"
+ "github.com/gdamore/tcell/v2"
+ "github.com/rivo/tview"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestFiltering(t *testing.T) {
+ app := testapp.CreateMockedApp(false)
+ ui := CreateUI(app, true, true)
+ ui.Analyzer = &testanalyze.MockedAnalyzer{}
+ ui.done = make(chan struct{})
+ err := ui.AnalyzePath("test_dir", nil)
+ assert.Nil(t, err)
+
+ <-ui.done // wait for analyzer
+
+ for _, f := range ui.app.(*testapp.MockedApp).UpdateDraws {
+ f()
+ }
+
+ ui.showFilterInput()
+ ui.filterValue = ""
+ ui.showDir()
+
+ assert.Contains(t, ui.table.GetCell(0, 0).Text, "aaa") // nothing is filtered
+
+ ui.filterValue = "cc"
+ ui.showDir()
+
+ assert.Contains(t, ui.table.GetCell(0, 0).Text, "ccc") // shows only cccc
+
+ ui.hideFilterInput()
+ ui.showDir()
+
+ assert.Contains(t, ui.table.GetCell(0, 0).Text, "aaa") // filtering reset
+}
+
+func TestSwitchToTable(t *testing.T) {
+ fin := testdir.CreateTestDir()
+ defer fin()
+
+ app := testapp.CreateMockedApp(false)
+ ui := CreateUI(app, false, true)
+ ui.done = make(chan struct{})
+ err := ui.AnalyzePath("test_dir", nil)
+ assert.Nil(t, err)
+
+ <-ui.done // wait for analyzer
+
+ for _, f := range ui.app.(*testapp.MockedApp).UpdateDraws {
+ f()
+ }
+
+ ui.keyPressed(tcell.NewEventKey(tcell.KeyRune, '/', 0)) // open filtering input
+ handler := ui.filteringInput.InputHandler()
+ handler(tcell.NewEventKey(tcell.KeyRune, 'n', 0), func(p tview.Primitive) {})
+ handler(tcell.NewEventKey(tcell.KeyRune, 'e', 0), func(p tview.Primitive) {})
+ handler(tcell.NewEventKey(tcell.KeyRune, 's', 0), func(p tview.Primitive) {})
+
+ ui.table.Select(0, 0)
+ ui.keyPressed(tcell.NewEventKey(tcell.KeyRight, 'l', 0)) // we are filtering, should do nothing
+
+ assert.Contains(t, ui.table.GetCell(0, 0).Text, "nested")
+
+ handler(
+ tcell.NewEventKey(tcell.KeyTAB, ' ', 0), func(p tview.Primitive) {},
+ ) // switch focus to table
+ ui.keyPressed(tcell.NewEventKey(tcell.KeyTAB, ' ', 0)) // switch back to input
+ handler(
+ tcell.NewEventKey(tcell.KeyEnter, ' ', 0), func(p tview.Primitive) {},
+ ) // switch back to table
+
+ ui.keyPressed(tcell.NewEventKey(tcell.KeyRight, 'l', 0)) // open nested dir
+
+ assert.Contains(t, ui.table.GetCell(1, 0).Text, "subnested")
+ assert.Empty(t, ui.filterValue) // filtering reset
+}
+
+func TestExitFiltering(t *testing.T) {
+ fin := testdir.CreateTestDir()
+ defer fin()
+
+ app := testapp.CreateMockedApp(false)
+ ui := CreateUI(app, true, true)
+ ui.done = make(chan struct{})
+ err := ui.AnalyzePath("test_dir", nil)
+ assert.Nil(t, err)
+
+ <-ui.done // wait for analyzer
+
+ for _, f := range ui.app.(*testapp.MockedApp).UpdateDraws {
+ f()
+ }
+
+ ui.keyPressed(tcell.NewEventKey(tcell.KeyRune, '/', 0)) // open filtering input
+ handler := ui.filteringInput.InputHandler()
+ ui.filterValue = "xxx"
+ ui.showDir()
+
+ assert.Equal(t, ui.table.GetCell(0, 0).Text, "") // nothing is filtered
+
+ handler(
+ tcell.NewEventKey(tcell.KeyEsc, ' ', 0), func(p tview.Primitive) {},
+ ) // exit filtering
+
+ assert.Contains(t, ui.table.GetCell(0, 0).Text, "nested")
+ assert.Empty(t, ui.filterValue) // filtering reset
+}
diff --git a/tui/keys.go b/tui/keys.go
index 93a0520..1d0692f 100644
--- a/tui/keys.go
+++ b/tui/keys.go
@@ -9,6 +9,9 @@ func (ui *UI) keyPressed(key *tcell.EventKey) *tcell.EventKey {
if ui.pages.HasPage("file") {
return key // send event to primitive
}
+ if ui.filtering {
+ return key
+ }
if key.Key() == tcell.KeyEsc || key.Rune() == 'q' {
if ui.pages.HasPage("help") {
@@ -37,6 +40,7 @@ func (ui *UI) keyPressed(key *tcell.EventKey) *tcell.EventKey {
ui.pages.HasPage("progress") ||
ui.pages.HasPage("deleting") ||
ui.pages.HasPage("emptying") ||
+ ui.pages.HasPage("help") ||
ui.pages.HasPage("info") {
return key
}
@@ -51,6 +55,12 @@ func (ui *UI) keyPressed(key *tcell.EventKey) *tcell.EventKey {
return nil
}
+ if key.Key() == tcell.KeyTab && ui.filteringInput != nil {
+ ui.filtering = true
+ ui.app.SetFocus(ui.filteringInput)
+ return nil
+ }
+
switch key.Rune() {
case 'd':
ui.handleDelete(false)
@@ -84,6 +94,8 @@ func (ui *UI) keyPressed(key *tcell.EventKey) *tcell.EventKey {
ui.setSorting("itemCount")
case 'n':
ui.setSorting("name")
+ case '/':
+ ui.showFilterInput()
default:
return key
}
diff --git a/tui/tui.go b/tui/tui.go
index c95d474..ab9bd51 100644
--- a/tui/tui.go
+++ b/tui/tui.go
@@ -22,6 +22,7 @@ const helpText = ` [::b]up/down, k/j [white:black:-]Move cursor up/down
[::b]left, h [white:black:-]Go to parent directory
[::b]r [white:black:-]Rescan current directory
+ [::b]/ [white:black:-]Search items by name
[::b]a [white:black:-]Toggle between showing disk usage and apparent size
[::b]c [white:black:-]Show/hide file count
[::b]q [white:black:-]Quit gdu
@@ -43,12 +44,14 @@ type UI struct {
*common.UI
app common.TermApplication
header *tview.TextView
- footer *tview.TextView
+ footer *tview.Flex
+ footerLabel *tview.TextView
currentDirLabel *tview.TextView
pages *tview.Pages
progress *tview.TextView
help *tview.Flex
table *tview.Table
+ filteringInput *tview.InputField
currentDir *analyze.Dir
devices []*device.Device
topDir *analyze.Dir
@@ -56,6 +59,8 @@ type UI struct {
currentDirPath string
askBeforeDelete bool
showItemCount bool
+ filtering bool
+ filterValue string
sortBy string
sortOrder string
done chan struct{}
@@ -118,10 +123,13 @@ func CreateUI(app common.TermApplication, useColors bool, showApparentSize bool)
Background(tcell.ColorGray).Bold(true))
}
- ui.footer = tview.NewTextView().SetDynamicColors(true)
- ui.footer.SetTextColor(textColor)
- ui.footer.SetBackgroundColor(textBgColor)
- ui.footer.SetText(" No items to display. ")
+ ui.footerLabel = tview.NewTextView().SetDynamicColors(true)
+ ui.footerLabel.SetTextColor(textColor)
+ ui.footerLabel.SetBackgroundColor(textBgColor)
+ ui.footerLabel.SetText(" No items to display. ")
+
+ ui.footer = tview.NewFlex()
+ ui.footer.AddItem(ui.footerLabel, 0, 1, false)
grid := tview.NewGrid().SetRows(1, 1, 0, 1).SetColumns(0)
grid.AddItem(ui.header, 0, 0, 1, 1, 0, 0, false).
@@ -152,6 +160,12 @@ func (ui *UI) rescanDir() {
}
func (ui *UI) showDir() {
+ var (
+ totalUsage int64
+ totalSize int64
+ itemCount int
+ )
+
ui.currentDirPath = ui.currentDir.GetPath()
ui.currentDirLabel.SetText("[::b] --- " +
strings.TrimPrefix(ui.currentDirPath, build.RootPathPrefix) +
@@ -171,6 +185,17 @@ func (ui *UI) showDir() {
ui.sortItems()
for i, item := range ui.currentDir.Files {
+ if ui.filterValue != "" && !strings.Contains(
+ strings.ToLower(item.GetName()),
+ strings.ToLower(ui.filterValue),
+ ) {
+ continue
+ }
+
+ totalUsage += item.GetUsage()
+ totalSize += item.GetSize()
+ itemCount += item.GetItemCount()
+
cell := tview.NewTableCell(ui.formatFileRow(item))
cell.SetStyle(tcell.Style{}.Foreground(tcell.ColorDefault))
cell.SetReference(ui.currentDir.Files[i])
@@ -188,20 +213,23 @@ func (ui *UI) showDir() {
footerTextColor = "[black:white:-]"
}
- ui.footer.SetText(
+ ui.footerLabel.SetText(
" Total disk usage: " +
footerNumberColor +
- ui.formatSize(ui.currentDir.Usage, true, false) +
+ ui.formatSize(totalUsage, true, false) +
" Apparent size: " +
footerNumberColor +
- ui.formatSize(ui.currentDir.Size, true, false) +
- " Items: " + footerNumberColor + fmt.Sprint(ui.currentDir.ItemCount) +
+ ui.formatSize(totalSize, true, false) +
+ " Items: " + footerNumberColor + fmt.Sprint(itemCount) +
footerTextColor +
" Sorting by: " + ui.sortBy + " " + ui.sortOrder)
ui.table.Select(0, 0)
ui.table.ScrollToBeginning()
- ui.app.SetFocus(ui.table)
+
+ if !ui.filtering {
+ ui.app.SetFocus(ui.table)
+ }
}
func (ui *UI) sortItems() {
@@ -244,6 +272,7 @@ func (ui *UI) fileItemSelected(row, column int) {
}
ui.currentDir = selectedDir.(*analyze.Dir)
+ ui.hideFilterInput()
ui.showDir()
if selectedDir == origDir.Parent {
@@ -391,7 +420,7 @@ func (ui *UI) showHelp() {
AddItem(nil, 0, 1, false).
AddItem(tview.NewFlex().SetDirection(tview.FlexRow).
AddItem(nil, 0, 1, false).
- AddItem(text, 26, 1, false).
+ AddItem(text, 27, 1, false).
AddItem(nil, 0, 1, false), 80, 1, false).
AddItem(nil, 0, 1, false)
diff --git a/tui/tui_test.go b/tui/tui_test.go
index 1620ed9..c34b16e 100644
--- a/tui/tui_test.go
+++ b/tui/tui_test.go
@@ -48,12 +48,12 @@ func TestFooter(t *testing.T) {
ui.showDir()
ui.pages.HidePage("progress")
- ui.footer.Draw(simScreen)
+ ui.footerLabel.Draw(simScreen)
simScreen.Show()
b, _, _ := simScreen.GetContents()
- text := []byte(" Total disk usage: 4.0 KiB Apparent size: 5 B Items: 2")
+ text := []byte(" Total disk usage: 4.0 KiB Apparent size: 2 B Items: 1")
for i, r := range b {
if i >= len(text) {
break
@@ -85,9 +85,11 @@ func TestHelp(t *testing.T) {
ui.help.Draw(simScreen)
simScreen.Show()
+ // printScreen(simScreen)
+
b, _, _ := simScreen.GetContents()
- cells := b[306 : 306+9]
+ cells := b[356 : 356+9]
text := []byte("directory")
for i, r := range cells {
@@ -104,9 +106,11 @@ func TestHelpBw(t *testing.T) {
ui.help.Draw(simScreen)
simScreen.Show()
+ // printScreen(simScreen)
+
b, _, _ := simScreen.GetContents()
- cells := b[306 : 306+9]
+ cells := b[356 : 356+9]
text := []byte("directory")
for i, r := range cells {
@@ -319,15 +323,6 @@ func TestMin(t *testing.T) {
// }
// }
-// func analyzeMock(path string, progress *analyze.CurrentProgress, ignore analyze.ShouldDirBeIgnored) *analyze.Dir {
-// return &analyze.Dir{
-// File: &analyze.File{
-// Name: "xxx",
-// },
-// BasePath: ".",
-// }
-// }
-
func getDevicesInfoMock() device.DevicesInfoGetter {
item := &device.Device{
Name: "/dev/root",