V3 is off to a great start!
This commit is contained in:
parent
7443259b23
commit
c2d2c04b1a
2
.gitignore
vendored
2
.gitignore
vendored
@ -29,3 +29,5 @@ boltbrowser
|
|||||||
# Test Database
|
# Test Database
|
||||||
test.db
|
test.db
|
||||||
*.db
|
*.db
|
||||||
|
|
||||||
|
build/
|
||||||
|
@ -26,6 +26,7 @@ func NewBoltDB(db *bolt.DB) *BoltDB {
|
|||||||
|
|
||||||
func (bd *BoltDB) RefreshDatabase() {
|
func (bd *BoltDB) RefreshDatabase() {
|
||||||
// Reload the database from the file
|
// Reload the database from the file
|
||||||
|
shadowBuckets := bd.buckets
|
||||||
bd.buckets = []BoltBucket{}
|
bd.buckets = []BoltBucket{}
|
||||||
bd.db.View(func(tx *bolt.Tx) error {
|
bd.db.View(func(tx *bolt.Tx) error {
|
||||||
err := tx.ForEach(func(nm []byte, b *bolt.Bucket) error {
|
err := tx.ForEach(func(nm []byte, b *bolt.Bucket) error {
|
||||||
@ -51,6 +52,7 @@ func (bd *BoltDB) RefreshDatabase() {
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
|
bd.SyncOpenBuckets(shadowBuckets)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bd *BoltDB) GetBuckets() []BoltBucket { return bd.buckets }
|
func (bd *BoltDB) GetBuckets() []BoltBucket { return bd.buckets }
|
||||||
@ -271,12 +273,12 @@ func (bd *BoltDB) OpenAllBuckets() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bd *BoltDB) SyncOpenBuckets(shadow *BoltDB) {
|
func (bd *BoltDB) SyncOpenBuckets(shadow []BoltBucket) {
|
||||||
// First test this bucket
|
// First test this bucket
|
||||||
for i := range bd.buckets {
|
for i := range bd.buckets {
|
||||||
for j := range shadow.buckets {
|
for j := range shadow {
|
||||||
if bd.buckets[i].name == shadow.buckets[j].name {
|
if bd.buckets[i].name == shadow[j].name {
|
||||||
bd.buckets[i].SyncOpenBuckets(&shadow.buckets[j])
|
bd.buckets[i].SyncOpenBuckets(&shadow[j])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -527,7 +529,7 @@ func (bd *BoltDB) InsertPair(path []string, k string, v string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bd *BoltDB) exportValue(path []string, fName string) error {
|
func (bd *BoltDB) ExportValue(path []string, fName string) error {
|
||||||
return bd.db.View(func(tx *bolt.Tx) error {
|
return bd.db.View(func(tx *bolt.Tx) error {
|
||||||
// len(b.path)-1 is the key whose value we want to export
|
// len(b.path)-1 is the key whose value we want to export
|
||||||
// the rest are buckets leading to that key
|
// the rest are buckets leading to that key
|
||||||
|
@ -19,6 +19,11 @@ type BoltBucket struct {
|
|||||||
isRoot bool
|
isRoot bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *BoltBucket) GetName() string { return b.name }
|
||||||
|
func (b *BoltBucket) SetName(nm string) { b.name = nm }
|
||||||
|
func (b *BoltBucket) GetBuckets() []BoltBucket { return b.buckets }
|
||||||
|
func (b *BoltBucket) GetPairs() []BoltPair { return b.pairs }
|
||||||
|
|
||||||
/*
|
/*
|
||||||
GetPath returns the database path leading to this BoltBucket
|
GetPath returns the database path leading to this BoltBucket
|
||||||
*/
|
*/
|
||||||
|
@ -14,10 +14,7 @@ type BoltPair struct {
|
|||||||
/*
|
/*
|
||||||
GetPath Returns the path of the BoltPair
|
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) GetKey() string { return p.key }
|
||||||
}
|
func (p *BoltPair) GetValue() string { return p.val }
|
||||||
|
func (p BoltPair) String() string { return fmt.Sprintf("%s: %s", p.key, p.val) }
|
||||||
func (p BoltPair) String() string {
|
|
||||||
return fmt.Sprintf("%s: %s", p.key, p.val)
|
|
||||||
}
|
|
||||||
|
121
ui/bolt_detail_pane.go
Normal file
121
ui/bolt_detail_pane.go
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.bullercodeworks.com/brian/boltbrowser/models"
|
||||||
|
"git.bullercodeworks.com/brian/boltbrowser/util"
|
||||||
|
"git.bullercodeworks.com/brian/wandle"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
BoltDetailPane shows detail about either a bolt bucket or bolt pair
|
||||||
|
(as defined in the models subpackage)
|
||||||
|
*/
|
||||||
|
type BoltDetailPane struct {
|
||||||
|
x, y int
|
||||||
|
width int
|
||||||
|
height int
|
||||||
|
bucket *models.BoltBucket
|
||||||
|
pair *models.BoltPair
|
||||||
|
scrollRow int
|
||||||
|
active bool
|
||||||
|
|
||||||
|
firstRow int
|
||||||
|
buffer []string
|
||||||
|
|
||||||
|
db *models.BoltDB
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *BoltDetailPane) SetDB(db *models.BoltDB) { p.db = db }
|
||||||
|
func (p *BoltDetailPane) Init() wandle.Cmd { return nil }
|
||||||
|
func (p *BoltDetailPane) Update(wandle.Msg) wandle.Cmd { return nil }
|
||||||
|
func (p *BoltDetailPane) View(style wandle.Style) {
|
||||||
|
if len(p.buffer) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := range p.buffer {
|
||||||
|
if i < len(p.buffer) {
|
||||||
|
wandle.Print(p.x, p.y+i, style, p.buffer[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (p *BoltDetailPane) IsActive() bool { return p.active }
|
||||||
|
func (p *BoltDetailPane) SetActive(b bool) { p.active = b }
|
||||||
|
func (p *BoltDetailPane) Focusable() bool { return true }
|
||||||
|
func (p *BoltDetailPane) SetX(x int) { p.x = x }
|
||||||
|
func (p *BoltDetailPane) SetY(y int) { p.y = y }
|
||||||
|
func (p *BoltDetailPane) GetX() int { return p.x }
|
||||||
|
func (p *BoltDetailPane) GetY() int { return p.y }
|
||||||
|
func (p *BoltDetailPane) GetHeight() int { return p.height }
|
||||||
|
func (p *BoltDetailPane) SetHeight(h int) { p.height = h }
|
||||||
|
func (p *BoltDetailPane) GetWidth() int { return p.width }
|
||||||
|
func (p *BoltDetailPane) SetWidth(w int) { p.width = w }
|
||||||
|
func (p *BoltDetailPane) SetBuffer(buffer []string) { p.buffer = buffer }
|
||||||
|
func (p *BoltDetailPane) refresh() {
|
||||||
|
p.buffer = []string{}
|
||||||
|
if p.pair != nil {
|
||||||
|
p.buffer = append(p.buffer, []string{
|
||||||
|
fmt.Sprintf("Path: %s", pathToString(util.StringifyPath(p.pair.GetPath()))),
|
||||||
|
fmt.Sprintf("Key: %s", util.Stringify([]byte(p.pair.GetKey()))),
|
||||||
|
}...)
|
||||||
|
value := strings.Split(string(p.formatValue([]byte(p.pair.GetValue()))), "\n")
|
||||||
|
if len(value) == 1 {
|
||||||
|
p.buffer = append(p.buffer, fmt.Sprintf("Value: %s", value[0]))
|
||||||
|
} else {
|
||||||
|
p.buffer = append(p.buffer, "Value:")
|
||||||
|
p.buffer = append(p.buffer, value...)
|
||||||
|
}
|
||||||
|
//fmt.Sprintf("Value: %s", ),
|
||||||
|
} else if p.bucket != nil {
|
||||||
|
p.buffer = append(p.buffer, []string{
|
||||||
|
fmt.Sprintf("Path: %s", pathToString(util.StringifyPath(p.bucket.GetPath()))),
|
||||||
|
fmt.Sprintf("Buckets: %d", len(p.bucket.GetBuckets())),
|
||||||
|
fmt.Sprintf("Pairs: %d", len(p.bucket.GetPairs())),
|
||||||
|
}...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *BoltDetailPane) SetActiveItem(bucket *models.BoltBucket, pair *models.BoltPair, e error) {
|
||||||
|
if bucket != nil {
|
||||||
|
p.SetBucket(bucket)
|
||||||
|
} else if pair != nil {
|
||||||
|
p.SetPair(pair)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *BoltDetailPane) SetBucket(b *models.BoltBucket) {
|
||||||
|
p.pair = nil
|
||||||
|
p.bucket = b
|
||||||
|
p.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *BoltDetailPane) SetPair(pair *models.BoltPair) {
|
||||||
|
p.bucket = nil
|
||||||
|
p.pair = pair
|
||||||
|
p.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *BoltDetailPane) formatValue(val []byte) []byte {
|
||||||
|
// Attempt JSON parsing and formatting
|
||||||
|
out, err := formatValueJSON(val)
|
||||||
|
if err == nil {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
return []byte(util.Stringify([]byte(val)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatValueJSON(val []byte) ([]byte, error) {
|
||||||
|
var jsonOut interface{}
|
||||||
|
err := json.Unmarshal(val, &jsonOut)
|
||||||
|
if err != nil {
|
||||||
|
return val, err
|
||||||
|
}
|
||||||
|
out, err := json.MarshalIndent(jsonOut, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return val, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
@ -1,9 +1,10 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"time"
|
||||||
|
|
||||||
"git.bullercodeworks.com/brian/boltbrowser/models"
|
"git.bullercodeworks.com/brian/boltbrowser/models"
|
||||||
|
"git.bullercodeworks.com/brian/boltbrowser/util"
|
||||||
"git.bullercodeworks.com/brian/wandle"
|
"git.bullercodeworks.com/brian/wandle"
|
||||||
"github.com/nsf/termbox-go"
|
"github.com/nsf/termbox-go"
|
||||||
)
|
)
|
||||||
@ -16,13 +17,28 @@ type BoltTreePane struct {
|
|||||||
active bool
|
active bool
|
||||||
|
|
||||||
buffer []string
|
buffer []string
|
||||||
|
pathBuffer [][]string
|
||||||
currentPath []string
|
currentPath []string
|
||||||
currentPathIdx int
|
currentPathIdx int
|
||||||
filter string
|
filter string
|
||||||
|
filterMode bool
|
||||||
|
|
||||||
|
insertPairCmd func(*models.BoltBucket) wandle.Cmd
|
||||||
|
insertBucketCmd func(*models.BoltBucket) wandle.Cmd
|
||||||
|
editPairKeyCmd func(*models.BoltPair) wandle.Cmd
|
||||||
|
editPairValueCmd func(*models.BoltPair) wandle.Cmd
|
||||||
|
editBucketCmd func(*models.BoltBucket) wandle.Cmd
|
||||||
|
deleteItemCmd func([]string) wandle.Cmd
|
||||||
|
setStatus func(string, time.Duration)
|
||||||
|
|
||||||
db *models.BoltDB
|
db *models.BoltDB
|
||||||
|
|
||||||
message string
|
detailPane *BoltDetailPane
|
||||||
|
}
|
||||||
|
|
||||||
|
type TreePaneRenamePath struct {
|
||||||
|
oldPath []string
|
||||||
|
newPath []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBoltTreePane(x, y, w, h int) *BoltTreePane {
|
func NewBoltTreePane(x, y, w, h int) *BoltTreePane {
|
||||||
@ -34,15 +50,37 @@ func NewBoltTreePane(x, y, w, h int) *BoltTreePane {
|
|||||||
|
|
||||||
func (w *BoltTreePane) Init() wandle.Cmd {
|
func (w *BoltTreePane) Init() wandle.Cmd {
|
||||||
if w.db != nil {
|
if w.db != nil {
|
||||||
w.buffer = w.db.Lines()
|
w.refreshDBAndBuffer()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
func (w *BoltTreePane) SetDetailPane(pane *BoltDetailPane) { w.detailPane = pane }
|
||||||
|
func (w *BoltTreePane) Measure() {}
|
||||||
func (w *BoltTreePane) Update(msg wandle.Msg) wandle.Cmd {
|
func (w *BoltTreePane) Update(msg wandle.Msg) wandle.Cmd {
|
||||||
|
if w.db == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
if len(w.currentPath) == 0 {
|
if len(w.currentPath) == 0 {
|
||||||
w.currentPath = w.db.GetNextVisiblePath(nil, w.filter)
|
w.currentPath = w.db.GetNextVisiblePath(nil, w.filter)
|
||||||
}
|
}
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
|
case BrowseMsg:
|
||||||
|
if msg.source == BS_CmdRefreshTree {
|
||||||
|
pth, ok := msg.data.(TreePaneRenamePath)
|
||||||
|
if ok {
|
||||||
|
if comparePaths(w.currentPath, pth.oldPath) {
|
||||||
|
w.currentPath = pth.newPath
|
||||||
|
}
|
||||||
|
for i := range w.pathBuffer {
|
||||||
|
if comparePaths(w.pathBuffer[i], pth.oldPath) {
|
||||||
|
for j := range w.pathBuffer[i] {
|
||||||
|
w.pathBuffer[i][j] = pth.newPath[j]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.refreshDBAndBuffer()
|
||||||
|
}
|
||||||
case termbox.Event:
|
case termbox.Event:
|
||||||
return w.handleTermboxEvent(msg)
|
return w.handleTermboxEvent(msg)
|
||||||
}
|
}
|
||||||
@ -51,82 +89,105 @@ func (w *BoltTreePane) Update(msg wandle.Msg) wandle.Cmd {
|
|||||||
func (w *BoltTreePane) handleTermboxEvent(msg termbox.Event) wandle.Cmd {
|
func (w *BoltTreePane) handleTermboxEvent(msg termbox.Event) wandle.Cmd {
|
||||||
switch msg.Type {
|
switch msg.Type {
|
||||||
case termbox.EventKey:
|
case termbox.EventKey:
|
||||||
w.message = "Key Pressed: " + wandle.KeyToString(msg)
|
|
||||||
if msg.Ch == 'g' {
|
if msg.Ch == 'g' {
|
||||||
// Jump to Beginning
|
return w.jumpToStart
|
||||||
w.currentPath = w.db.GetNextVisiblePath(nil, w.filter)
|
|
||||||
return func() wandle.Msg { return nil }
|
|
||||||
} else if msg.Ch == 'G' {
|
} else if msg.Ch == 'G' {
|
||||||
// Jump to End
|
return w.jumpToEnd
|
||||||
w.currentPath = w.db.GetPrevVisiblePath(nil, w.filter)
|
|
||||||
return func() wandle.Msg { return nil }
|
|
||||||
} else if msg.Key == termbox.KeyCtrlF {
|
} else if msg.Key == termbox.KeyCtrlF {
|
||||||
w.jumpCursorDown(w.height / 2)
|
return w.pageDown
|
||||||
return func() wandle.Msg { return nil }
|
|
||||||
} else if msg.Key == termbox.KeyCtrlB {
|
} else if msg.Key == termbox.KeyCtrlB {
|
||||||
w.jumpCursorDown(w.height / 2)
|
return w.pageUp
|
||||||
return func() wandle.Msg { return nil }
|
|
||||||
} else if msg.Ch == 'j' || msg.Key == termbox.KeyArrowDown {
|
} else if msg.Ch == 'j' || msg.Key == termbox.KeyArrowDown {
|
||||||
w.moveCursorDown()
|
return w.moveCursorDown
|
||||||
return func() wandle.Msg { return nil }
|
|
||||||
} else if msg.Ch == 'k' || msg.Key == termbox.KeyArrowUp {
|
} else if msg.Ch == 'k' || msg.Key == termbox.KeyArrowUp {
|
||||||
w.moveCursorUp()
|
return w.moveCursorUp
|
||||||
return func() wandle.Msg { return nil }
|
} else if msg.Key == termbox.KeyCtrlR {
|
||||||
|
return w.refreshDBAndBuffer
|
||||||
} else if msg.Ch == 'p' {
|
} else if msg.Ch == 'p' {
|
||||||
|
return w.insertPairAtCurrent
|
||||||
return func() wandle.Msg { return nil }
|
|
||||||
} else if msg.Ch == 'P' {
|
} else if msg.Ch == 'P' {
|
||||||
|
return w.insertPairAtParent
|
||||||
return func() wandle.Msg { return nil }
|
|
||||||
} else if msg.Ch == 'b' {
|
} else if msg.Ch == 'b' {
|
||||||
|
return w.insertBucketAtCurrent
|
||||||
return func() wandle.Msg { return nil }
|
|
||||||
} else if msg.Ch == 'B' {
|
} else if msg.Ch == 'B' {
|
||||||
|
return w.insertBucketAtParent
|
||||||
return func() wandle.Msg { return nil }
|
} else if msg.Ch == 'e' {
|
||||||
} else if msg.Ch == 'l' || msg.Key == termbox.KeyArrowRight || msg.Key == termbox.KeyEnter {
|
return w.editPairValue
|
||||||
b, p, _ := w.db.GetGenericFromPath(w.currentPath)
|
} else if msg.Ch == 'r' {
|
||||||
// Select the current item
|
return w.renameBucketOrPair
|
||||||
if b != nil {
|
} else if msg.Key == termbox.KeyEnter {
|
||||||
w.db.ToggleOpenBucket(w.currentPath)
|
return w.handleEnterPressed
|
||||||
} else if p != nil {
|
} else if msg.Ch == 'l' || msg.Key == termbox.KeyArrowRight {
|
||||||
// Edit the pair
|
return w.handleRightPressed
|
||||||
}
|
|
||||||
return func() wandle.Msg { return nil }
|
|
||||||
} else if msg.Ch == 'h' || msg.Key == termbox.KeyArrowLeft {
|
} else if msg.Ch == 'h' || msg.Key == termbox.KeyArrowLeft {
|
||||||
b, _, e := w.db.GetGenericFromPath(w.currentPath)
|
return w.handleLeftPressed
|
||||||
if e == nil && b != nil && b.IsExpanded() {
|
} else if msg.Ch == 'D' {
|
||||||
w.db.CloseBucket(w.currentPath)
|
return w.handleDelete
|
||||||
} else {
|
} else if msg.Ch == 'x' {
|
||||||
if len(w.currentPath) > 1 {
|
// TODO: Export Value
|
||||||
parent, err := w.db.GetBucketFromPath(w.currentPath[:len(w.currentPath)-1])
|
} else if msg.Ch == 'X' {
|
||||||
if err == nil {
|
// TODO: Export Key/Value (or Bucket) as JSON
|
||||||
w.db.CloseBucket(parent.GetPath())
|
} else if msg.Ch == '/' {
|
||||||
w.currentPath = parent.GetPath()
|
// TODO: Start Filtering
|
||||||
}
|
|
||||||
} else {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return func() wandle.Msg { return nil }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (w *BoltTreePane) View(style wandle.Style) {
|
func (w *BoltTreePane) View(style wandle.Style) {
|
||||||
|
if w.db == nil {
|
||||||
|
txt := "No Database Loaded"
|
||||||
|
midX := (w.x + w.width/2) - len(txt)/2
|
||||||
|
midY := (w.y + w.height/2) - 2
|
||||||
|
wandle.Print(midX, midY, style, txt)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Find the cursor in the pane
|
||||||
|
for k, v := range w.pathBuffer {
|
||||||
|
if comparePaths(w.currentPath, v) {
|
||||||
|
w.scrollRow = k
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
treeOffset := 0
|
treeOffset := 0
|
||||||
maxCursor := w.height * 2 / 3
|
maxCursor := w.height * 2 / 3
|
||||||
if w.scrollRow > maxCursor {
|
if w.scrollRow > maxCursor {
|
||||||
treeOffset = w.scrollRow - maxCursor
|
treeOffset = w.scrollRow - maxCursor
|
||||||
}
|
}
|
||||||
if len(w.buffer) > 0 {
|
if len(w.buffer) > 0 {
|
||||||
for k, v := range w.buffer[treeOffset:] {
|
kIdx := 0
|
||||||
wandle.Print(w.x, (w.y + k - 1), style, v)
|
for k := treeOffset; k < len(w.buffer); k++ {
|
||||||
|
v := w.buffer[k]
|
||||||
|
st := style
|
||||||
|
if k < len(w.pathBuffer) && comparePaths(w.currentPath, w.pathBuffer[k]) {
|
||||||
|
st = st.Invert()
|
||||||
|
}
|
||||||
|
pos := (w.y + kIdx - 1)
|
||||||
|
bot := (w.y + w.height)
|
||||||
|
if pos < bot {
|
||||||
|
wandle.Print(w.x, pos, st, util.Stringify([]byte(v)))
|
||||||
|
}
|
||||||
|
kIdx++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
wd, h := termbox.Size()
|
|
||||||
wandle.Print(wd, h-1, style, w.message)
|
|
||||||
}
|
}
|
||||||
|
func (w *BoltTreePane) SetInsertPairCommand(cmd func(*models.BoltBucket) wandle.Cmd) {
|
||||||
|
w.insertPairCmd = cmd
|
||||||
|
}
|
||||||
|
func (w *BoltTreePane) SetInsertBucketCommand(cmd func(*models.BoltBucket) wandle.Cmd) {
|
||||||
|
w.insertBucketCmd = cmd
|
||||||
|
}
|
||||||
|
func (w *BoltTreePane) SetEditPairKeyCommand(cmd func(*models.BoltPair) wandle.Cmd) {
|
||||||
|
w.editPairKeyCmd = cmd
|
||||||
|
}
|
||||||
|
func (w *BoltTreePane) SetEditPairValueCommand(cmd func(*models.BoltPair) wandle.Cmd) {
|
||||||
|
w.editPairValueCmd = cmd
|
||||||
|
}
|
||||||
|
func (w *BoltTreePane) SetRenameBucketCommand(cmd func(*models.BoltBucket) wandle.Cmd) {
|
||||||
|
w.editBucketCmd = cmd
|
||||||
|
}
|
||||||
|
func (w *BoltTreePane) SetDeleteItemCommand(cmd func([]string) wandle.Cmd) {
|
||||||
|
w.deleteItemCmd = cmd
|
||||||
|
}
|
||||||
func (w *BoltTreePane) IsActive() bool { return w.active }
|
func (w *BoltTreePane) IsActive() bool { return w.active }
|
||||||
func (w *BoltTreePane) SetActive(b bool) { w.active = b }
|
func (w *BoltTreePane) SetActive(b bool) { w.active = b }
|
||||||
func (w *BoltTreePane) Focusable() bool { return true }
|
func (w *BoltTreePane) Focusable() bool { return true }
|
||||||
@ -139,25 +200,34 @@ func (w *BoltTreePane) GetHeight() int { return w.height }
|
|||||||
func (w *BoltTreePane) SetWidth(wdt int) { w.height = wdt }
|
func (w *BoltTreePane) SetWidth(wdt int) { w.height = wdt }
|
||||||
func (w *BoltTreePane) GetWidth() int { return w.width }
|
func (w *BoltTreePane) GetWidth() int { return w.width }
|
||||||
|
|
||||||
|
func (w *BoltTreePane) HasDB() bool { return w.db != nil }
|
||||||
func (w *BoltTreePane) SetDB(db *models.BoltDB) {
|
func (w *BoltTreePane) SetDB(db *models.BoltDB) {
|
||||||
w.db = db
|
w.db = db
|
||||||
w.RefreshBuffer()
|
w.detailPane.SetDB(db)
|
||||||
|
w.refreshDBAndBuffer()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *BoltTreePane) refreshDBAndBuffer() wandle.Msg {
|
||||||
|
w.db.RefreshDatabase()
|
||||||
|
w.RefreshBuffer()
|
||||||
|
w.UpdateDetailPane()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
func (w *BoltTreePane) RefreshBuffer() {
|
func (w *BoltTreePane) RefreshBuffer() {
|
||||||
|
w.buffer = nil
|
||||||
buckets := w.db.GetBuckets()
|
buckets := w.db.GetBuckets()
|
||||||
for i := range buckets {
|
for i := range buckets {
|
||||||
w.buffer = append(w.buffer, buckets[i].Lines()...)
|
w.buffer = append(w.buffer, buckets[i].Lines()...)
|
||||||
}
|
}
|
||||||
|
w.pathBuffer, _ = w.db.BuildVisiblePathSlice(w.filter)
|
||||||
|
if len(w.currentPath) == 0 {
|
||||||
|
w.currentPath = w.db.GetNextVisiblePath(nil, w.filter)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *BoltTreePane) jumpCursorDown(distance int) {
|
func (w *BoltTreePane) jumpCursorDown(distance int) {
|
||||||
paths, err := w.db.BuildVisiblePathSlice(w.filter)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
findPath := w.currentPath
|
findPath := w.currentPath
|
||||||
for idx, pth := range paths {
|
for idx, pth := range w.pathBuffer {
|
||||||
startJump := true
|
startJump := true
|
||||||
for i := range pth {
|
for i := range pth {
|
||||||
if len(w.currentPath) > i && pth[i] != w.currentPath[i] {
|
if len(w.currentPath) > i && pth[i] != w.currentPath[i] {
|
||||||
@ -167,7 +237,7 @@ func (w *BoltTreePane) jumpCursorDown(distance int) {
|
|||||||
if startJump {
|
if startJump {
|
||||||
distance--
|
distance--
|
||||||
if distance == 0 {
|
if distance == 0 {
|
||||||
w.currentPath = paths[len(paths)-1-idx]
|
w.currentPath = w.pathBuffer[len(w.pathBuffer)-1-idx]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -181,14 +251,11 @@ func (w *BoltTreePane) jumpCursorDown(distance int) {
|
|||||||
if isCurPath {
|
if isCurPath {
|
||||||
w.currentPath = w.db.GetNextVisiblePath(nil, w.filter)
|
w.currentPath = w.db.GetNextVisiblePath(nil, w.filter)
|
||||||
}
|
}
|
||||||
|
w.UpdateDetailPane()
|
||||||
}
|
}
|
||||||
func (w *BoltTreePane) jumpCursorUp(distance int) {
|
func (w *BoltTreePane) jumpCursorUp(distance int) {
|
||||||
paths, err := w.db.BuildVisiblePathSlice(w.filter)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
findPath := w.currentPath
|
findPath := w.currentPath
|
||||||
for idx, pth := range paths {
|
for idx, pth := range w.pathBuffer {
|
||||||
startJump := true
|
startJump := true
|
||||||
for i := range pth {
|
for i := range pth {
|
||||||
if len(w.currentPath) > i && pth[i] != w.currentPath[i] {
|
if len(w.currentPath) > i && pth[i] != w.currentPath[i] {
|
||||||
@ -198,7 +265,7 @@ func (w *BoltTreePane) jumpCursorUp(distance int) {
|
|||||||
if startJump {
|
if startJump {
|
||||||
distance--
|
distance--
|
||||||
if distance == 0 {
|
if distance == 0 {
|
||||||
w.currentPath = paths[idx]
|
w.currentPath = w.pathBuffer[idx]
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -213,20 +280,185 @@ func (w *BoltTreePane) jumpCursorUp(distance int) {
|
|||||||
if isCurPath {
|
if isCurPath {
|
||||||
w.currentPath = w.db.GetNextVisiblePath(nil, w.filter)
|
w.currentPath = w.db.GetNextVisiblePath(nil, w.filter)
|
||||||
}
|
}
|
||||||
|
w.UpdateDetailPane()
|
||||||
}
|
}
|
||||||
func (w *BoltTreePane) moveCursorUp() {
|
func (w *BoltTreePane) jumpToStart() wandle.Msg {
|
||||||
|
w.currentPath = w.db.GetNextVisiblePath(nil, w.filter)
|
||||||
|
w.UpdateDetailPane()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (w *BoltTreePane) jumpToEnd() wandle.Msg {
|
||||||
|
w.currentPath = w.db.GetPrevVisiblePath(nil, w.filter)
|
||||||
|
w.UpdateDetailPane()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (w *BoltTreePane) pageUp() wandle.Msg {
|
||||||
|
w.jumpCursorDown(w.height / 2)
|
||||||
|
w.UpdateDetailPane()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (w *BoltTreePane) pageDown() wandle.Msg {
|
||||||
|
w.jumpCursorDown(w.height / 2)
|
||||||
|
w.UpdateDetailPane()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (w *BoltTreePane) moveCursorUp() wandle.Msg {
|
||||||
p := w.db.GetPrevVisiblePath(w.currentPath, w.filter)
|
p := w.db.GetPrevVisiblePath(w.currentPath, w.filter)
|
||||||
if p != nil {
|
if p != nil {
|
||||||
w.currentPath = p
|
w.currentPath = p
|
||||||
}
|
}
|
||||||
|
w.UpdateDetailPane()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
func (w *BoltTreePane) moveCursorDown() {
|
func (w *BoltTreePane) moveCursorDown() wandle.Msg {
|
||||||
p := w.db.GetNextVisiblePath(w.currentPath, w.filter)
|
p := w.db.GetNextVisiblePath(w.currentPath, w.filter)
|
||||||
if p != nil {
|
if p != nil {
|
||||||
w.currentPath = p
|
w.currentPath = p
|
||||||
}
|
}
|
||||||
|
w.UpdateDetailPane()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (w *BoltTreePane) handleEnterPressed() wandle.Msg {
|
||||||
|
b, p, _ := w.GetSelected()
|
||||||
|
// Select the current item
|
||||||
|
if b != nil {
|
||||||
|
if w.db.ToggleOpenBucket(w.currentPath) == nil {
|
||||||
|
w.RefreshBuffer()
|
||||||
|
}
|
||||||
|
} else if p != nil {
|
||||||
|
// Edit the pair
|
||||||
|
if w.editPairValueCmd != nil {
|
||||||
|
return w.editPairValueCmd(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (w *BoltTreePane) handleRightPressed() wandle.Msg {
|
||||||
|
b, p, _ := w.GetSelected()
|
||||||
|
// Select the current item
|
||||||
|
if b != nil {
|
||||||
|
if w.db.OpenBucket(w.currentPath) == nil {
|
||||||
|
w.RefreshBuffer()
|
||||||
|
}
|
||||||
|
} else if p != nil {
|
||||||
|
// Edit the pair
|
||||||
|
if w.editPairValueCmd != nil {
|
||||||
|
return w.editPairValueCmd(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (w *BoltTreePane) handleLeftPressed() wandle.Msg {
|
||||||
|
b, _, e := w.GetSelected()
|
||||||
|
if e == nil && b != nil && b.IsExpanded() {
|
||||||
|
if err := w.db.CloseBucket(w.currentPath); err == nil {
|
||||||
|
w.RefreshBuffer()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if len(w.currentPath) > 1 {
|
||||||
|
parent, err := w.db.GetBucketFromPath(w.currentPath[:len(w.currentPath)-1])
|
||||||
|
if err == nil {
|
||||||
|
if w.db.CloseBucket(parent.GetPath()) == nil {
|
||||||
|
w.currentPath = parent.GetPath()
|
||||||
|
w.RefreshBuffer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (w *BoltTreePane) handleDelete() wandle.Msg {
|
||||||
|
if w.deleteItemCmd != nil {
|
||||||
|
return w.deleteItemCmd(w.currentPath)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (w *BoltTreePane) insertBucketAtCurrent() wandle.Msg {
|
||||||
|
if w.insertBucketCmd == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
insertPath := w.currentPath
|
||||||
|
b, p, e := w.GetSelected()
|
||||||
|
if p != nil {
|
||||||
|
insertPath = insertPath[:len(insertPath)-1]
|
||||||
|
b, e = w.db.GetBucketFromPath(insertPath)
|
||||||
|
}
|
||||||
|
if e != nil {
|
||||||
|
w.setStatus("Error inserting new pair. Invalid Path.", time.Second)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return w.insertBucketCmd(b)
|
||||||
|
}
|
||||||
|
func (w *BoltTreePane) insertBucketAtParent() wandle.Msg {
|
||||||
|
if w.insertBucketCmd == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
insertPath := w.currentPath[:len(w.currentPath)-1]
|
||||||
|
b, e := w.db.GetBucketFromPath(insertPath)
|
||||||
|
if e != nil {
|
||||||
|
w.setStatus("Error inserting new pair. Invalid Path.", time.Second)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return w.insertBucketCmd(b)
|
||||||
|
}
|
||||||
|
func (w *BoltTreePane) insertPairAtCurrent() wandle.Msg {
|
||||||
|
insertPath := w.currentPath
|
||||||
|
b, p, e := w.GetSelected()
|
||||||
|
if p != nil {
|
||||||
|
insertPath = insertPath[:len(insertPath)-1]
|
||||||
|
b, e = w.db.GetBucketFromPath(insertPath)
|
||||||
|
}
|
||||||
|
if e != nil {
|
||||||
|
w.setStatus("Error inserting new pair. Invalid Path.", time.Second)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return w.insertPairCmd(b)
|
||||||
|
}
|
||||||
|
func (w *BoltTreePane) insertPairAtParent() wandle.Msg {
|
||||||
|
insertPath := w.currentPath[:len(w.currentPath)-1]
|
||||||
|
b, e := w.db.GetBucketFromPath(insertPath)
|
||||||
|
if e != nil {
|
||||||
|
w.setStatus("Error inserting new pair. Invalid Path.", time.Second)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return w.insertPairCmd(b)
|
||||||
|
}
|
||||||
|
func (w *BoltTreePane) editPairValue() wandle.Msg {
|
||||||
|
if w.editPairValueCmd == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
b, p, _ := w.GetSelected()
|
||||||
|
if b != nil {
|
||||||
|
w.setStatus("Cannot edit a bucket, did you mean to (r)ename?", time.Second*2)
|
||||||
|
} else if p != nil {
|
||||||
|
return w.editPairValueCmd(p)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (w *BoltTreePane) renameBucketOrPair() wandle.Msg {
|
||||||
|
b, p, e := w.GetSelected()
|
||||||
|
if e != nil {
|
||||||
|
w.setStatus("Error renaming entity", time.Second)
|
||||||
|
} else if b != nil {
|
||||||
|
return w.editBucketCmd(b)
|
||||||
|
} else if p != nil {
|
||||||
|
if w.editPairKeyCmd != nil {
|
||||||
|
return w.editPairKeyCmd(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *BoltTreePane) comparePaths(p1, p2 []string) bool {
|
func (w *BoltTreePane) SetStatusFunc(f func(string, time.Duration)) { w.setStatus = f }
|
||||||
return strings.Join(p1, " → ") == strings.Join(p2, " → ")
|
|
||||||
|
// We do this a lot, so here's a helper function
|
||||||
|
func (w *BoltTreePane) GetCurrentPath() []string { return w.currentPath }
|
||||||
|
func (w *BoltTreePane) GetSelected() (*models.BoltBucket, *models.BoltPair, error) {
|
||||||
|
return w.db.GetGenericFromPath(w.currentPath)
|
||||||
|
}
|
||||||
|
func (w *BoltTreePane) UpdateDetailPane() {
|
||||||
|
if w.detailPane != nil {
|
||||||
|
w.detailPane.SetActiveItem(w.GetSelected())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
BS_CmdRefresh = BrowseId | iota
|
BS_CmdRefresh = BrowseId | iota
|
||||||
|
BS_CmdRefreshTree
|
||||||
BS_CmdDBTimeout
|
BS_CmdDBTimeout
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -32,13 +33,27 @@ type browseScreen struct {
|
|||||||
|
|
||||||
status *widdles.Text
|
status *widdles.Text
|
||||||
|
|
||||||
treePane *BoltTreePane
|
treePane *BoltTreePane
|
||||||
rightPane ViewPort
|
detailPane *BoltDetailPane
|
||||||
|
|
||||||
|
inputDialog *widdles.InputDialog
|
||||||
|
confirmDialog *widdles.ConfirmDialog
|
||||||
|
|
||||||
|
initialized bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBrowseScreen(u *Ui) *browseScreen { return &browseScreen{ui: u} }
|
func NewBrowseScreen(u *Ui) *browseScreen {
|
||||||
|
return &browseScreen{
|
||||||
|
ui: u,
|
||||||
|
inputDialog: widdles.NewInputDialog("Edit", ""),
|
||||||
|
confirmDialog: widdles.NewConfirmDialog("Are you sure?", ""),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *browseScreen) Init() wandle.Cmd {
|
func (s *browseScreen) Init() wandle.Cmd {
|
||||||
|
if s.initialized {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
w, h := termbox.Size()
|
w, h := termbox.Size()
|
||||||
dbs := viper.GetStringSlice("dbs")
|
dbs := viper.GetStringSlice("dbs")
|
||||||
dbidx := viper.GetInt("dbidx")
|
dbidx := viper.GetInt("dbidx")
|
||||||
@ -46,9 +61,23 @@ func (s *browseScreen) Init() wandle.Cmd {
|
|||||||
return wandle.Quit
|
return wandle.Quit
|
||||||
}
|
}
|
||||||
s.dbPath = dbs[dbidx]
|
s.dbPath = dbs[dbidx]
|
||||||
s.treePane = NewBoltTreePane(0, 3, w/2, h-2)
|
s.treePane = NewBoltTreePane(0, 3, w/2, h-6)
|
||||||
|
s.treePane.SetInsertPairCommand(s.insertPair)
|
||||||
|
s.treePane.SetInsertBucketCommand(s.insertBucket)
|
||||||
|
s.treePane.SetEditPairKeyCommand(s.editPairKey)
|
||||||
|
s.treePane.SetEditPairValueCommand(s.editPairValue)
|
||||||
|
s.treePane.SetRenameBucketCommand(s.editBucket)
|
||||||
|
s.treePane.SetDeleteItemCommand(s.deleteItem)
|
||||||
|
s.treePane.SetStatusFunc(s.setStatus)
|
||||||
|
s.detailPane = &BoltDetailPane{
|
||||||
|
x: w/2 + 2, y: 2,
|
||||||
|
width: w / 2, height: h - 6,
|
||||||
|
}
|
||||||
|
s.detailPane.Init()
|
||||||
|
s.treePane.SetDetailPane(s.detailPane)
|
||||||
s.status = widdles.NewText("Press '?' for help", 0, (h - 1), w, 1)
|
s.status = widdles.NewText("Press '?' for help", 0, (h - 1), w, 1)
|
||||||
|
s.inputDialog.Init()
|
||||||
|
s.confirmDialog.Init()
|
||||||
|
|
||||||
return func() wandle.Msg {
|
return func() wandle.Msg {
|
||||||
timeout, err := time.ParseDuration(viper.GetString("version"))
|
timeout, err := time.ParseDuration(viper.GetString("version"))
|
||||||
@ -65,6 +94,7 @@ func (s *browseScreen) Init() wandle.Cmd {
|
|||||||
}
|
}
|
||||||
s.treePane.SetDB(s.db)
|
s.treePane.SetDB(s.db)
|
||||||
s.resizeWindow(w, h)
|
s.resizeWindow(w, h)
|
||||||
|
s.initialized = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -80,22 +110,40 @@ func (s *browseScreen) Update(msg wandle.Msg) wandle.Cmd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *browseScreen) handleBrowseMsg(msg BrowseMsg) wandle.Cmd {
|
func (s *browseScreen) handleBrowseMsg(msg BrowseMsg) wandle.Cmd {
|
||||||
switch msg.source {
|
if msg.source == BS_CmdRefreshTree {
|
||||||
|
return s.treePane.Update(msg)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *browseScreen) handleTermboxEvent(msg termbox.Event) wandle.Cmd {
|
func (s *browseScreen) handleTermboxEvent(msg termbox.Event) wandle.Cmd {
|
||||||
|
if s.inputDialog.IsActive() {
|
||||||
|
return s.inputDialog.Update(msg)
|
||||||
|
}
|
||||||
|
if s.confirmDialog.IsActive() {
|
||||||
|
return s.confirmDialog.Update(msg)
|
||||||
|
}
|
||||||
switch msg.Type {
|
switch msg.Type {
|
||||||
case termbox.EventKey:
|
case termbox.EventKey:
|
||||||
if cmd := s.treePane.Update(msg); cmd != nil {
|
if cmd := s.treePane.Update(msg); cmd != nil {
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
if msg.Ch == '?' {
|
switch {
|
||||||
|
case msg.Ch == '?':
|
||||||
return wandle.SwitchScreenCmd(NewAboutScreen(s.ui))
|
return wandle.SwitchScreenCmd(NewAboutScreen(s.ui))
|
||||||
} else if msg.Ch == 'q' || msg.Key == termbox.KeyCtrlC {
|
case msg.Ch == 'q' || msg.Key == termbox.KeyCtrlC:
|
||||||
return wandle.Quit
|
return wandle.Quit
|
||||||
} else if msg.Key == termbox.KeyCtrlN {
|
case msg.Ch == 'x':
|
||||||
|
return s.exportValue
|
||||||
|
case msg.Ch == 'X':
|
||||||
|
return s.exportJSON
|
||||||
|
// TODO: External editor
|
||||||
|
//case msg.Ch == 'E':
|
||||||
|
// return s.startEditor
|
||||||
|
case msg.Ch == 'J':
|
||||||
|
// TODO: Move Right Pane Down
|
||||||
|
case msg.Ch == 'K':
|
||||||
|
// TODO: Move Right Pane Up
|
||||||
|
case msg.Key == termbox.KeyCtrlN:
|
||||||
// Next File
|
// Next File
|
||||||
idx := viper.GetInt("dbidx") + 1
|
idx := viper.GetInt("dbidx") + 1
|
||||||
if idx >= len(viper.GetStringSlice("dbs")) {
|
if idx >= len(viper.GetStringSlice("dbs")) {
|
||||||
@ -104,7 +152,7 @@ func (s *browseScreen) handleTermboxEvent(msg termbox.Event) wandle.Cmd {
|
|||||||
viper.Set("dbidx", idx)
|
viper.Set("dbidx", idx)
|
||||||
return wandle.SwitchScreenCmd(NewBrowseScreen(s.ui))
|
return wandle.SwitchScreenCmd(NewBrowseScreen(s.ui))
|
||||||
}
|
}
|
||||||
} else if msg.Key == termbox.KeyCtrlP {
|
case msg.Key == termbox.KeyCtrlP:
|
||||||
// Previous File
|
// Previous File
|
||||||
idx := viper.GetInt("dbidx") - 1
|
idx := viper.GetInt("dbidx") - 1
|
||||||
if idx < 0 {
|
if idx < 0 {
|
||||||
@ -127,25 +175,47 @@ func (s *browseScreen) resizeWindow(w, h int) {
|
|||||||
}
|
}
|
||||||
// Re-build Tree pane
|
// Re-build Tree pane
|
||||||
s.treePane.SetWidth(lw)
|
s.treePane.SetWidth(lw)
|
||||||
|
s.treePane.SetHeight(h - 4)
|
||||||
// Re-build Right Pane buffer
|
// Re-build Right Pane buffer
|
||||||
s.rightPane = ViewPort{
|
s.detailPane.SetX(lw + 1)
|
||||||
x: lw + 1,
|
s.detailPane.SetWidth(w - lw)
|
||||||
width: w - lw,
|
s.detailPane.SetHeight(h - 2)
|
||||||
height: h - 2,
|
// Re-build the input dialog
|
||||||
|
dlgWidth := 4
|
||||||
|
if w < 80 {
|
||||||
|
dlgWidth = 2
|
||||||
}
|
}
|
||||||
|
s.inputDialog.SetX((w / 2) - (w / (dlgWidth * 2)))
|
||||||
|
s.inputDialog.SetWidth(w / dlgWidth)
|
||||||
|
s.inputDialog.SetY((h / 2) - (h / 4))
|
||||||
|
s.inputDialog.SetHeight(4)
|
||||||
|
// Re-build the confirmation dialog
|
||||||
|
s.confirmDialog.SetX((w / 2) - (w / (dlgWidth * 2)))
|
||||||
|
s.confirmDialog.SetWidth(w / dlgWidth)
|
||||||
|
s.confirmDialog.SetY((h / 2) - (h / 4))
|
||||||
|
s.confirmDialog.SetHeight(4)
|
||||||
|
s.confirmDialog.EnableHotkeys()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *browseScreen) View(style wandle.Style) {
|
func (s *browseScreen) View(style wandle.Style) {
|
||||||
w, h := termbox.Size()
|
w, h := termbox.Size()
|
||||||
s.drawHeader(style)
|
s.drawHeader(style)
|
||||||
s.treePane.View(style)
|
s.treePane.View(style)
|
||||||
|
midX := s.detailPane.GetX() - 1
|
||||||
if w > 80 {
|
if w > 80 {
|
||||||
x := s.rightPane.GetX() - 1
|
termbox.SetCell(midX, 1, '╤', style.Foreground, style.Background)
|
||||||
termbox.SetCell(x, 1, '╦', style.Foreground, style.Background)
|
wandle.Fill('│', midX, 2, midX, h-3, style)
|
||||||
wandle.Fill('║', x, 2, x, h-1, style)
|
s.detailPane.View(style)
|
||||||
s.rightPane.View(style)
|
|
||||||
}
|
}
|
||||||
|
wandle.Fill('═', 0, h-2, w, h-2, style)
|
||||||
|
termbox.SetCell(midX, h-2, '╧', style.Foreground, style.Background)
|
||||||
s.status.View(style)
|
s.status.View(style)
|
||||||
|
if s.inputDialog.IsVisible() {
|
||||||
|
s.inputDialog.View(style)
|
||||||
|
}
|
||||||
|
if s.confirmDialog.IsVisible() {
|
||||||
|
s.confirmDialog.View(style)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *browseScreen) drawHeader(style wandle.Style) {
|
func (s *browseScreen) drawHeader(style wandle.Style) {
|
||||||
@ -173,9 +243,272 @@ func (s *browseScreen) setStatus(status string, timeout time.Duration) {
|
|||||||
go func() {
|
go func() {
|
||||||
time.Sleep(timeout)
|
time.Sleep(timeout)
|
||||||
if s.status.GetText() == status {
|
if s.status.GetText() == status {
|
||||||
s.status.SetText("Press '?' for help")
|
s.status.SetText("")
|
||||||
s.ui.wandle.Send(BS_CmdRefresh)
|
s.ui.wandle.Send(BS_CmdRefresh)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
func (s *browseScreen) insertPair(bucket *models.BoltBucket) wandle.Cmd {
|
||||||
|
title := fmt.Sprintf("New Pair: %s", pathToString(bucket.GetPath()))
|
||||||
|
s.inputDialog.SetTitle(title)
|
||||||
|
s.inputDialog.SetMessage("New Pair Key")
|
||||||
|
s.inputDialog.SetValue("")
|
||||||
|
s.inputDialog.SetOkCommand(func() wandle.Msg {
|
||||||
|
key := s.inputDialog.GetValue()
|
||||||
|
if key == "" {
|
||||||
|
s.setStatus("Pair key cannot be empty", time.Second)
|
||||||
|
} else {
|
||||||
|
s.inputDialog.SetMessage("New Pair Value")
|
||||||
|
s.inputDialog.SetValue("")
|
||||||
|
s.inputDialog.SetOkCommand(func() wandle.Msg {
|
||||||
|
val := s.inputDialog.GetValue()
|
||||||
|
if err := s.db.InsertPair(bucket.GetPath(), key, val); err != nil {
|
||||||
|
s.setStatus("Error inserting pair.", time.Second)
|
||||||
|
} else {
|
||||||
|
s.inputDialog.Hide()
|
||||||
|
return BrowseMsg{source: BS_CmdRefreshTree}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
s.inputDialog.SetCancelCommand(func() wandle.Msg {
|
||||||
|
s.inputDialog.Hide()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
s.inputDialog.Show()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *browseScreen) insertBucket(bucket *models.BoltBucket) wandle.Cmd {
|
||||||
|
title := fmt.Sprintf("New Bucket: %s", pathToString(s.treePane.currentPath))
|
||||||
|
s.inputDialog.SetTitle(title)
|
||||||
|
s.inputDialog.SetValue("")
|
||||||
|
s.inputDialog.SetOkCommand(func() wandle.Msg {
|
||||||
|
key := s.inputDialog.GetValue()
|
||||||
|
if key == "" {
|
||||||
|
s.setStatus("Pair key cannot be empty", time.Second)
|
||||||
|
} else {
|
||||||
|
if err := s.db.InsertBucket(bucket.GetPath(), key); err != nil {
|
||||||
|
s.setStatus("Error inserting pair.", time.Second)
|
||||||
|
} else {
|
||||||
|
s.inputDialog.Hide()
|
||||||
|
return BrowseMsg{source: BS_CmdRefreshTree}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
s.inputDialog.SetCancelCommand(func() wandle.Msg {
|
||||||
|
s.inputDialog.Hide()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
s.inputDialog.Show()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *browseScreen) editPairKey(pair *models.BoltPair) wandle.Cmd {
|
||||||
|
s.inputDialog.SetTitle(fmt.Sprintf("Input new value for '%s'", pair.GetKey()))
|
||||||
|
s.inputDialog.SetValue(pair.GetKey())
|
||||||
|
s.inputDialog.SetOkCommand(func() wandle.Msg {
|
||||||
|
v := s.inputDialog.GetValue()
|
||||||
|
if v == "" {
|
||||||
|
s.setStatus("No value given, did you mean to (d)elete?", time.Second)
|
||||||
|
} else if err := s.db.UpdatePairKey(s.treePane.currentPath, v); err != nil {
|
||||||
|
s.setStatus("Error changing pair key", time.Second)
|
||||||
|
} else {
|
||||||
|
s.treePane.currentPath[len(s.treePane.currentPath)-1] = v
|
||||||
|
s.inputDialog.Hide()
|
||||||
|
return BrowseMsg{source: BS_CmdRefreshTree}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
s.inputDialog.SetCancelCommand(func() wandle.Msg {
|
||||||
|
s.inputDialog.Hide()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
s.inputDialog.Show()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *browseScreen) editPairValue(pair *models.BoltPair) wandle.Cmd {
|
||||||
|
s.inputDialog.SetTitle(fmt.Sprintf("Input new value for '%s'", pair.GetKey()))
|
||||||
|
s.inputDialog.SetValue(pair.GetValue())
|
||||||
|
s.inputDialog.SetOkCommand(func() wandle.Msg {
|
||||||
|
v := s.inputDialog.GetValue()
|
||||||
|
if v == "" {
|
||||||
|
s.setStatus("No value given, did you mean to (d)elete?", time.Second)
|
||||||
|
} else if err := s.treePane.db.UpdatePairValue(s.treePane.currentPath, v); err != nil {
|
||||||
|
s.setStatus("Error updating pair value.", time.Second)
|
||||||
|
} else {
|
||||||
|
s.inputDialog.Hide()
|
||||||
|
return BrowseMsg{source: BS_CmdRefreshTree}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
s.inputDialog.SetCancelCommand(func() wandle.Msg {
|
||||||
|
s.inputDialog.Hide()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
s.inputDialog.Show()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *browseScreen) editBucket(bucket *models.BoltBucket) wandle.Cmd {
|
||||||
|
s.inputDialog.SetTitle(fmt.Sprintf("Rename Bucket '%s' to:", bucket.GetName()))
|
||||||
|
s.inputDialog.SetValue(bucket.GetName())
|
||||||
|
s.inputDialog.SetOkCommand(func() wandle.Msg {
|
||||||
|
v := s.inputDialog.GetValue()
|
||||||
|
if v == "" {
|
||||||
|
s.setStatus("A Bucket has to have a name.", time.Second)
|
||||||
|
} else if err := s.db.RenameBucket(s.treePane.currentPath, v); err != nil {
|
||||||
|
s.setStatus("Error renaming bucket.", time.Second)
|
||||||
|
} else {
|
||||||
|
bkt, _ := s.treePane.db.GetBucketFromPath(s.treePane.currentPath)
|
||||||
|
if bkt != nil {
|
||||||
|
bkt.SetName(v)
|
||||||
|
}
|
||||||
|
oldPath := make([]string, len(s.treePane.currentPath))
|
||||||
|
copy(oldPath, s.treePane.currentPath)
|
||||||
|
s.treePane.currentPath[len(s.treePane.currentPath)-1] = v
|
||||||
|
s.inputDialog.Hide()
|
||||||
|
return BrowseMsg{
|
||||||
|
source: BS_CmdRefreshTree,
|
||||||
|
data: TreePaneRenamePath{
|
||||||
|
oldPath: oldPath,
|
||||||
|
newPath: s.treePane.currentPath,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
s.inputDialog.Show()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *browseScreen) deleteItem(path []string) wandle.Cmd {
|
||||||
|
key := path[len(path)-1]
|
||||||
|
var itemType string
|
||||||
|
b, p, _ := s.treePane.db.GetGenericFromPath(path)
|
||||||
|
if b != nil {
|
||||||
|
itemType = "Bucket"
|
||||||
|
} else if p != nil {
|
||||||
|
itemType = "Pair"
|
||||||
|
} else {
|
||||||
|
s.setStatus("Error deleting item.", time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.confirmDialog.SetTitle(fmt.Sprintf("Delete %s '%s'?", itemType, key))
|
||||||
|
s.confirmDialog.SetMessage(fmt.Sprintf("Are you sure you want to delete this %s?", itemType))
|
||||||
|
s.confirmDialog.SetOkCommand(func() wandle.Msg {
|
||||||
|
s.treePane.moveCursorUp()
|
||||||
|
if err := s.treePane.db.DeleteKey(path); err != nil {
|
||||||
|
s.setStatus(fmt.Sprintf("Error deleting %s.", itemType), time.Second)
|
||||||
|
}
|
||||||
|
s.confirmDialog.Hide()
|
||||||
|
return BrowseMsg{source: BS_CmdRefreshTree}
|
||||||
|
})
|
||||||
|
s.confirmDialog.SetCancelCommand(func() wandle.Msg {
|
||||||
|
s.confirmDialog.Hide()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
s.confirmDialog.Show()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *browseScreen) exportValue() wandle.Msg {
|
||||||
|
path := s.treePane.GetCurrentPath()
|
||||||
|
b, p, e := s.treePane.GetSelected()
|
||||||
|
if e != nil || p == nil {
|
||||||
|
s.setStatus("Couldn't do string export on "+path[len(path)-1]+" (did you mean 'X'?)", time.Second)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
s.inputDialog.SetTitle(fmt.Sprintf("Export value of '%s' to:", b.GetName()))
|
||||||
|
s.inputDialog.SetValue("")
|
||||||
|
s.inputDialog.SetOkCommand(func() wandle.Msg {
|
||||||
|
v := s.inputDialog.GetValue()
|
||||||
|
if v == "" {
|
||||||
|
s.setStatus("Must give a file name to export to.", time.Second)
|
||||||
|
} else {
|
||||||
|
if err := s.db.ExportValue(s.treePane.GetCurrentPath(), v); err != nil {
|
||||||
|
s.setStatus("Error Exporting Value", time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
s.inputDialog.Show()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *browseScreen) exportJSON() wandle.Msg {
|
||||||
|
b, p, e := s.treePane.GetSelected()
|
||||||
|
if e != nil {
|
||||||
|
s.setStatus("Error getting value to export", time.Second)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var nm string
|
||||||
|
if b != nil {
|
||||||
|
nm = b.GetName()
|
||||||
|
} else if p != nil {
|
||||||
|
nm = p.GetKey()
|
||||||
|
}
|
||||||
|
|
||||||
|
s.inputDialog.SetTitle(fmt.Sprintf("Export JSON of '%s' to:", nm))
|
||||||
|
s.inputDialog.SetValue("")
|
||||||
|
s.inputDialog.SetOkCommand(func() wandle.Msg {
|
||||||
|
v := s.inputDialog.GetValue()
|
||||||
|
if v == "" {
|
||||||
|
s.setStatus("Must give a file name to export to.", time.Second)
|
||||||
|
} else {
|
||||||
|
if err := s.db.ExportJSON(s.treePane.GetCurrentPath(), v); err != nil {
|
||||||
|
s.setStatus("Error Exporting JSON", time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
s.inputDialog.Show()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: This kind of works, but we need to stop termbox from intercepting
|
||||||
|
* keypresses, and we need to make sure that the screen redraws when we
|
||||||
|
* return.
|
||||||
|
func (s *browseScreen) startEditor() wandle.Msg {
|
||||||
|
editor := os.Getenv("EDITOR")
|
||||||
|
if editor == "" {
|
||||||
|
s.setStatus("EDITOR environment variable is not set.", time.Second)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
path := s.treePane.GetCurrentPath()
|
||||||
|
_, p, _ := s.treePane.GetSelected()
|
||||||
|
if p == nil {
|
||||||
|
s.setStatus("Error pulling value to write to temp file", time.Second)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.CreateTemp(os.TempDir(), "boltbrowser-")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
defer os.Remove(f.Name())
|
||||||
|
if _, err := f.Write([]byte(p.GetValue())); err != nil {
|
||||||
|
s.setStatus("Error writing value to temp file", time.Second)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
c := exec.Command(editor, f.Name())
|
||||||
|
c.Stdin = os.Stdin
|
||||||
|
c.Stdout = os.Stdout
|
||||||
|
e := c.Run()
|
||||||
|
if e != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if b, e := ioutil.ReadAll(f); e != nil {
|
||||||
|
s.setStatus("Error reading edited temp file", time.Second)
|
||||||
|
} else {
|
||||||
|
if err := s.treePane.db.UpdatePairValue(path, string(b)); err != nil {
|
||||||
|
s.setStatus("Error saving new value to db", time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
8
ui/ui.go
8
ui/ui.go
@ -1,6 +1,8 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"git.bullercodeworks.com/brian/wandle"
|
"git.bullercodeworks.com/brian/wandle"
|
||||||
"github.com/nsf/termbox-go"
|
"github.com/nsf/termbox-go"
|
||||||
)
|
)
|
||||||
@ -29,3 +31,9 @@ func NewUi() *Ui {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *Ui) Start() error { return u.wandle.Start() }
|
func (u *Ui) Start() error { return u.wandle.Start() }
|
||||||
|
|
||||||
|
// A few Helper Functions
|
||||||
|
func pathToString(p []string) string { return strings.Join(p, " → ") }
|
||||||
|
func comparePaths(p1, p2 []string) bool {
|
||||||
|
return pathToString(p1) == pathToString(p2)
|
||||||
|
}
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
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 }
|
|
@ -1,13 +1,17 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
bolt "go.etcd.io/bbolt"
|
bolt "go.etcd.io/bbolt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ToJsonString(b *bolt.Bucket) string {
|
func ToJsonString(b *bolt.Bucket) string {
|
||||||
|
// TODO: Escape any double quotes inside
|
||||||
|
// TODO: Deal with empty buckets
|
||||||
ret := "{"
|
ret := "{"
|
||||||
b.ForEach(func(k, v []byte) error {
|
b.ForEach(func(k, v []byte) error {
|
||||||
ret = fmt.Sprintf("%s\"%s\":", ret, string(k))
|
ret = fmt.Sprintf("%s\"%s\":", ret, string(k))
|
||||||
@ -44,3 +48,34 @@ func WriteToFile(fn, s string, mode int) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stringify ensures that we can print only valid characters.
|
||||||
|
// It's wrong to assume that everything is a string, since BoltDB is typeless.
|
||||||
|
func Stringify(v []byte) string {
|
||||||
|
if utf8.Valid(v) {
|
||||||
|
ok := true
|
||||||
|
for _, r := range string(v) {
|
||||||
|
if r < 0x20 {
|
||||||
|
ok = false
|
||||||
|
break
|
||||||
|
} else if r >= 0x7f && r <= 0x9f {
|
||||||
|
ok = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
return string(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(v) == 8 {
|
||||||
|
return fmt.Sprintf("%v", binary.BigEndian.Uint64(v))
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%x", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringifyPath(path []string) []string {
|
||||||
|
for k, v := range path {
|
||||||
|
path[k] = Stringify([]byte(v))
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user