From 42c78535f9301fb28fa99810436640a36508ab61 Mon Sep 17 00:00:00 2001 From: raziman Date: Fri, 19 Feb 2021 13:09:07 +0800 Subject: [PATCH 01/21] fix cpu spike when no songs playing on startup --- playingbar.go | 8 -------- start.go | 12 +++++++++--- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/playingbar.go b/playingbar.go index 61a2697..7b0d53a 100644 --- a/playingbar.go +++ b/playingbar.go @@ -38,14 +38,6 @@ func newPlayingBar() *PlayingBar { progress: make(chan int), } - textView.SetChangedFunc(func() { - gomu.app.Draw() - - if !gomu.player.isRunning { - p.setDefault() - } - }) - return p } diff --git a/start.go b/start.go index 8afaec9..58313d5 100644 --- a/start.go +++ b/start.go @@ -357,15 +357,21 @@ func start(application *tview.Application, args Args) { }) // fix transparent background issue - application.SetBeforeDrawFunc(func(screen tcell.Screen) bool { + gomu.app.SetBeforeDrawFunc(func(screen tcell.Screen) bool { screen.Clear() return false }) + gomu.app.SetAfterDrawFunc(func(_ tcell.Screen) { + gomu.playingBar.setDefault() + }) + go populateAudioLength(gomu.playlist.GetRoot()) + gomu.app.SetRoot(gomu.pages, true).SetFocus(gomu.playlist) + // main loop - if err := application.SetRoot(gomu.pages, true).SetFocus(gomu.playlist).Run(); err != nil { - logError(err) + if err := gomu.app.Run(); err != nil { + die(err) } } From 2788a10225862d7e874ff1c2aa0d921b336a5554 Mon Sep 17 00:00:00 2001 From: raziman Date: Fri, 19 Feb 2021 15:06:10 +0800 Subject: [PATCH 02/21] allow skip current song when queue is empty --- player.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/player.go b/player.go index a213d7f..a76639b 100644 --- a/player.go +++ b/player.go @@ -235,7 +235,7 @@ func (p *Player) togglePause() { // skips current song func (p *Player) skip() { - if gomu.queue.GetItemCount() < 1 { + if p.currentSong == nil { return } From 5e4c9f64b59e6ab7f044b2008a70808220052aad Mon Sep 17 00:00:00 2001 From: raziman Date: Fri, 19 Feb 2021 15:07:44 +0800 Subject: [PATCH 03/21] fix screen does not update playingbar --- player.go | 1 + playingbar.go | 1 + 2 files changed, 2 insertions(+) diff --git a/player.go b/player.go index a76639b..da926b4 100644 --- a/player.go +++ b/player.go @@ -157,6 +157,7 @@ next: // when there are no songs to be played, set currentSong as nil p.currentSong = nil gomu.playingBar.setDefault() + gomu.app.Draw() break next } diff --git a/playingbar.go b/playingbar.go index 7b0d53a..d9f297a 100644 --- a/playingbar.go +++ b/playingbar.go @@ -76,6 +76,7 @@ func (p *PlayingBar) run() error { progressBar, fmtDuration(end), )) + gomu.app.Draw() } From dcce622458e38294663538928d9427dcb239e113 Mon Sep 17 00:00:00 2001 From: raziman Date: Fri, 19 Feb 2021 15:08:10 +0800 Subject: [PATCH 04/21] set correct progress bar length when screen is drawn --- start.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/start.go b/start.go index 58313d5..1120236 100644 --- a/start.go +++ b/start.go @@ -362,8 +362,12 @@ func start(application *tview.Application, args Args) { return false }) + init := false gomu.app.SetAfterDrawFunc(func(_ tcell.Screen) { - gomu.playingBar.setDefault() + if !init && !gomu.player.isRunning { + gomu.playingBar.setDefault() + init = true + } }) go populateAudioLength(gomu.playlist.GetRoot()) From 310a43f8cfb97f37af8545d124b48af2554ad302 Mon Sep 17 00:00:00 2001 From: raziman Date: Fri, 19 Feb 2021 10:25:34 +0800 Subject: [PATCH 05/21] add EventHook --- hook/hook.go | 31 +++++++++++++++++++++++++++++++ hook/hook_test.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 hook/hook.go create mode 100644 hook/hook_test.go diff --git a/hook/hook.go b/hook/hook.go new file mode 100644 index 0000000..21eac7b --- /dev/null +++ b/hook/hook.go @@ -0,0 +1,31 @@ +package hook + +type EventHook struct { + events map[string][]func() +} + +func NewEventHook() EventHook { + return EventHook{make(map[string][]func())} +} + +func (e *EventHook) AddHook(eventName string, handler func()) { + + hooks, ok := e.events[eventName] + if !ok { + e.events[eventName] = []func(){handler} + } + + e.events[eventName] = append(hooks, handler) +} + +func (e *EventHook) RunHooks(eventName string) { + + hooks, ok := e.events[eventName] + if !ok { + return + } + + for _, hook := range hooks { + hook() + } +} diff --git a/hook/hook_test.go b/hook/hook_test.go new file mode 100644 index 0000000..a5dbf29 --- /dev/null +++ b/hook/hook_test.go @@ -0,0 +1,31 @@ +package hook + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRunHook(t *testing.T) { + + h := NewEventHook() + i := 0 + + h.AddHook("sample", func() { + i++ + }) + + h.AddHook("sample", func() { + i++ + }) + + assert.Equal(t, i, 0) + + h.RunHooks("x") + + assert.Equal(t, i, 0) + + h.RunHooks("sample") + + assert.Equal(t, i, 2) +} From 4fa99d10438d95db96025efc54cab3778f376708 Mon Sep 17 00:00:00 2001 From: raziman Date: Fri, 19 Feb 2021 12:08:08 +0800 Subject: [PATCH 06/21] add more tests --- hook/hook_test.go | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/hook/hook_test.go b/hook/hook_test.go index a5dbf29..e64c3d1 100644 --- a/hook/hook_test.go +++ b/hook/hook_test.go @@ -6,7 +6,27 @@ import ( "github.com/stretchr/testify/assert" ) -func TestRunHook(t *testing.T) { +func TestAddHook(t *testing.T) { + + h := NewEventHook() + + h.AddHook("a", nil) + h.AddHook("a", nil) + h.AddHook("a", nil) + h.AddHook("a", nil) + + assert.Equal(t, 1, len(h.events), "should only contain 1 event") + + hooks := h.events["a"] + assert.Equal(t, 4, len(hooks), "should contain 4 hooks") + + h.AddHook("b", nil) + h.AddHook("c", nil) + + assert.Equal(t, 3, len(h.events), "should contain 3 events") +} + +func TestRunHooks(t *testing.T) { h := NewEventHook() i := 0 @@ -19,13 +39,17 @@ func TestRunHook(t *testing.T) { i++ }) - assert.Equal(t, i, 0) + h.AddHook("noop", func() { + i++ + }) + + assert.Equal(t, i, 0, "should not execute any hook") h.RunHooks("x") - assert.Equal(t, i, 0) + assert.Equal(t, i, 0, "should not execute any hook") h.RunHooks("sample") - assert.Equal(t, i, 2) + assert.Equal(t, i, 2, "should only execute event 'sample'") } From e8f0b306f07143242bf9e68ae42ae22cbfd6638d Mon Sep 17 00:00:00 2001 From: raziman Date: Fri, 19 Feb 2021 12:08:56 +0800 Subject: [PATCH 07/21] change NewEventHook to return pointer --- gomu.go | 5 ++++- hook/hook.go | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/gomu.go b/gomu.go index 6111987..cbb4fe2 100644 --- a/gomu.go +++ b/gomu.go @@ -1,9 +1,10 @@ package main import ( + "github.com/issadarkthing/gomu/anko" + "github.com/issadarkthing/gomu/hook" "github.com/rivo/tview" "github.com/ztrue/tracerr" - "github.com/issadarkthing/gomu/anko" ) var VERSION = "N/A" @@ -25,6 +26,7 @@ type Gomu struct { panels []Panel args Args anko anko.Anko + hook *hook.EventHook } // Creates new instance of gomu with default values @@ -33,6 +35,7 @@ func newGomu() *Gomu { gomu := &Gomu{ command: newCommand(), anko: anko.NewAnko(), + hook: hook.NewEventHook(), } return gomu diff --git a/hook/hook.go b/hook/hook.go index 21eac7b..c0e887f 100644 --- a/hook/hook.go +++ b/hook/hook.go @@ -4,8 +4,8 @@ type EventHook struct { events map[string][]func() } -func NewEventHook() EventHook { - return EventHook{make(map[string][]func())} +func NewEventHook() *EventHook { + return &EventHook{make(map[string][]func())} } func (e *EventHook) AddHook(eventName string, handler func()) { @@ -13,6 +13,7 @@ func (e *EventHook) AddHook(eventName string, handler func()) { hooks, ok := e.events[eventName] if !ok { e.events[eventName] = []func(){handler} + return } e.events[eventName] = append(hooks, handler) From cefb7b9264d902e4676277f5e0a30abfdfd4f3e0 Mon Sep 17 00:00:00 2001 From: raziman Date: Fri, 19 Feb 2021 12:09:42 +0800 Subject: [PATCH 08/21] show nil in repl --- popup.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/popup.go b/popup.go index 82185b5..12e7c1c 100644 --- a/popup.go +++ b/popup.go @@ -638,11 +638,10 @@ func replPopup() { if err != nil { fmt.Fprintf(textview, "%s%s\n%v\n\n", prompt, text, err) return nil - } - - if res != nil { + } else { fmt.Fprintf(textview, "%s%s\n%v\n\n", prompt, text, res) } + } return event From 1f0e990e2acec00e5135e4057a52782b44e5e81d Mon Sep 17 00:00:00 2001 From: raziman Date: Fri, 19 Feb 2021 12:22:41 +0800 Subject: [PATCH 09/21] setup hooks --- player.go | 3 +++ start.go | 45 ++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/player.go b/player.go index da926b4..3b79367 100644 --- a/player.go +++ b/player.go @@ -121,7 +121,10 @@ func (p *Player) run(currSong *AudioFile) error { // sets the volume of previous player volume.Volume += p.volume p._volume = volume + + // starts playing the audio speaker.Play(p._volume) + gomu.hook.RunHooks("playing") p.isRunning = true diff --git a/start.go b/start.go index 1120236..cf859b5 100644 --- a/start.go +++ b/start.go @@ -62,9 +62,19 @@ func defineBuiltins() { gomu.anko.Define("shell", shell) } -// execInit executes helper modules and default config that should only be +func setupHooks() { + gomu.hook.AddHook("playing", func() { + _, err := gomu.anko.Execute(`Event.run_hooks("playing")`) + if err != nil { + err = tracerr.Errorf("error execute hook: %w", err) + logError(err) + } + }) +} + +// loadModules executes helper modules and default config that should only be // executed once -func execInit() error { +func loadModules() error { const listModule = ` module List { @@ -94,6 +104,31 @@ module List { return acc } } +` + const eventModule = ` +module Event { + events = {} + + func add_hook(name, f) { + hooks = events[name] + + if hooks == nil { + events[name] = [f] + return + } + + hooks += f + events[name] = hooks + } + + func run_hooks(name) { + hooks = events[name] + + for hook in hooks { + hook() + } + } +} ` const keybindModule = ` @@ -115,8 +150,8 @@ module Keybinds { } } ` - - _, err := gomu.anko.Execute(listModule + keybindModule) + setupHooks() + _, err := gomu.anko.Execute(eventModule + listModule + keybindModule) if err != nil { return tracerr.Wrap(err) } @@ -231,7 +266,7 @@ func start(application *tview.Application, args Args) { gomu = newGomu() gomu.command.defineCommands() defineBuiltins() - err := execInit() + err := loadModules() if err != nil { die(err) } From af336e1a605feabd6eca4bf8ff49e82730449632 Mon Sep 17 00:00:00 2001 From: raziman Date: Fri, 19 Feb 2021 22:22:24 +0800 Subject: [PATCH 10/21] add comments --- hook/hook.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hook/hook.go b/hook/hook.go index c0e887f..5e70f74 100644 --- a/hook/hook.go +++ b/hook/hook.go @@ -4,10 +4,12 @@ type EventHook struct { events map[string][]func() } +// NewNewEventHook returns new instance of EventHook func NewEventHook() *EventHook { return &EventHook{make(map[string][]func())} } +// AddHook accepts a function which will be executed when the event is emitted. func (e *EventHook) AddHook(eventName string, handler func()) { hooks, ok := e.events[eventName] @@ -19,6 +21,7 @@ func (e *EventHook) AddHook(eventName string, handler func()) { e.events[eventName] = append(hooks, handler) } +// RunHooks executes all hooks installed for an event. func (e *EventHook) RunHooks(eventName string) { hooks, ok := e.events[eventName] From 9d41e80edcd38e435d55d37313dbaa208dcd57a1 Mon Sep 17 00:00:00 2001 From: raziman Date: Fri, 19 Feb 2021 22:22:36 +0800 Subject: [PATCH 11/21] add hook test --- hook/hook_test.go | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/hook/hook_test.go b/hook/hook_test.go index e64c3d1..87dc63a 100644 --- a/hook/hook_test.go +++ b/hook/hook_test.go @@ -29,27 +29,29 @@ func TestAddHook(t *testing.T) { func TestRunHooks(t *testing.T) { h := NewEventHook() - i := 0 + x := 0 - h.AddHook("sample", func() { - i++ - }) + for i := 0; i < 100; i ++ { + h.AddHook("sample", func() { + x++ + }) + } - h.AddHook("sample", func() { - i++ + h.AddHook("noop", func() { + x++ }) h.AddHook("noop", func() { - i++ + x++ }) - assert.Equal(t, i, 0, "should not execute any hook") + assert.Equal(t, x, 0, "should not execute any hook") h.RunHooks("x") - assert.Equal(t, i, 0, "should not execute any hook") + assert.Equal(t, x, 0, "should not execute any hook") h.RunHooks("sample") - assert.Equal(t, i, 2, "should only execute event 'sample'") + assert.Equal(t, x, 100, "should only execute event 'sample'") } From 94d83bc46436f7f340eefb52cf437b82bda76159 Mon Sep 17 00:00:00 2001 From: raziman Date: Fri, 19 Feb 2021 22:23:26 +0800 Subject: [PATCH 12/21] setup hooks setup hooks --- player.go | 6 +++++- start.go | 34 ++++++++++++++++++++++++++-------- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/player.go b/player.go index 3b79367..eebfc09 100644 --- a/player.go +++ b/player.go @@ -124,7 +124,7 @@ func (p *Player) run(currSong *AudioFile) error { // starts playing the audio speaker.Play(p._volume) - gomu.hook.RunHooks("playing") + gomu.hook.RunHooks("new_song") p.isRunning = true @@ -194,6 +194,7 @@ next: } func (p *Player) pause() { + gomu.hook.RunHooks("pause") speaker.Lock() p.ctrl.Paused = true p.isRunning = false @@ -201,6 +202,7 @@ func (p *Player) pause() { } func (p *Player) play() { + gomu.hook.RunHooks("play") speaker.Lock() p.ctrl.Paused = false p.isRunning = true @@ -239,6 +241,8 @@ func (p *Player) togglePause() { // skips current song func (p *Player) skip() { + gomu.hook.RunHooks("skip") + if p.currentSong == nil { return } diff --git a/start.go b/start.go index cf859b5..8bb1396 100644 --- a/start.go +++ b/start.go @@ -63,13 +63,27 @@ func defineBuiltins() { } func setupHooks() { - gomu.hook.AddHook("playing", func() { - _, err := gomu.anko.Execute(`Event.run_hooks("playing")`) - if err != nil { - err = tracerr.Errorf("error execute hook: %w", err) - logError(err) - } - }) + + events := []string{ + "enter", + "new_song", + "skip", + "play", + "pause", + "exit", + } + + for _, event := range events { + name := event + gomu.hook.AddHook(name, func() { + src := fmt.Sprintf(`Event.run_hooks("%s")`, name) + _, err := gomu.anko.Execute(src) + if err != nil { + err = tracerr.Errorf("error execute hook: %w", err) + logError(err) + } + }) + } } // loadModules executes helper modules and default config that should only be @@ -150,7 +164,6 @@ module Keybinds { } } ` - setupHooks() _, err := gomu.anko.Execute(eventModule + listModule + keybindModule) if err != nil { return tracerr.Wrap(err) @@ -266,6 +279,7 @@ func start(application *tview.Application, args Args) { gomu = newGomu() gomu.command.defineCommands() defineBuiltins() + err := loadModules() if err != nil { die(err) @@ -276,6 +290,9 @@ func start(application *tview.Application, args Args) { die(err) } + setupHooks() + + gomu.hook.RunHooks("enter") gomu.args = args gomu.colors = newColor() @@ -413,4 +430,5 @@ func start(application *tview.Application, args Args) { die(err) } + gomu.hook.RunHooks("exit") } From 92765747e0e0b661ab177b4cd09aee64d0ba3b3e Mon Sep 17 00:00:00 2001 From: raziman Date: Fri, 19 Feb 2021 22:24:32 +0800 Subject: [PATCH 13/21] prevent from range over nil --- start.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/start.go b/start.go index 8bb1396..758d22a 100644 --- a/start.go +++ b/start.go @@ -138,6 +138,10 @@ module Event { func run_hooks(name) { hooks = events[name] + if hooks == nil { + return + } + for hook in hooks { hook() } From 2d65e54e6457603a725aae5b40624c961bb21e55 Mon Sep 17 00:00:00 2001 From: raziman Date: Sat, 20 Feb 2021 11:18:03 +0800 Subject: [PATCH 14/21] change NewAnko to return *Anko --- anko/anko.go | 4 ++-- gomu.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/anko/anko.go b/anko/anko.go index 74c9d32..ad2338e 100644 --- a/anko/anko.go +++ b/anko/anko.go @@ -17,7 +17,7 @@ type Anko struct { env *env.Env } -func NewAnko() Anko { +func NewAnko() *Anko { env := core.Import(env.NewEnv()) importToX(env) @@ -45,7 +45,7 @@ func NewAnko() Anko { panic(err) } - return Anko{env} + return &Anko{env} } // Define defines new symbol and value to the Anko env. diff --git a/gomu.go b/gomu.go index cbb4fe2..e4720dd 100644 --- a/gomu.go +++ b/gomu.go @@ -25,7 +25,7 @@ type Gomu struct { prevPanel Panel panels []Panel args Args - anko anko.Anko + anko *anko.Anko hook *hook.EventHook } From f712fbab7554ab1adaf5fd27479c3deb35d1bd22 Mon Sep 17 00:00:00 2001 From: raziman Date: Sat, 20 Feb 2021 11:18:33 +0800 Subject: [PATCH 15/21] add hook tests --- start.go | 16 +++++++++------- start_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 7 deletions(-) create mode 100644 start_test.go diff --git a/start.go b/start.go index 758d22a..edb01c8 100644 --- a/start.go +++ b/start.go @@ -13,6 +13,8 @@ import ( "syscall" "github.com/gdamore/tcell/v2" + "github.com/issadarkthing/gomu/anko" + "github.com/issadarkthing/gomu/hook" "github.com/rivo/tview" "github.com/ztrue/tracerr" ) @@ -62,7 +64,7 @@ func defineBuiltins() { gomu.anko.Define("shell", shell) } -func setupHooks() { +func setupHooks(hook *hook.EventHook, anko *anko.Anko) { events := []string{ "enter", @@ -75,9 +77,9 @@ func setupHooks() { for _, event := range events { name := event - gomu.hook.AddHook(name, func() { + hook.AddHook(name, func() { src := fmt.Sprintf(`Event.run_hooks("%s")`, name) - _, err := gomu.anko.Execute(src) + _, err := anko.Execute(src) if err != nil { err = tracerr.Errorf("error execute hook: %w", err) logError(err) @@ -88,7 +90,7 @@ func setupHooks() { // loadModules executes helper modules and default config that should only be // executed once -func loadModules() error { +func loadModules(env *anko.Anko) error { const listModule = ` module List { @@ -168,7 +170,7 @@ module Keybinds { } } ` - _, err := gomu.anko.Execute(eventModule + listModule + keybindModule) + _, err := env.Execute(eventModule + listModule + keybindModule) if err != nil { return tracerr.Wrap(err) } @@ -284,7 +286,7 @@ func start(application *tview.Application, args Args) { gomu.command.defineCommands() defineBuiltins() - err := loadModules() + err := loadModules(gomu.anko) if err != nil { die(err) } @@ -294,7 +296,7 @@ func start(application *tview.Application, args Args) { die(err) } - setupHooks() + setupHooks(gomu.hook, gomu.anko) gomu.hook.RunHooks("enter") gomu.args = args diff --git a/start_test.go b/start_test.go new file mode 100644 index 0000000..6c302f7 --- /dev/null +++ b/start_test.go @@ -0,0 +1,47 @@ +package main + +import ( + "testing" + + "github.com/issadarkthing/gomu/anko" + "github.com/issadarkthing/gomu/hook" + "github.com/stretchr/testify/assert" +) + + +func TestSetupHooks(t *testing.T) { + + gomu := newGomu() + gomu.anko = anko.NewAnko() + gomu.hook = hook.NewEventHook() + + err := loadModules(gomu.anko) + if err != nil { + t.Error(err) + } + + setupHooks(gomu.hook, gomu.anko) + + const src = ` +i = 0 + +Event.add_hook("skip", func() { + i++ +}) + ` + + _, err = gomu.anko.Execute(src) + if err != nil { + t.Error(err) + } + + gomu.hook.RunHooks("enter") + + for i := 0; i < 12; i++ { + gomu.hook.RunHooks("skip") + } + + got := gomu.anko.GetInt("i") + + assert.Equal(t, 12, got) +} From 41d1ed2d15f2dcdbeae00fbd2978115dc613a44b Mon Sep 17 00:00:00 2001 From: raziman Date: Sat, 20 Feb 2021 15:37:22 +0800 Subject: [PATCH 16/21] fix panic when pressing up key in repl --- popup.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/popup.go b/popup.go index 12e7c1c..7db3a3b 100644 --- a/popup.go +++ b/popup.go @@ -603,16 +603,20 @@ func replPopup() { switch event.Key() { case tcell.KeyUp: - input.SetText(history[upCount]) - if upCount < len(history)-1 { + if upCount < len(history) { + input.SetText(history[upCount]) upCount++ } case tcell.KeyDown: if upCount > 0 { - upCount-- + if upCount == len(history) { + upCount -= 2 + } else { + upCount -= 1 + } input.SetText(history[upCount]) } else if upCount == 0 { input.SetText("") From e2a5f1fe992755e44b1e901aa278445d375b3f1a Mon Sep 17 00:00:00 2001 From: raziman Date: Sat, 20 Feb 2021 16:21:04 +0800 Subject: [PATCH 17/21] make autocompletion async --- command.go | 97 +------------------------------------------- popup.go | 117 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 96 deletions(-) diff --git a/command.go b/command.go index f8e1140..f4a044b 100644 --- a/command.go +++ b/command.go @@ -1,10 +1,6 @@ package main import ( - "fmt" - "time" - - "github.com/gdamore/tcell/v2" "github.com/rivo/tview" "github.com/ztrue/tracerr" ) @@ -66,98 +62,7 @@ func (c Command) defineCommands() { }) c.define("youtube_search", func() { - - popupId := "youtube-search-input-popup" - - input := newInputPopup(popupId, " Youtube Search ", "search: ", "") - - // quick hack to change the autocomplete text color - tview.Styles.PrimitiveBackgroundColor = tcell.ColorBlack - input.SetAutocompleteFunc(func(currentText string) (entries []string) { - - if currentText == "" { - return []string{} - } - - suggestions, err := getSuggestions(currentText) - if err != nil { - logError(err) - } - - return suggestions - }) - - input.SetDoneFunc(func(key tcell.Key) { - - switch key { - case tcell.KeyEnter: - search := input.GetText() - defaultTimedPopup(" Youtube Search ", "Searching for "+search) - gomu.pages.RemovePage(popupId) - gomu.popups.pop() - - go func() { - - results, err := getSearchResult(search) - if err != nil { - logError(err) - defaultTimedPopup(" Error ", err.Error()) - return - } - - titles := []string{} - urls := make(map[string]string) - - for _, result := range results { - duration, err := time.ParseDuration(fmt.Sprintf("%ds", result.LengthSeconds)) - if err != nil { - logError(err) - return - } - - durationText := fmt.Sprintf("[ %s ] ", fmtDuration(duration)) - title := durationText + result.Title - - urls[title] = `https://www.youtube.com/watch?v=` + result.VideoId - - titles = append(titles, title) - } - - searchPopup("Youtube Videos", titles, func(title string) { - - audioFile := gomu.playlist.getCurrentFile() - - var dir *tview.TreeNode - - if audioFile.isAudioFile { - dir = audioFile.parent - } else { - dir = audioFile.node - } - - go func() { - url := urls[title] - if err := ytdl(url, dir); err != nil { - logError(err) - } - gomu.playlist.refresh() - }() - gomu.app.SetFocus(gomu.prevPanel.(tview.Primitive)) - }) - - gomu.app.Draw() - }() - - case tcell.KeyEscape: - gomu.pages.RemovePage(popupId) - gomu.popups.pop() - gomu.app.SetFocus(gomu.prevPanel.(tview.Primitive)) - - default: - input.Autocomplete() - } - - }) + ytSearchPopup() }) c.define("download_audio", func() { diff --git a/popup.go b/popup.go index 7db3a3b..b6e55eb 100644 --- a/popup.go +++ b/popup.go @@ -6,6 +6,7 @@ import ( "fmt" "regexp" "strings" + "sync" "time" "unicode/utf8" @@ -665,3 +666,119 @@ func replPopup() { gomu.pages.AddPage(popupId, center(flex, 90, 30), true, true) gomu.popups.push(flex) } + +func ytSearchPopup() { + + popupId := "youtube-search-input-popup" + + input := newInputPopup(popupId, " Youtube Search ", "search: ", "") + + + var mutex sync.Mutex + prefixMap := make(map[string][]string) + + // quick hack to change the autocomplete text color + tview.Styles.PrimitiveBackgroundColor = tcell.ColorBlack + input.SetAutocompleteFunc(func(currentText string) []string { + + // Ignore empty text. + prefix := strings.TrimSpace(strings.ToLower(currentText)) + if prefix == "" { + return nil + } + + mutex.Lock() + defer mutex.Unlock() + entries, ok := prefixMap[prefix] + if ok { + return entries + } + + go func() { + suggestions, err := getSuggestions(currentText) + if err != nil { + logError(err) + return + } + + mutex.Lock() + prefixMap[prefix] = suggestions + mutex.Unlock() + + input.Autocomplete() + gomu.app.Draw() + }() + + return nil + }) + + input.SetDoneFunc(func(key tcell.Key) { + + switch key { + case tcell.KeyEnter: + search := input.GetText() + defaultTimedPopup(" Youtube Search ", "Searching for "+search) + gomu.pages.RemovePage(popupId) + gomu.popups.pop() + + go func() { + + results, err := getSearchResult(search) + if err != nil { + logError(err) + defaultTimedPopup(" Error ", err.Error()) + return + } + + titles := []string{} + urls := make(map[string]string) + + for _, result := range results { + duration, err := time.ParseDuration(fmt.Sprintf("%ds", result.LengthSeconds)) + if err != nil { + logError(err) + return + } + + durationText := fmt.Sprintf("[ %s ] ", fmtDuration(duration)) + title := durationText + result.Title + + urls[title] = `https://www.youtube.com/watch?v=` + result.VideoId + + titles = append(titles, title) + } + + searchPopup("Youtube Videos", titles, func(title string) { + + audioFile := gomu.playlist.getCurrentFile() + + var dir *tview.TreeNode + + if audioFile.isAudioFile { + dir = audioFile.parent + } else { + dir = audioFile.node + } + + go func() { + url := urls[title] + if err := ytdl(url, dir); err != nil { + logError(err) + } + gomu.playlist.refresh() + }() + gomu.app.SetFocus(gomu.prevPanel.(tview.Primitive)) + }) + + gomu.app.Draw() + }() + + case tcell.KeyEscape: + gomu.pages.RemovePage(popupId) + gomu.popups.pop() + gomu.app.SetFocus(gomu.prevPanel.(tview.Primitive)) + + } + + }) +} From d84fc56fadebad3556f372af36d5cd7e877e5843 Mon Sep 17 00:00:00 2001 From: raziman Date: Sat, 20 Feb 2021 22:56:55 +0800 Subject: [PATCH 18/21] add command test --- command_test.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 command_test.go diff --git a/command_test.go b/command_test.go new file mode 100644 index 0000000..c7f968a --- /dev/null +++ b/command_test.go @@ -0,0 +1,25 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + + +func TestGetFn(t *testing.T) { + + c := newCommand() + + c.define("sample", func() {}) + + f, err := c.getFn("sample") + if err != nil { + t.Error(err) + } + + assert.NotNil(t, f) + + f, err = c.getFn("x") + assert.Error(t, err) +} From b0a3885b871d27c28f1b251ad65fead72faf601a Mon Sep 17 00:00:00 2001 From: raziman Date: Sun, 21 Feb 2021 09:32:22 +0800 Subject: [PATCH 19/21] avoid memory leak --- popup.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/popup.go b/popup.go index b6e55eb..5c5e694 100644 --- a/popup.go +++ b/popup.go @@ -51,6 +51,8 @@ func (s *Stack) pop() tview.Primitive { } last := s.popups[len(s.popups)-1] + s.popups[len(s.popups)-1] = nil // avoid memory leak + res := s.popups[:len(s.popups)-1] s.popups = res From 6209496daa55287f22d84e8cf54d1254d4f7d7f9 Mon Sep 17 00:00:00 2001 From: raziman Date: Sun, 21 Feb 2021 09:58:55 +0800 Subject: [PATCH 20/21] fix broken link --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 804e265..de1b36b 100644 --- a/README.md +++ b/README.md @@ -101,8 +101,8 @@ Each panel has it's own additional keybinding. To view the available keybinding ### Scripting -Gomu uses [anko](github.com/mattn/anko) as its scripting language. You can read -more about scripting at our [wiki](github.com/issadarkthing/gomu/wiki) +Gomu uses [anko](https://github.com/mattn/anko) as its scripting language. You can read +more about scripting at our [wiki](https://github.com/issadarkthing/gomu/wiki) ``` go From 6d9b9c63d0bfc625ed51b8faefe498820f2a4fad Mon Sep 17 00:00:00 2001 From: raziman Date: Sun, 21 Feb 2021 11:26:19 +0800 Subject: [PATCH 21/21] prevent from panic in repl causes app to crash --- popup.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/popup.go b/popup.go index 5c5e694..4b37f16 100644 --- a/popup.go +++ b/popup.go @@ -641,6 +641,12 @@ func replPopup() { input.SetText("") + defer func() { + if err := recover(); err != nil { + fmt.Fprintf(textview, "%s%s\n%v\n\n", prompt, text, err) + } + }() + res, err := gomu.anko.Execute(text) if err != nil { fmt.Fprintf(textview, "%s%s\n%v\n\n", prompt, text, err)