diff options
author | Daniel Milde <daniel@milde.cz> | 2021-08-01 12:01:06 +0200 |
---|---|---|
committer | Daniel Milde <daniel@milde.cz> | 2021-08-01 12:01:06 +0200 |
commit | 8ff4249b9faad4a9b343568e01cb2879c3126529 (patch) | |
tree | 50c78bd5b60e4694d2a2f65b57faa432e56cb192 | |
parent | 91acb896ab2e74490cb222f88924c6a866524ab2 (diff) |
closes #80
-rw-r--r-- | tui/actions.go | 4 | ||||
-rw-r--r-- | tui/filter.go | 50 | ||||
-rw-r--r-- | tui/filter_test.go | 115 | ||||
-rw-r--r-- | tui/keys.go | 12 | ||||
-rw-r--r-- | tui/tui.go | 51 | ||||
-rw-r--r-- | tui/tui_test.go | 21 |
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 } @@ -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", |