diff --git a/models/bolt.go b/models/bolt.go index d1dbe3f..52da85e 100644 --- a/models/bolt.go +++ b/models/bolt.go @@ -108,12 +108,12 @@ func (bd *BoltDB) GetPairFromPath(path []string) (*BoltPair, error) { return p, err } -func (bd *BoltDB) GetVisibleItemCount(path []string) (int, error) { +func (bd *BoltDB) GetVisibleItemCount(path []string, search string) (int, error) { vis := 0 var retErr error if len(path) == 0 { for i := range bd.buckets { - n, err := bd.GetVisibleItemCount(bd.buckets[i].GetPath()) + n, err := bd.GetVisibleItemCount(bd.buckets[i].GetPath(), search) if err != nil { return 0, err } @@ -126,13 +126,13 @@ func (bd *BoltDB) GetVisibleItemCount(path []string) (int, error) { } // 1 for the bucket vis++ - if b.expanded { - // This bucket is expanded, add up it's children + if b.expanded || search != "" { + // This bucket is expanded (or we're searching), add up it's children // * 1 for each pair vis += len(b.pairs) // * recurse for buckets for i := range b.buckets { - n, err := bd.GetVisibleItemCount(b.buckets[i].GetPath()) + n, err := bd.GetVisibleItemCount(b.buckets[i].GetPath(), search) if err != nil { return 0, err } @@ -143,12 +143,12 @@ func (bd *BoltDB) GetVisibleItemCount(path []string) (int, error) { return vis, retErr } -func (bd *BoltDB) BuildVisiblePathSlice(filter string) ([][]string, error) { +func (bd *BoltDB) BuildVisiblePathSlice(search string) ([][]string, error) { var retSlice [][]string var retErr error // The root path, recurse for root buckets for i := range bd.buckets { - bktS, bktErr := bd.buckets[i].BuildVisiblePathSlice([]string{}, filter) + bktS, bktErr := bd.buckets[i].BuildVisiblePathSlice([]string{}, search) if bktErr == nil { retSlice = append(retSlice, bktS...) } else { @@ -159,8 +159,8 @@ func (bd *BoltDB) BuildVisiblePathSlice(filter string) ([][]string, error) { return retSlice, retErr } -func (bd *BoltDB) IsVisiblePath(path []string, filter string) bool { - visPaths, err := bd.BuildVisiblePathSlice(filter) +func (bd *BoltDB) IsVisiblePath(path []string, search string) bool { + visPaths, err := bd.BuildVisiblePathSlice(search) if err != nil { return false } @@ -181,8 +181,8 @@ func (bd *BoltDB) IsVisiblePath(path []string, filter string) bool { } return false } -func (bd *BoltDB) GetPrevVisiblePath(path []string, filter string) []string { - visPaths, err := bd.BuildVisiblePathSlice(filter) +func (bd *BoltDB) GetPrevVisiblePath(path []string, search string) []string { + visPaths, err := bd.BuildVisiblePathSlice(search) if path == nil { if len(visPaths) > 0 { return visPaths[len(visPaths)-1] @@ -205,8 +205,8 @@ func (bd *BoltDB) GetPrevVisiblePath(path []string, filter string) []string { } return nil } -func (bd *BoltDB) GetNextVisiblePath(path []string, filter string) []string { - visPaths, err := bd.BuildVisiblePathSlice(filter) +func (bd *BoltDB) GetNextVisiblePath(path []string, search string) []string { + visPaths, err := bd.BuildVisiblePathSlice(search) if path == nil { if len(visPaths) > 0 { return visPaths[0] diff --git a/models/bucket.go b/models/bucket.go index 24da0c8..dd7073d 100644 --- a/models/bucket.go +++ b/models/bucket.go @@ -38,14 +38,16 @@ func (b *BoltBucket) GetPath() []string { buildVisiblePathSlice builds a slice of string slices containing all visible paths in this bucket The passed prefix is the path leading to the current bucket */ -func (b *BoltBucket) BuildVisiblePathSlice(prefix []string, filter string) ([][]string, error) { +func (b *BoltBucket) BuildVisiblePathSlice(prefix []string, search string) ([][]string, error) { var retSlice [][]string var retErr error - retSlice = append(retSlice, append(prefix, b.name)) - if b.expanded { + if search == "" || strings.Contains(b.name, search) { + retSlice = append(retSlice, append(prefix, b.name)) + } + if b.expanded || search != "" { // Add subbuckets for i := range b.buckets { - bktS, bktErr := b.buckets[i].BuildVisiblePathSlice(append(prefix, b.name), filter) + bktS, bktErr := b.buckets[i].BuildVisiblePathSlice(append(prefix, b.name), search) if bktErr != nil { return retSlice, bktErr } @@ -53,7 +55,7 @@ func (b *BoltBucket) BuildVisiblePathSlice(prefix []string, filter string) ([][] } // Add pairs for i := range b.pairs { - if filter != "" && !strings.Contains(b.pairs[i].key, filter) { + if search != "" && !strings.Contains(b.pairs[i].key, search) { continue } retSlice = append(retSlice, append(append(prefix, b.name), b.pairs[i].key)) diff --git a/ui/bolt_tree_pane.go b/ui/bolt_tree_pane.go index b09addf..519948c 100644 --- a/ui/bolt_tree_pane.go +++ b/ui/bolt_tree_pane.go @@ -6,6 +6,7 @@ import ( "git.bullercodeworks.com/brian/boltbrowser/models" "git.bullercodeworks.com/brian/boltbrowser/util" "git.bullercodeworks.com/brian/wandle" + "git.bullercodeworks.com/brian/widdles" "github.com/nsf/termbox-go" ) @@ -21,8 +22,7 @@ type BoltTreePane struct { pathBuffer [][]string currentPath []string currentPathIdx int - filter string - filterMode bool + search *widdles.Field insertPairCmd func(*models.BoltBucket) wandle.Cmd insertBucketCmd func(*models.BoltBucket) wandle.Cmd @@ -46,6 +46,7 @@ func NewBoltTreePane(x, y, w, h int) *BoltTreePane { return &BoltTreePane{ x: x, y: y, width: w, height: h, + search: widdles.NewField("Search", "", x, y+h), } } @@ -53,16 +54,18 @@ func (w *BoltTreePane) Init() wandle.Cmd { if w.db != nil { w.refreshDBAndBuffer() } + w.search.SetWidth(w.width) return nil } func (w *BoltTreePane) SetDetailPane(pane *BoltDetailPane) { w.detailPane = pane } -func (w *BoltTreePane) Measure() {} +func (w *BoltTreePane) Measure() { +} func (w *BoltTreePane) Update(msg wandle.Msg) wandle.Cmd { if w.db == nil { return nil } if len(w.currentPath) == 0 { - w.currentPath = w.db.GetNextVisiblePath(nil, w.filter) + w.currentPath = w.db.GetNextVisiblePath(nil, w.search.GetValue()) } switch msg := msg.(type) { case BrowseMsg: @@ -88,48 +91,65 @@ func (w *BoltTreePane) Update(msg wandle.Msg) wandle.Cmd { return nil } func (w *BoltTreePane) handleTermboxEvent(msg termbox.Event) wandle.Cmd { - switch msg.Type { - case termbox.EventKey: - if msg.Ch == 'g' { - return w.jumpToStart - } else if msg.Ch == 'G' { - return w.jumpToEnd - } else if msg.Key == termbox.KeyCtrlF { - return w.pageDown - } else if msg.Key == termbox.KeyCtrlB { - return w.pageUp - } else if msg.Ch == 'j' || msg.Key == termbox.KeyArrowDown { - return w.moveCursorDown - } else if msg.Ch == 'k' || msg.Key == termbox.KeyArrowUp { - return w.moveCursorUp - } else if msg.Key == termbox.KeyCtrlR { - return w.refreshDBAndBuffer - } else if msg.Ch == 'p' { - return w.insertPairAtCurrent - } else if msg.Ch == 'P' { - return w.insertPairAtParent - } else if msg.Ch == 'b' { - return w.insertBucketAtCurrent - } else if msg.Ch == 'B' { - return w.insertBucketAtParent - } else if msg.Ch == 'e' { - return w.editPairValue - } else if msg.Ch == 'r' { - return w.renameBucketOrPair - } else if msg.Key == termbox.KeyEnter { - return w.handleEnterPressed - } else if msg.Ch == 'l' || msg.Key == termbox.KeyArrowRight { - return w.handleRightPressed - } else if msg.Ch == 'h' || msg.Key == termbox.KeyArrowLeft { - return w.handleLeftPressed - } else if msg.Ch == 'D' { - return w.handleDelete - } else if msg.Ch == 'x' { - // TODO: Export Value - } else if msg.Ch == 'X' { - // TODO: Export Key/Value (or Bucket) as JSON - } else if msg.Ch == '/' { - // TODO: Start Filtering + if w.search.IsActive() { + if msg.Type == termbox.EventKey { + if msg.Key == termbox.KeyEnter { + return func() wandle.Msg { + w.search.SetActive(false) + return nil + } + } else { + w.setStatus(w.search.Message, time.Second) + return w.search.Update(msg) + } + } + } else { + switch msg.Type { + case termbox.EventKey: + if msg.Ch == 'g' { + return w.jumpToStart + } else if msg.Ch == 'G' { + return w.jumpToEnd + } else if msg.Key == termbox.KeyCtrlF { + return w.pageDown + } else if msg.Key == termbox.KeyCtrlB { + return w.pageUp + } else if msg.Ch == 'j' || msg.Key == termbox.KeyArrowDown { + return w.moveCursorDown + } else if msg.Ch == 'k' || msg.Key == termbox.KeyArrowUp { + return w.moveCursorUp + } else if msg.Key == termbox.KeyCtrlR { + return w.refreshDBAndBuffer + } else if msg.Ch == 'p' { + return w.insertPairAtCurrent + } else if msg.Ch == 'P' { + return w.insertPairAtParent + } else if msg.Ch == 'b' { + return w.insertBucketAtCurrent + } else if msg.Ch == 'B' { + return w.insertBucketAtParent + } else if msg.Ch == 'e' { + return w.editPairValue + } else if msg.Ch == 'r' { + return w.renameBucketOrPair + } else if msg.Key == termbox.KeyEnter { + return w.handleEnterPressed + } else if msg.Ch == 'l' || msg.Key == termbox.KeyArrowRight { + return w.handleRightPressed + } else if msg.Ch == 'h' || msg.Key == termbox.KeyArrowLeft { + return w.handleLeftPressed + } else if msg.Ch == 'D' { + return w.handleDelete + } else if msg.Ch == 'x' { + // TODO: Export Value + } else if msg.Ch == 'X' { + // TODO: Export Key/Value (or Bucket) as JSON + } else if msg.Ch == '/' { + return func() wandle.Msg { + w.search.SetActive(true) + return nil + } + } } } return nil @@ -145,6 +165,9 @@ func (w *BoltTreePane) View(style wandle.Style) { wandle.Print(midX, midY, style, txt) return } + if w.search.IsActive() || w.search.GetValue() != "" { + w.search.View(style) + } // Find the cursor in the pane for k, v := range w.pathBuffer { if comparePaths(w.currentPath, v) { @@ -230,9 +253,9 @@ func (w *BoltTreePane) RefreshBuffer() { for i := range buckets { w.buffer = append(w.buffer, buckets[i].Lines()...) } - w.pathBuffer, _ = w.db.BuildVisiblePathSlice(w.filter) + w.pathBuffer, _ = w.db.BuildVisiblePathSlice(w.search.GetValue()) if len(w.currentPath) == 0 { - w.currentPath = w.db.GetNextVisiblePath(nil, w.filter) + w.currentPath = w.db.GetNextVisiblePath(nil, w.search.GetValue()) } } @@ -260,7 +283,7 @@ func (w *BoltTreePane) jumpCursorDown(distance int) { } } if isCurPath { - w.currentPath = w.db.GetNextVisiblePath(nil, w.filter) + w.currentPath = w.db.GetNextVisiblePath(nil, w.search.GetValue()) } w.UpdateDetailPane() } @@ -289,17 +312,17 @@ func (w *BoltTreePane) jumpCursorUp(distance int) { } } if isCurPath { - w.currentPath = w.db.GetNextVisiblePath(nil, w.filter) + w.currentPath = w.db.GetNextVisiblePath(nil, w.search.GetValue()) } w.UpdateDetailPane() } func (w *BoltTreePane) jumpToStart() wandle.Msg { - w.currentPath = w.db.GetNextVisiblePath(nil, w.filter) + w.currentPath = w.db.GetNextVisiblePath(nil, w.search.GetValue()) w.UpdateDetailPane() return nil } func (w *BoltTreePane) jumpToEnd() wandle.Msg { - w.currentPath = w.db.GetPrevVisiblePath(nil, w.filter) + w.currentPath = w.db.GetPrevVisiblePath(nil, w.search.GetValue()) w.UpdateDetailPane() return nil } @@ -314,7 +337,7 @@ func (w *BoltTreePane) pageDown() wandle.Msg { return nil } func (w *BoltTreePane) moveCursorUp() wandle.Msg { - p := w.db.GetPrevVisiblePath(w.currentPath, w.filter) + p := w.db.GetPrevVisiblePath(w.currentPath, w.search.GetValue()) if p != nil { w.currentPath = p } @@ -322,7 +345,7 @@ func (w *BoltTreePane) moveCursorUp() wandle.Msg { return nil } func (w *BoltTreePane) moveCursorDown() wandle.Msg { - p := w.db.GetNextVisiblePath(w.currentPath, w.filter) + p := w.db.GetNextVisiblePath(w.currentPath, w.search.GetValue()) if p != nil { w.currentPath = p } diff --git a/ui/screen_browse.go b/ui/screen_browse.go index ec76f17..fdc0a0c 100644 --- a/ui/screen_browse.go +++ b/ui/screen_browse.go @@ -75,6 +75,7 @@ func (s *browseScreen) Init() wandle.Cmd { } s.dbPath = dbs[dbidx] s.treePane = NewBoltTreePane(0, 3, s.width/2, s.height-6) + s.treePane.Init() s.treePane.SetVisible(true) s.treePane.SetInsertPairCommand(s.insertPair) s.treePane.SetInsertBucketCommand(s.insertBucket) @@ -143,6 +144,9 @@ func (s *browseScreen) handleTermboxEvent(msg termbox.Event) wandle.Cmd { return cmd } + if s.treePane.search.IsActive() { + return s.treePane.Update(msg) + } var cmds []wandle.Cmd cmds = append(cmds, s.treePane.Update(msg)) cmds = append(cmds, s.detailPane.Update(msg))