browse indexed tags
This commit is contained in:
parent
d0bff1d1bc
commit
9af9e33bad
61
main.go
61
main.go
@ -6,6 +6,7 @@ import (
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"talc/pkg/menu"
|
||||
"talc/pkg/tal"
|
||||
@ -18,7 +19,6 @@ var path = flag.String("path", ".", "library path")
|
||||
|
||||
func init() {
|
||||
flag.Parse()
|
||||
|
||||
*path = filepath.FromSlash(*path)
|
||||
if (*path)[0] == '~' {
|
||||
user, err := user.Current()
|
||||
@ -44,15 +44,15 @@ func (t *Talc) HandleEvent(ev tcell.Event) bool {
|
||||
case tcell.KeyCtrlC:
|
||||
app.Quit()
|
||||
return true
|
||||
case tcell.KeyCtrlL:
|
||||
app.Refresh()
|
||||
return true
|
||||
case tcell.KeyRune:
|
||||
switch ev.Rune() {
|
||||
case 'q':
|
||||
app.Quit()
|
||||
return true
|
||||
}
|
||||
case tcell.KeyCtrlL:
|
||||
app.Refresh()
|
||||
return true
|
||||
}
|
||||
case *tcell.EventResize:
|
||||
app.Refresh()
|
||||
@ -74,21 +74,52 @@ func main() {
|
||||
os.Exit(11)
|
||||
}
|
||||
|
||||
books := make([]string, len(alib.Books))
|
||||
for i, book := range alib.Books {
|
||||
books[i] = book.String()
|
||||
}
|
||||
|
||||
s := tcell.StyleDefault
|
||||
ss := s.Foreground(tcell.ColorGreen)
|
||||
|
||||
m := menu.NewMenu()
|
||||
m.SetStyle(s)
|
||||
m.SetSelectedStyle(ss)
|
||||
m.EnableCursor(true)
|
||||
m.SetLines(books)
|
||||
meta := alib.GetMeta()
|
||||
|
||||
talc.AddWidget(m, 1)
|
||||
root := menu.Option{
|
||||
Name: "talc",
|
||||
}
|
||||
tags := []menu.Option{}
|
||||
for tag, vals := range meta {
|
||||
topt := menu.Option{
|
||||
Name: tag,
|
||||
Action: menu.ActionMenu,
|
||||
}
|
||||
valopts := []menu.Option{}
|
||||
for value, books := range vals {
|
||||
vopt := menu.Option{
|
||||
Name: value,
|
||||
Action: menu.ActionMenu,
|
||||
}
|
||||
bookopts := []menu.Option{}
|
||||
for _, book := range books {
|
||||
bopt := menu.Option{
|
||||
Name: book.String(),
|
||||
Action: menu.ActionExec,
|
||||
Args: []string{os.Getenv("EDITOR"), book.Path},
|
||||
}
|
||||
bookopts = append(bookopts, bopt)
|
||||
}
|
||||
sort.Sort(menu.ByName(bookopts))
|
||||
vopt.Options = bookopts
|
||||
valopts = append(valopts, vopt)
|
||||
}
|
||||
sort.Sort(menu.ByOptions(valopts))
|
||||
topt.Options = valopts
|
||||
tags = append(tags, topt)
|
||||
}
|
||||
sort.Sort(menu.ByOptions(tags))
|
||||
root.Options = tags
|
||||
|
||||
mt := menu.NewMenu()
|
||||
mt.SetStyle(s)
|
||||
mt.SetSelectedStyle(ss)
|
||||
mt.SetCurrent(root)
|
||||
|
||||
talc.AddWidget(mt, 10)
|
||||
|
||||
app.SetRootWidget(talc)
|
||||
if err := app.Run(); err != nil {
|
||||
|
@ -12,20 +12,36 @@ type Menu struct {
|
||||
MenuView
|
||||
}
|
||||
|
||||
func (m *Menu) SetLines(lines []string) {
|
||||
// func (m *Menu) SetLines(lines []string) {
|
||||
// m.Init()
|
||||
// mm := m.model
|
||||
// mm.width = 0
|
||||
// mm.height = len(lines)
|
||||
// mm.lines = lines
|
||||
// for _, l := range lines {
|
||||
// if len(l) > mm.width {
|
||||
// mm.width = len(l)
|
||||
// }
|
||||
// }
|
||||
// m.MenuView.SetModel(mm)
|
||||
// }
|
||||
|
||||
func (m *Menu) SetCurrent(option Option) {
|
||||
m.Init()
|
||||
mm := m.model
|
||||
mm.width = 0
|
||||
mm.height = len(lines)
|
||||
mm.lines = lines
|
||||
for _, l := range lines {
|
||||
if len(l) > mm.width {
|
||||
mm.width = len(l)
|
||||
}
|
||||
}
|
||||
mm.SetCurrent(option)
|
||||
|
||||
m.MenuView.SetModel(mm)
|
||||
}
|
||||
|
||||
// func (m *Menu) SetOptions(options []Option) {
|
||||
// m.Init()
|
||||
// mm := m.model
|
||||
// mm.SetOptions(options)
|
||||
|
||||
// m.MenuView.SetModel(mm)
|
||||
// }
|
||||
|
||||
func (m *Menu) SetStyle(style tcell.Style) {
|
||||
m.model.style = style
|
||||
m.MenuView.SetStyle(style)
|
||||
@ -48,7 +64,6 @@ func (m *Menu) HideCursor(on bool) {
|
||||
func (m *Menu) Init() {
|
||||
m.once.Do(func() {
|
||||
mm := &menuModel{
|
||||
lines: []string{},
|
||||
width: 0,
|
||||
cursor: true,
|
||||
hide: false,
|
||||
|
@ -6,14 +6,20 @@ import (
|
||||
|
||||
type MenuModel interface {
|
||||
GetBounds() (int, int)
|
||||
SetCursor(int)
|
||||
GetCurrent() Option
|
||||
//GetParent() *Option
|
||||
GetCursor() (int, bool, bool)
|
||||
MoveCursor(offx, offy int)
|
||||
GetLine(int) (string, tcell.Style)
|
||||
GetOption(int) Option
|
||||
MoveCursor(y int)
|
||||
SetCurrent(Option)
|
||||
//SetParent(*Option)
|
||||
SetCursor(int)
|
||||
}
|
||||
|
||||
type menuModel struct {
|
||||
lines []string
|
||||
//parent *Option
|
||||
current Option
|
||||
|
||||
width int
|
||||
height int
|
||||
y int
|
||||
@ -23,17 +29,52 @@ type menuModel struct {
|
||||
style tcell.Style
|
||||
}
|
||||
|
||||
func (m *menuModel) GetLine(y int) (string, tcell.Style) {
|
||||
if y < 0 || y >= len(m.lines) {
|
||||
return "", m.style
|
||||
}
|
||||
return m.lines[y], m.style
|
||||
}
|
||||
|
||||
func (m *menuModel) GetBounds() (int, int) {
|
||||
return m.width, m.height
|
||||
}
|
||||
|
||||
// func (m *menuModel) GetParent() *Option {
|
||||
// return m.parent
|
||||
// }
|
||||
|
||||
// func (m *menuModel) SetParent(opt *Option) {
|
||||
// m.parent = opt
|
||||
// }
|
||||
|
||||
func (m *menuModel) GetCurrent() Option {
|
||||
return m.current
|
||||
}
|
||||
|
||||
func (m *menuModel) SetCurrent(opt Option) {
|
||||
m.current = opt
|
||||
m.y = opt.y
|
||||
m.height = len(m.current.Options)
|
||||
m.width = 0
|
||||
for _, opt := range m.current.Options {
|
||||
if len(opt.Name) > m.width {
|
||||
m.width = len(opt.Name)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *menuModel) SetCursor(y int) {
|
||||
m.y = y
|
||||
m.limitCursor()
|
||||
}
|
||||
|
||||
func (m *menuModel) GetOption(y int) Option {
|
||||
if y < 0 || y >= len(m.current.Options) {
|
||||
return Option{}
|
||||
}
|
||||
return m.current.Options[y]
|
||||
}
|
||||
|
||||
func (m *menuModel) MoveCursor(y int) {
|
||||
m.y += y
|
||||
m.limitCursor()
|
||||
}
|
||||
|
||||
func (m *menuModel) limitCursor() {
|
||||
if m.y > m.height-1 {
|
||||
m.y = m.height - 1
|
||||
@ -43,16 +84,6 @@ func (m *menuModel) limitCursor() {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *menuModel) SetCursor(y int) {
|
||||
m.y = y
|
||||
m.limitCursor()
|
||||
}
|
||||
|
||||
func (m *menuModel) MoveCursor(x, y int) {
|
||||
m.y += y
|
||||
m.limitCursor()
|
||||
}
|
||||
|
||||
func (m *menuModel) GetCursor() (int, bool, bool) {
|
||||
return m.y, m.cursor, !m.hide
|
||||
}
|
||||
|
39
pkg/menu/menu_option.go
Normal file
39
pkg/menu/menu_option.go
Normal file
@ -0,0 +1,39 @@
|
||||
package menu
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
ActionMenu = iota + 1
|
||||
ActionExec
|
||||
)
|
||||
|
||||
type Action uint8
|
||||
|
||||
type Option struct {
|
||||
Name string
|
||||
Options []Option
|
||||
Action Action
|
||||
Args []string
|
||||
y int
|
||||
parent *Option
|
||||
}
|
||||
|
||||
func (o *Option) String() string {
|
||||
ol := len(o.Options)
|
||||
if ol > 1 {
|
||||
return fmt.Sprintf("%s (%d)", o.Name, ol)
|
||||
}
|
||||
return o.Name
|
||||
}
|
||||
|
||||
type ByName []Option
|
||||
|
||||
func (a ByName) Len() int { return len(a) }
|
||||
func (a ByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a ByName) Less(i, j int) bool { return a[i].Name < a[j].Name }
|
||||
|
||||
type ByOptions []Option
|
||||
|
||||
func (a ByOptions) Len() int { return len(a) }
|
||||
func (a ByOptions) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a ByOptions) Less(i, j int) bool { return len(a[i].Options) > len(a[j].Options) }
|
@ -1,6 +1,8 @@
|
||||
package menu
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
@ -16,6 +18,7 @@ type MenuView struct {
|
||||
model MenuModel
|
||||
view views.View
|
||||
port *views.ViewPort
|
||||
|
||||
views.WidgetWatchers
|
||||
once sync.Once
|
||||
}
|
||||
@ -27,6 +30,12 @@ func (mv *MenuView) HandleEvent(e tcell.Event) bool {
|
||||
switch e := e.(type) {
|
||||
case *tcell.EventKey:
|
||||
switch e.Key() {
|
||||
case tcell.KeyBackspace, tcell.KeyBackspace2:
|
||||
mv.keyBack()
|
||||
return true
|
||||
case tcell.KeyEnter:
|
||||
mv.keyEnter()
|
||||
return true
|
||||
case tcell.KeyUp, tcell.KeyCtrlP:
|
||||
mv.keyUp()
|
||||
return true
|
||||
@ -47,18 +56,32 @@ func (mv *MenuView) HandleEvent(e tcell.Event) bool {
|
||||
return true
|
||||
case tcell.KeyRune:
|
||||
switch e.Rune() {
|
||||
case 'h':
|
||||
mv.keyBack()
|
||||
return true
|
||||
case 'j':
|
||||
mv.keyDown()
|
||||
return true
|
||||
case 'k':
|
||||
mv.keyUp()
|
||||
return true
|
||||
case 'l':
|
||||
mv.keyEnter()
|
||||
return true
|
||||
case 'g':
|
||||
mv.keyHome()
|
||||
return true
|
||||
case 'G':
|
||||
mv.keyEnd()
|
||||
return true
|
||||
case 'q':
|
||||
c := mv.model.GetCurrent()
|
||||
p := c.parent
|
||||
if p == nil {
|
||||
mv.keyBack()
|
||||
return true
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -81,21 +104,19 @@ func (mv *MenuView) Draw() {
|
||||
mv.view.SetContent(x, y, ' ', nil, mv.style)
|
||||
}
|
||||
}
|
||||
ex, ey := model.GetBounds()
|
||||
vx, vy := port.Size()
|
||||
if ex < vx {
|
||||
ex = vx
|
||||
}
|
||||
_, ey := model.GetBounds()
|
||||
_, vy := port.Size()
|
||||
if ey < vy {
|
||||
ey = vy
|
||||
}
|
||||
cy, en, sh := mv.model.GetCursor()
|
||||
for y := 0; y < ey; y++ {
|
||||
ch, style := mv.model.GetLine(y)
|
||||
opt := mv.model.GetOption(y)
|
||||
style := mv.style
|
||||
if en && y == cy && sh {
|
||||
style = mv.selectedStyle
|
||||
}
|
||||
puts(port, style, 0, y, string(ch))
|
||||
puts(port, style, 0, y, opt.String())
|
||||
}
|
||||
}
|
||||
|
||||
@ -109,7 +130,6 @@ func puts(s views.View, style tcell.Style, x, y int, str string) {
|
||||
if len(deferred) == 0 {
|
||||
deferred = append(deferred, ' ')
|
||||
dwidth = 1
|
||||
|
||||
}
|
||||
deferred = append(deferred, r)
|
||||
zwj = true
|
||||
@ -120,7 +140,6 @@ func puts(s views.View, style tcell.Style, x, y int, str string) {
|
||||
deferred = append(deferred, r)
|
||||
zwj = false
|
||||
continue
|
||||
|
||||
}
|
||||
switch runewidth.RuneWidth(r) {
|
||||
case 0:
|
||||
@ -133,7 +152,6 @@ func puts(s views.View, style tcell.Style, x, y int, str string) {
|
||||
if len(deferred) != 0 {
|
||||
s.SetContent(x+i, y, deferred[0], deferred[1:], style)
|
||||
i += dwidth
|
||||
|
||||
}
|
||||
deferred = nil
|
||||
dwidth = 1
|
||||
@ -145,10 +163,8 @@ func puts(s views.View, style tcell.Style, x, y int, str string) {
|
||||
}
|
||||
deferred = nil
|
||||
dwidth = 2
|
||||
|
||||
}
|
||||
deferred = append(deferred, r)
|
||||
|
||||
}
|
||||
if len(deferred) != 0 {
|
||||
s.SetContent(x+i, y, deferred[0], deferred[1:], style)
|
||||
@ -157,12 +173,71 @@ func puts(s views.View, style tcell.Style, x, y int, str string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (mv *MenuView) keyBack() {
|
||||
// p := mv.model.GetParent()
|
||||
// if p == nil {
|
||||
// return
|
||||
// }
|
||||
|
||||
cur := mv.model.GetCurrent()
|
||||
|
||||
if cur.parent == nil {
|
||||
return
|
||||
}
|
||||
|
||||
mv.model.SetCurrent(*cur.parent)
|
||||
w, h := mv.model.GetBounds()
|
||||
mv.port.SetContentSize(w, h, true)
|
||||
mv.model.SetCursor(cur.y)
|
||||
mv.port.Center(0, cur.y)
|
||||
}
|
||||
|
||||
func (mv *MenuView) keyEnter() {
|
||||
var y int
|
||||
var en bool
|
||||
|
||||
if y, en, _ = mv.model.GetCursor(); !en {
|
||||
return
|
||||
}
|
||||
|
||||
y, _, _ = mv.model.GetCursor()
|
||||
opt := mv.model.GetOption(y)
|
||||
opt.y = y
|
||||
|
||||
cur := mv.model.GetCurrent()
|
||||
|
||||
opt.parent = &cur
|
||||
|
||||
switch opt.Action {
|
||||
case ActionMenu:
|
||||
if len(opt.Options) == 0 {
|
||||
return
|
||||
}
|
||||
mv.model.SetCurrent(opt)
|
||||
mv.model.SetCursor(0)
|
||||
w, h := mv.model.GetBounds()
|
||||
mv.port.SetContentSize(w, h, true)
|
||||
case ActionExec:
|
||||
if len(opt.Args) == 0 {
|
||||
return
|
||||
}
|
||||
cmd := exec.Command(opt.Args[0], opt.Args[1:]...)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
err := cmd.Run()
|
||||
mv.Draw()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (mv *MenuView) keyUp() {
|
||||
if _, en, _ := mv.model.GetCursor(); !en {
|
||||
mv.port.ScrollUp(1)
|
||||
return
|
||||
}
|
||||
mv.model.MoveCursor(0, -1)
|
||||
mv.model.MoveCursor(-1)
|
||||
mv.MakeCursorVisible()
|
||||
}
|
||||
|
||||
@ -171,7 +246,7 @@ func (mv *MenuView) keyDown() {
|
||||
mv.port.ScrollDown(1)
|
||||
return
|
||||
}
|
||||
mv.model.MoveCursor(0, 1)
|
||||
mv.model.MoveCursor(1)
|
||||
mv.MakeCursorVisible()
|
||||
}
|
||||
|
||||
@ -181,7 +256,7 @@ func (mv *MenuView) keyPgUp() {
|
||||
mv.port.ScrollUp(vy)
|
||||
return
|
||||
}
|
||||
mv.model.MoveCursor(0, -vy)
|
||||
mv.model.MoveCursor(-vy)
|
||||
mv.MakeCursorVisible()
|
||||
}
|
||||
|
||||
@ -191,7 +266,7 @@ func (mv *MenuView) keyPgDn() {
|
||||
mv.port.ScrollDown(vy)
|
||||
return
|
||||
}
|
||||
mv.model.MoveCursor(0, +vy)
|
||||
mv.model.MoveCursor(+vy)
|
||||
mv.MakeCursorVisible()
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,19 @@ type Repo struct {
|
||||
Books []Book
|
||||
}
|
||||
|
||||
func (lr *Repo) GetMeta() map[string]map[string][]Book {
|
||||
meta := map[string]map[string][]Book{}
|
||||
for _, book := range lr.Books {
|
||||
for key, value := range book.Meta {
|
||||
if meta[key] == nil {
|
||||
meta[key] = map[string][]Book{}
|
||||
}
|
||||
meta[key][value] = append(meta[key][value], book)
|
||||
}
|
||||
}
|
||||
return meta
|
||||
}
|
||||
|
||||
func (lr *Repo) Scan() error {
|
||||
books := lr.scanTree(lr.Path, 1, 3, ".muse")
|
||||
|
||||
@ -91,9 +104,9 @@ func readMeta(p string) (map[string]string, error) {
|
||||
tag := strings.TrimSpace(result[1])
|
||||
value := strings.TrimSpace(result[2])
|
||||
meta[tag] = value
|
||||
} else {
|
||||
break
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user