diff --git a/main.go b/main.go index 4aa8845..5fce2f5 100644 --- a/main.go +++ b/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 { diff --git a/pkg/menu/menu.go b/pkg/menu/menu.go index b7849df..01f9c8e 100644 --- a/pkg/menu/menu.go +++ b/pkg/menu/menu.go @@ -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, diff --git a/pkg/menu/menu_model.go b/pkg/menu/menu_model.go index dfe2835..ceb7d2c 100644 --- a/pkg/menu/menu_model.go +++ b/pkg/menu/menu_model.go @@ -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 } diff --git a/pkg/menu/menu_option.go b/pkg/menu/menu_option.go new file mode 100644 index 0000000..089147f --- /dev/null +++ b/pkg/menu/menu_option.go @@ -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) } diff --git a/pkg/menu/menu_view.go b/pkg/menu/menu_view.go index f7979a3..c2b1bf1 100644 --- a/pkg/menu/menu_view.go +++ b/pkg/menu/menu_view.go @@ -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() } diff --git a/pkg/tal/repo.go b/pkg/tal/repo.go index d87edfc..cf0cbf2 100644 --- a/pkg/tal/repo.go +++ b/pkg/tal/repo.go @@ -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 } }