V3 is in the works
This commit is contained in:
parent
7cc06eeddc
commit
492e3b9547
2
Makefile
2
Makefile
@ -3,7 +3,7 @@ boltbrowser:
|
|||||||
go build -o build/boltbrowser
|
go build -o build/boltbrowser
|
||||||
|
|
||||||
run: ./build/boltbrowser
|
run: ./build/boltbrowser
|
||||||
cd build && ./boltbrowser
|
cd build && ./boltbrowser test.db
|
||||||
|
|
||||||
install:
|
install:
|
||||||
go install
|
go install
|
||||||
|
@ -23,6 +23,7 @@ var rootCmd = &cobra.Command{
|
|||||||
return errors.New("No database given.")
|
return errors.New("No database given.")
|
||||||
}
|
}
|
||||||
viper.Set("dbs", args)
|
viper.Set("dbs", args)
|
||||||
|
viper.Set("dbidx", 0)
|
||||||
if err := ui.NewUi().Start(); err != nil {
|
if err := ui.NewUi().Start(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -40,7 +41,7 @@ func Execute() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.Flags().BoolP("readonly", "ro", false, "Open database in read only mode")
|
rootCmd.Flags().BoolP("readonly", "r", false, "Open database in read only mode")
|
||||||
viper.BindPFlag("readonly", rootCmd.Flags().Lookup("readonly"))
|
viper.BindPFlag("readonly", rootCmd.Flags().Lookup("readonly"))
|
||||||
rootCmd.Flags().Bool("no-value", false, "Do not output values in the left pane")
|
rootCmd.Flags().Bool("no-value", false, "Do not output values in the left pane")
|
||||||
viper.BindPFlag("no-value", rootCmd.Flags().Lookup("no-value"))
|
viper.BindPFlag("no-value", rootCmd.Flags().Lookup("no-value"))
|
||||||
@ -48,7 +49,3 @@ func init() {
|
|||||||
viper.BindPFlag("timeout", rootCmd.Flags().Lookup("timeout"))
|
viper.BindPFlag("timeout", rootCmd.Flags().Lookup("timeout"))
|
||||||
viper.Set("version", AppVersion)
|
viper.Set("version", AppVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
func printUsage() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
4
go.mod
4
go.mod
@ -1,5 +1,9 @@
|
|||||||
module git.bullercodeworks.com/brian/boltbrowser
|
module git.bullercodeworks.com/brian/boltbrowser
|
||||||
|
|
||||||
|
replace git.bullercodeworks.com/brian/wandle => /home/brbuller/Development/go/src/git.bullercodeworks.com/brian/wandle
|
||||||
|
|
||||||
|
replace git.bullercodeworks.com/brian/widdles => /home/brbuller/Development/go/src/git.bullercodeworks.com/brian/widdles
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.bullercodeworks.com/brian/wandle v1.0.1
|
git.bullercodeworks.com/brian/wandle v1.0.1
|
||||||
git.bullercodeworks.com/brian/widdles v1.1.0
|
git.bullercodeworks.com/brian/widdles v1.1.0
|
||||||
|
@ -18,6 +18,12 @@ type BoltDB struct {
|
|||||||
buckets []BoltBucket
|
buckets []BoltBucket
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewBoltDB(db *bolt.DB) *BoltDB {
|
||||||
|
b := &BoltDB{db: db}
|
||||||
|
b.RefreshDatabase()
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
func (bd *BoltDB) RefreshDatabase() {
|
func (bd *BoltDB) RefreshDatabase() {
|
||||||
// Reload the database from the file
|
// Reload the database from the file
|
||||||
bd.buckets = []BoltBucket{}
|
bd.buckets = []BoltBucket{}
|
||||||
@ -47,6 +53,8 @@ func (bd *BoltDB) RefreshDatabase() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (bd *BoltDB) GetBuckets() []BoltBucket { return bd.buckets }
|
||||||
|
|
||||||
func (bd *BoltDB) GetGenericFromPath(path []string) (*BoltBucket, *BoltPair, error) {
|
func (bd *BoltDB) GetGenericFromPath(path []string) (*BoltBucket, *BoltPair, error) {
|
||||||
// Check if 'path' leads to a pair
|
// Check if 'path' leads to a pair
|
||||||
p, err := bd.GetPairFromPath(path)
|
p, err := bd.GetPairFromPath(path)
|
||||||
@ -592,3 +600,11 @@ func (bd *BoltDB) AddBucketFromBoltBucket(path []string, bb *BoltBucket) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (bd BoltDB) Lines() []string {
|
||||||
|
var ret []string
|
||||||
|
for i := range bd.buckets {
|
||||||
|
ret = append(ret, bd.buckets[i].Lines()...)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
@ -2,6 +2,7 @@ package models
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -92,3 +93,26 @@ func (b *BoltBucket) GetPair(k string) (*BoltPair, error) {
|
|||||||
}
|
}
|
||||||
return nil, errors.New("Pair Not Found")
|
return nil, errors.New("Pair Not Found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b BoltBucket) Lines() []string {
|
||||||
|
var ret []string
|
||||||
|
bktPrefix := strings.Repeat(" ", len(b.GetPath())*2)
|
||||||
|
wrk := "+ "
|
||||||
|
if b.expanded {
|
||||||
|
wrk = "- "
|
||||||
|
}
|
||||||
|
wrk = fmt.Sprintf("%s%s%s", bktPrefix, wrk, b.name)
|
||||||
|
ret = append(ret, wrk)
|
||||||
|
if b.expanded {
|
||||||
|
for i := range b.buckets {
|
||||||
|
ret = append(ret, b.buckets[i].Lines()...)
|
||||||
|
}
|
||||||
|
for _, bp := range b.pairs {
|
||||||
|
prPrefix := strings.Repeat(" ", len(bp.GetPath())*2)
|
||||||
|
ret = append(ret, fmt.Sprintf("%s%s", prPrefix, bp.String()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b BoltBucket) IsExpanded() bool { return b.expanded }
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
BoltPair is just a struct representation of a Pair in the Bolt DB
|
BoltPair is just a struct representation of a Pair in the Bolt DB
|
||||||
*/
|
*/
|
||||||
@ -15,3 +17,7 @@ GetPath Returns the path of the BoltPair
|
|||||||
func (p *BoltPair) GetPath() []string {
|
func (p *BoltPair) GetPath() []string {
|
||||||
return append(p.parent.GetPath(), p.key)
|
return append(p.parent.GetPath(), p.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p BoltPair) String() string {
|
||||||
|
return fmt.Sprintf("%s: %s", p.key, p.val)
|
||||||
|
}
|
||||||
|
232
ui/bolt_tree_pane.go
Normal file
232
ui/bolt_tree_pane.go
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.bullercodeworks.com/brian/boltbrowser/models"
|
||||||
|
"git.bullercodeworks.com/brian/wandle"
|
||||||
|
"github.com/nsf/termbox-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BoltTreePane struct {
|
||||||
|
x, y int
|
||||||
|
width int
|
||||||
|
height int
|
||||||
|
scrollRow int
|
||||||
|
active bool
|
||||||
|
|
||||||
|
buffer []string
|
||||||
|
currentPath []string
|
||||||
|
currentPathIdx int
|
||||||
|
filter string
|
||||||
|
|
||||||
|
db *models.BoltDB
|
||||||
|
|
||||||
|
message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBoltTreePane(x, y, w, h int) *BoltTreePane {
|
||||||
|
return &BoltTreePane{
|
||||||
|
x: x, y: y,
|
||||||
|
width: w, height: h,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *BoltTreePane) Init() wandle.Cmd {
|
||||||
|
if w.db != nil {
|
||||||
|
w.buffer = w.db.Lines()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (w *BoltTreePane) Update(msg wandle.Msg) wandle.Cmd {
|
||||||
|
if len(w.currentPath) == 0 {
|
||||||
|
w.currentPath = w.db.GetNextVisiblePath(nil, w.filter)
|
||||||
|
}
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case termbox.Event:
|
||||||
|
return w.handleTermboxEvent(msg)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (w *BoltTreePane) handleTermboxEvent(msg termbox.Event) wandle.Cmd {
|
||||||
|
switch msg.Type {
|
||||||
|
case termbox.EventKey:
|
||||||
|
w.message = "Key Pressed: " + wandle.KeyToString(msg)
|
||||||
|
if msg.Ch == 'g' {
|
||||||
|
// Jump to Beginning
|
||||||
|
w.currentPath = w.db.GetNextVisiblePath(nil, w.filter)
|
||||||
|
return func() wandle.Msg { return nil }
|
||||||
|
} else if msg.Ch == 'G' {
|
||||||
|
// Jump to End
|
||||||
|
w.currentPath = w.db.GetPrevVisiblePath(nil, w.filter)
|
||||||
|
return func() wandle.Msg { return nil }
|
||||||
|
} else if msg.Key == termbox.KeyCtrlF {
|
||||||
|
w.jumpCursorDown(w.height / 2)
|
||||||
|
return func() wandle.Msg { return nil }
|
||||||
|
} else if msg.Key == termbox.KeyCtrlB {
|
||||||
|
w.jumpCursorDown(w.height / 2)
|
||||||
|
return func() wandle.Msg { return nil }
|
||||||
|
} else if msg.Ch == 'j' || msg.Key == termbox.KeyArrowDown {
|
||||||
|
w.moveCursorDown()
|
||||||
|
return func() wandle.Msg { return nil }
|
||||||
|
} else if msg.Ch == 'k' || msg.Key == termbox.KeyArrowUp {
|
||||||
|
w.moveCursorUp()
|
||||||
|
return func() wandle.Msg { return nil }
|
||||||
|
} else if msg.Ch == 'p' {
|
||||||
|
|
||||||
|
return func() wandle.Msg { return nil }
|
||||||
|
} else if msg.Ch == 'P' {
|
||||||
|
|
||||||
|
return func() wandle.Msg { return nil }
|
||||||
|
} else if msg.Ch == 'b' {
|
||||||
|
|
||||||
|
return func() wandle.Msg { return nil }
|
||||||
|
} else if msg.Ch == 'B' {
|
||||||
|
|
||||||
|
return func() wandle.Msg { return nil }
|
||||||
|
} else if msg.Ch == 'l' || msg.Key == termbox.KeyArrowRight || msg.Key == termbox.KeyEnter {
|
||||||
|
b, p, _ := w.db.GetGenericFromPath(w.currentPath)
|
||||||
|
// Select the current item
|
||||||
|
if b != nil {
|
||||||
|
w.db.ToggleOpenBucket(w.currentPath)
|
||||||
|
} else if p != nil {
|
||||||
|
// Edit the pair
|
||||||
|
}
|
||||||
|
return func() wandle.Msg { return nil }
|
||||||
|
} else if msg.Ch == 'h' || msg.Key == termbox.KeyArrowLeft {
|
||||||
|
b, _, e := w.db.GetGenericFromPath(w.currentPath)
|
||||||
|
if e == nil && b != nil && b.IsExpanded() {
|
||||||
|
w.db.CloseBucket(w.currentPath)
|
||||||
|
} else {
|
||||||
|
if len(w.currentPath) > 1 {
|
||||||
|
parent, err := w.db.GetBucketFromPath(w.currentPath[:len(w.currentPath)-1])
|
||||||
|
if err == nil {
|
||||||
|
w.db.CloseBucket(parent.GetPath())
|
||||||
|
w.currentPath = parent.GetPath()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return func() wandle.Msg { return nil }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (w *BoltTreePane) View(style wandle.Style) {
|
||||||
|
treeOffset := 0
|
||||||
|
maxCursor := w.height * 2 / 3
|
||||||
|
if w.scrollRow > maxCursor {
|
||||||
|
treeOffset = w.scrollRow - maxCursor
|
||||||
|
}
|
||||||
|
if len(w.buffer) > 0 {
|
||||||
|
for k, v := range w.buffer[treeOffset:] {
|
||||||
|
wandle.Print(w.x, (w.y + k - 1), style, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wd, h := termbox.Size()
|
||||||
|
wandle.Print(wd, h-1, style, w.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *BoltTreePane) IsActive() bool { return w.active }
|
||||||
|
func (w *BoltTreePane) SetActive(b bool) { w.active = b }
|
||||||
|
func (w *BoltTreePane) Focusable() bool { return true }
|
||||||
|
func (w *BoltTreePane) SetX(x int) { w.x = x }
|
||||||
|
func (w *BoltTreePane) SetY(y int) { w.y = y }
|
||||||
|
func (w *BoltTreePane) GetX() int { return w.x }
|
||||||
|
func (w *BoltTreePane) GetY() int { return w.y }
|
||||||
|
func (w *BoltTreePane) SetHeight(h int) { w.height = h }
|
||||||
|
func (w *BoltTreePane) GetHeight() int { return w.height }
|
||||||
|
func (w *BoltTreePane) SetWidth(wdt int) { w.height = wdt }
|
||||||
|
func (w *BoltTreePane) GetWidth() int { return w.width }
|
||||||
|
|
||||||
|
func (w *BoltTreePane) SetDB(db *models.BoltDB) {
|
||||||
|
w.db = db
|
||||||
|
w.RefreshBuffer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *BoltTreePane) RefreshBuffer() {
|
||||||
|
buckets := w.db.GetBuckets()
|
||||||
|
for i := range buckets {
|
||||||
|
w.buffer = append(w.buffer, buckets[i].Lines()...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *BoltTreePane) jumpCursorDown(distance int) {
|
||||||
|
paths, err := w.db.BuildVisiblePathSlice(w.filter)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
findPath := w.currentPath
|
||||||
|
for idx, pth := range paths {
|
||||||
|
startJump := true
|
||||||
|
for i := range pth {
|
||||||
|
if len(w.currentPath) > i && pth[i] != w.currentPath[i] {
|
||||||
|
startJump = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if startJump {
|
||||||
|
distance--
|
||||||
|
if distance == 0 {
|
||||||
|
w.currentPath = paths[len(paths)-1-idx]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isCurPath := true
|
||||||
|
for i := range w.currentPath {
|
||||||
|
if w.currentPath[i] != findPath[i] {
|
||||||
|
isCurPath = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isCurPath {
|
||||||
|
w.currentPath = w.db.GetNextVisiblePath(nil, w.filter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (w *BoltTreePane) jumpCursorUp(distance int) {
|
||||||
|
paths, err := w.db.BuildVisiblePathSlice(w.filter)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
findPath := w.currentPath
|
||||||
|
for idx, pth := range paths {
|
||||||
|
startJump := true
|
||||||
|
for i := range pth {
|
||||||
|
if len(w.currentPath) > i && pth[i] != w.currentPath[i] {
|
||||||
|
startJump = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if startJump {
|
||||||
|
distance--
|
||||||
|
if distance == 0 {
|
||||||
|
w.currentPath = paths[idx]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isCurPath := true
|
||||||
|
for i := range w.currentPath {
|
||||||
|
if w.currentPath[i] != findPath[i] {
|
||||||
|
isCurPath = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isCurPath {
|
||||||
|
w.currentPath = w.db.GetNextVisiblePath(nil, w.filter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (w *BoltTreePane) moveCursorUp() {
|
||||||
|
p := w.db.GetPrevVisiblePath(w.currentPath, w.filter)
|
||||||
|
if p != nil {
|
||||||
|
w.currentPath = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (w *BoltTreePane) moveCursorDown() {
|
||||||
|
p := w.db.GetNextVisiblePath(w.currentPath, w.filter)
|
||||||
|
if p != nil {
|
||||||
|
w.currentPath = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *BoltTreePane) comparePaths(p1, p2 []string) bool {
|
||||||
|
return strings.Join(p1, " → ") == strings.Join(p2, " → ")
|
||||||
|
}
|
@ -83,7 +83,9 @@ func (s *aboutScreen) handleAboutMsg(msg AboutMsg) wandle.Cmd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *aboutScreen) handleTermboxEvent(msg termbox.Event) wandle.Cmd {
|
func (s *aboutScreen) handleTermboxEvent(msg termbox.Event) wandle.Cmd {
|
||||||
|
if msg.Type == termbox.EventKey {
|
||||||
|
return wandle.SwitchScreenCmd(s.ui.browseScreen)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,15 +1,22 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.bullercodeworks.com/brian/boltbrowser/models"
|
||||||
"git.bullercodeworks.com/brian/wandle"
|
"git.bullercodeworks.com/brian/wandle"
|
||||||
"git.bullercodeworks.com/brian/widdles"
|
"git.bullercodeworks.com/brian/widdles"
|
||||||
"github.com/nsf/termbox-go"
|
"github.com/nsf/termbox-go"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
bolt "go.etcd.io/bbolt"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
//BS_CmdGotoAbout = BrowseId | iota
|
BS_CmdRefresh = BrowseId | iota
|
||||||
|
BS_CmdDBTimeout
|
||||||
)
|
)
|
||||||
|
|
||||||
type BrowseMsg struct {
|
type BrowseMsg struct {
|
||||||
@ -21,24 +28,45 @@ type BrowseMsg struct {
|
|||||||
type browseScreen struct {
|
type browseScreen struct {
|
||||||
ui *Ui
|
ui *Ui
|
||||||
dbPath string
|
dbPath string
|
||||||
|
db *models.BoltDB
|
||||||
|
|
||||||
menu *widdles.TopMenu
|
status *widdles.Text
|
||||||
status *widdles.Text
|
|
||||||
statusTimeout time.Duration
|
treePane *BoltTreePane
|
||||||
statusTime time.Time
|
rightPane ViewPort
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBrowseScreen(u *Ui) *browseScreen {
|
func NewBrowseScreen(u *Ui) *browseScreen { return &browseScreen{ui: u} }
|
||||||
w, h := termbox.Size()
|
|
||||||
return &browseScreen{
|
|
||||||
ui: u,
|
|
||||||
menu: widdles.NewTopMenu(0, 0, w),
|
|
||||||
status: widdles.NewText("", 0, h),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *browseScreen) Init() wandle.Cmd {
|
func (s *browseScreen) Init() wandle.Cmd {
|
||||||
return nil
|
w, h := termbox.Size()
|
||||||
|
dbs := viper.GetStringSlice("dbs")
|
||||||
|
dbidx := viper.GetInt("dbidx")
|
||||||
|
if len(dbs) <= dbidx {
|
||||||
|
return wandle.Quit
|
||||||
|
}
|
||||||
|
s.dbPath = dbs[dbidx]
|
||||||
|
s.treePane = NewBoltTreePane(0, 3, w/2, h-2)
|
||||||
|
|
||||||
|
s.status = widdles.NewText("Press '?' for help", 0, (h - 1), w, 1)
|
||||||
|
|
||||||
|
return func() wandle.Msg {
|
||||||
|
timeout, err := time.ParseDuration(viper.GetString("version"))
|
||||||
|
if err != nil {
|
||||||
|
timeout = time.Second
|
||||||
|
}
|
||||||
|
db, err := bolt.Open(s.dbPath, 0600, &bolt.Options{Timeout: timeout})
|
||||||
|
if err == bolt.ErrTimeout {
|
||||||
|
return func() wandle.Msg { return BrowseMsg{source: BS_CmdDBTimeout} }
|
||||||
|
}
|
||||||
|
s.db = models.NewBoltDB(db)
|
||||||
|
if viper.GetBool("readonly") {
|
||||||
|
db.Close()
|
||||||
|
}
|
||||||
|
s.treePane.SetDB(s.db)
|
||||||
|
s.resizeWindow(w, h)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *browseScreen) Update(msg wandle.Msg) wandle.Cmd {
|
func (s *browseScreen) Update(msg wandle.Msg) wandle.Cmd {
|
||||||
@ -58,18 +86,96 @@ func (s *browseScreen) handleBrowseMsg(msg BrowseMsg) wandle.Cmd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *browseScreen) handleTermboxEvent(msg termbox.Event) wandle.Cmd {
|
func (s *browseScreen) handleTermboxEvent(msg termbox.Event) wandle.Cmd {
|
||||||
if (msg.Type == termbox.EventKey && msg.Key == termbox.KeyEsc) || s.menu.IsActive() {
|
|
||||||
return s.menu.Update(msg)
|
|
||||||
}
|
|
||||||
switch msg.Type {
|
switch msg.Type {
|
||||||
case termbox.EventKey:
|
case termbox.EventKey:
|
||||||
|
if cmd := s.treePane.Update(msg); cmd != nil {
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
if msg.Ch == '?' {
|
if msg.Ch == '?' {
|
||||||
return wandle.SwitchScreenCmd(NewAboutScreen(s.ui))
|
return wandle.SwitchScreenCmd(NewAboutScreen(s.ui))
|
||||||
|
} else if msg.Ch == 'q' || msg.Key == termbox.KeyCtrlC {
|
||||||
|
return wandle.Quit
|
||||||
|
} else if msg.Key == termbox.KeyCtrlN {
|
||||||
|
// Next File
|
||||||
|
idx := viper.GetInt("dbidx") + 1
|
||||||
|
if idx >= len(viper.GetStringSlice("dbs")) {
|
||||||
|
s.setStatus("Already at last file", time.Second)
|
||||||
|
} else {
|
||||||
|
viper.Set("dbidx", idx)
|
||||||
|
return wandle.SwitchScreenCmd(NewBrowseScreen(s.ui))
|
||||||
|
}
|
||||||
|
} else if msg.Key == termbox.KeyCtrlP {
|
||||||
|
// Previous File
|
||||||
|
idx := viper.GetInt("dbidx") - 1
|
||||||
|
if idx < 0 {
|
||||||
|
s.setStatus("Already at first file", time.Second)
|
||||||
|
} else {
|
||||||
|
viper.Set("dbidx", idx)
|
||||||
|
return wandle.SwitchScreenCmd(NewBrowseScreen(s.ui))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
case termbox.EventResize:
|
||||||
|
s.resizeWindow(msg.Width, msg.Height)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *browseScreen) View(style wandle.Style) {
|
func (s *browseScreen) resizeWindow(w, h int) {
|
||||||
|
lw := w
|
||||||
|
if lw > 80 {
|
||||||
|
lw = lw / 2
|
||||||
|
}
|
||||||
|
// Re-build Tree pane
|
||||||
|
s.treePane.SetWidth(lw)
|
||||||
|
// Re-build Right Pane buffer
|
||||||
|
s.rightPane = ViewPort{
|
||||||
|
x: lw + 1,
|
||||||
|
width: w - lw,
|
||||||
|
height: h - 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *browseScreen) View(style wandle.Style) {
|
||||||
|
w, h := termbox.Size()
|
||||||
|
s.drawHeader(style)
|
||||||
|
s.treePane.View(style)
|
||||||
|
if w > 80 {
|
||||||
|
x := s.rightPane.GetX() - 1
|
||||||
|
termbox.SetCell(x, 1, '╦', style.Foreground, style.Background)
|
||||||
|
wandle.Fill('║', x, 2, x, h-1, style)
|
||||||
|
s.rightPane.View(style)
|
||||||
|
}
|
||||||
|
s.status.View(style)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *browseScreen) drawHeader(style wandle.Style) {
|
||||||
|
width, _ := termbox.Size()
|
||||||
|
headerStringLen := func(fileName string) int {
|
||||||
|
return len("boltbrowser") + len(fileName) + 1
|
||||||
|
}
|
||||||
|
headerFileName := s.dbPath
|
||||||
|
if headerStringLen(headerFileName) > width {
|
||||||
|
headerFileName = filepath.Base(headerFileName)
|
||||||
|
}
|
||||||
|
headerString := "boltbrowser" + ": " + headerFileName
|
||||||
|
count := ((width - len(headerString)) / 2) + 1
|
||||||
|
if count < 0 {
|
||||||
|
count = 0
|
||||||
|
}
|
||||||
|
spaces := strings.Repeat(" ", count)
|
||||||
|
wandle.Print(0, 0, style, fmt.Sprintf("%s%s%s", spaces, headerString, spaces))
|
||||||
|
wandle.Fill('═', 0, 1, width, 1, style)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *browseScreen) setStatus(status string, timeout time.Duration) {
|
||||||
|
s.status.SetText(status)
|
||||||
|
if timeout > 0 {
|
||||||
|
go func() {
|
||||||
|
time.Sleep(timeout)
|
||||||
|
if s.status.GetText() == status {
|
||||||
|
s.status.SetText("Press '?' for help")
|
||||||
|
s.ui.wandle.Send(BS_CmdRefresh)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
12
ui/ui.go
12
ui/ui.go
@ -1,9 +1,8 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
|
|
||||||
"git.bullercodeworks.com/brian/wandle"
|
"git.bullercodeworks.com/brian/wandle"
|
||||||
|
"github.com/nsf/termbox-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -12,7 +11,6 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Ui struct {
|
type Ui struct {
|
||||||
config Config
|
|
||||||
wandle *wandle.Program
|
wandle *wandle.Program
|
||||||
|
|
||||||
browseScreen *browseScreen
|
browseScreen *browseScreen
|
||||||
@ -21,10 +19,12 @@ type Ui struct {
|
|||||||
func NewUi() *Ui {
|
func NewUi() *Ui {
|
||||||
u := new(Ui)
|
u := new(Ui)
|
||||||
u.browseScreen = NewBrowseScreen(u)
|
u.browseScreen = NewBrowseScreen(u)
|
||||||
u.config = Config{
|
|
||||||
sep: string(os.PathSeparator),
|
|
||||||
}
|
|
||||||
u.wandle = wandle.NewProgram(u.browseScreen)
|
u.wandle = wandle.NewProgram(u.browseScreen)
|
||||||
|
//u.wandle = wandle.NewProgram(NewAboutScreen(u))
|
||||||
|
u.wandle.Style(wandle.NewStyle(
|
||||||
|
termbox.RGBToAttribute(uint8(0), uint8(255), uint8(0)),
|
||||||
|
termbox.RGBToAttribute(uint8(0), uint8(0), uint8(0)),
|
||||||
|
))
|
||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
39
ui/viewport.go
Normal file
39
ui/viewport.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import "git.bullercodeworks.com/brian/wandle"
|
||||||
|
|
||||||
|
/*
|
||||||
|
ViewPort helps keep track of what's being displayed on the screen
|
||||||
|
It matches the widdle interface:
|
||||||
|
git.bullercodeworks.com/brian/widdles/widdle.go
|
||||||
|
*/
|
||||||
|
type ViewPort struct {
|
||||||
|
x, y int
|
||||||
|
width int
|
||||||
|
height int
|
||||||
|
scrollRow int
|
||||||
|
active bool
|
||||||
|
|
||||||
|
firstRow int
|
||||||
|
buffer []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *ViewPort) Init() wandle.Cmd {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *ViewPort) Update(wandle.Msg) wandle.Cmd {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (w *ViewPort) View(style wandle.Style) {}
|
||||||
|
func (w *ViewPort) IsActive() bool { return w.active }
|
||||||
|
func (w *ViewPort) SetActive(b bool) { w.active = b }
|
||||||
|
func (w *ViewPort) Focusable() bool { return true }
|
||||||
|
func (w *ViewPort) SetX(x int) { w.x = x }
|
||||||
|
func (w *ViewPort) SetY(y int) { w.y = y }
|
||||||
|
func (w *ViewPort) GetX() int { return w.x }
|
||||||
|
func (w *ViewPort) GetY() int { return w.y }
|
||||||
|
func (w *ViewPort) GetHeight() int { return w.height }
|
||||||
|
func (w *ViewPort) GetWidth() int { return w.width }
|
||||||
|
|
||||||
|
func (w *ViewPort) SetBuffer(buffer []string) { w.buffer = buffer }
|
Loading…
x
Reference in New Issue
Block a user