diff --git a/cointop/chart.go b/cointop/chart.go index d5739c7..1d26b5b 100644 --- a/cointop/chart.go +++ b/cointop/chart.go @@ -5,7 +5,6 @@ import ( "time" "github.com/gizak/termui" - "github.com/jroimartin/gocui" "github.com/miguelmota/cointop/pkg/color" ) @@ -98,14 +97,14 @@ func (ct *Cointop) selectedCoinName() string { return "" } -func (ct *Cointop) toggleCoinChart(g *gocui.Gui, v *gocui.View) error { +func (ct *Cointop) toggleCoinChart() error { highlightedcoin := ct.highlightedRowCoin() if ct.selectedcoin == highlightedcoin { ct.selectedcoin = nil } else { ct.selectedcoin = highlightedcoin } - ct.Update(func() { + ct.update(func() { ct.chartview.Clear() ct.updateMarketbar() ct.updateChart() diff --git a/cointop/cointop.go b/cointop/cointop.go index ed3494e..7bed8a0 100644 --- a/cointop/cointop.go +++ b/cointop/cointop.go @@ -37,6 +37,7 @@ type Cointop struct { maxtablewidth int shortcutkeys map[string]string config config // toml config + searchfield *gocui.View } // Run runs cointop @@ -71,11 +72,6 @@ func Run() { } } -func (ct *Cointop) quit(g *gocui.Gui, v *gocui.View) error { +func (ct *Cointop) quit() error { return gocui.ErrQuit } - -func (ct *Cointop) refresh(g *gocui.Gui, v *gocui.View) error { - ct.forcerefresh <- true - return nil -} diff --git a/cointop/headers.go b/cointop/headers.go index c214b93..3201a37 100644 --- a/cointop/headers.go +++ b/cointop/headers.go @@ -8,7 +8,7 @@ import ( ) func (ct *Cointop) updateHeaders() { - ct.Update(func() { + ct.update(func() { cm := map[string]func(a ...interface{}) string{ "rank": color.Black, "name": color.Black, diff --git a/cointop/help.go b/cointop/help.go index 1bc1b77..a51f53e 100644 --- a/cointop/help.go +++ b/cointop/help.go @@ -1,12 +1,11 @@ package cointop import ( - "github.com/jroimartin/gocui" "github.com/miguelmota/cointop/pkg/open" ) // TODO: create a help menu -func (ct *Cointop) openHelp(g *gocui.Gui, v *gocui.View) error { +func (ct *Cointop) openHelp() error { open.URL(ct.helpLink()) return nil } diff --git a/cointop/keybindings.go b/cointop/keybindings.go index 7caac1a..23f3adf 100644 --- a/cointop/keybindings.go +++ b/cointop/keybindings.go @@ -192,41 +192,43 @@ func (ct *Cointop) keybindings(g *gocui.Gui) error { v = strings.TrimSpace(strings.ToLower(v)) var fn func(g *gocui.Gui, v *gocui.View) error key, mod := ct.parseKeys(k) + view := "table" switch v { case "move_up": - fn = ct.cursorUp + fn = ct.keyfn(ct.cursorUp) case "move_down": - fn = ct.cursorDown + fn = ct.keyfn(ct.cursorDown) case "previous_page": - fn = ct.prevPage + fn = ct.keyfn(ct.prevPage) case "next_page": - fn = ct.nextPage + fn = ct.keyfn(ct.nextPage) case "page_down": - fn = ct.pageDown + fn = ct.keyfn(ct.pageDown) case "page_up": - fn = ct.pageUp + fn = ct.keyfn(ct.pageUp) case "sort_column_symbol": fn = ct.sortfn("symbol", false) case "move_to_page_first_row": - fn = ct.navigateFirstLine + fn = ct.keyfn(ct.navigateFirstLine) case "move_to_page_last_row": - fn = ct.navigateLastLine + fn = ct.keyfn(ct.navigateLastLine) case "open_link": - fn = ct.openLink + fn = ct.keyfn(ct.openLink) case "refresh": - fn = ct.refresh + fn = ct.keyfn(ct.refresh) case "sort_column_asc": - fn = ct.sortAsc + fn = ct.keyfn(ct.sortAsc) case "sort_column_desc": - fn = ct.sortDesc + fn = ct.keyfn(ct.sortDesc) case "sort_left_column": - fn = ct.sortPrevCol + fn = ct.keyfn(ct.sortPrevCol) case "sort_right_column": - fn = ct.sortNextCol + fn = ct.keyfn(ct.sortNextCol) case "help": - fn = ct.openHelp + fn = ct.keyfn(ct.openHelp) + view = "" case "first_page": - fn = ct.firstPage + fn = ct.keyfn(ct.firstPage) case "sort_column_1h_change": fn = ct.sortfn("1hchange", true) case "sort_column_24h_change": @@ -236,15 +238,15 @@ func (ct *Cointop) keybindings(g *gocui.Gui) error { case "sort_column_available_supply": fn = ct.sortfn("availablesupply", true) case "toggle_row_chart": - fn = ct.toggleCoinChart + fn = ct.keyfn(ct.toggleCoinChart) case "move_to_page_visible_first_row": - fn = ct.navigatePageFirstLine + fn = ct.keyfn(ct.navigatePageFirstLine) case "move_to_page_visible_last_row": - fn = ct.navigatePageLastLine + fn = ct.keyfn(ct.navigatePageLastLine) case "sort_column_market_cap": fn = ct.sortfn("marketcap", true) case "move_to_page_visible_middle_row": - fn = ct.navigatePageMiddleLine + fn = ct.keyfn(ct.navigatePageMiddleLine) case "sort_column_name": fn = ct.sortfn("name", true) case "sort_column_price": @@ -258,30 +260,42 @@ func (ct *Cointop) keybindings(g *gocui.Gui) error { case "sort_column_24h_volume": fn = ct.sortfn("24hvolume", true) case "last_page": - fn = ct.lastPage + fn = ct.keyfn(ct.lastPage) case "quit": - fn = ct.quit + fn = ct.keyfn(ct.quit) + view = "" + case "open_search": + fn = ct.keyfn(ct.openSearch) + view = "" default: - fn = keynoop + fn = ct.keyfn(ct.noop) } - ct.setKeybindingMod(key, mod, fn) + ct.setKeybindingMod(key, mod, fn, view) } + ct.setKeybindingMod(gocui.KeyEnter, gocui.ModNone, ct.keyfn(ct.doSearch), "searchfield") + ct.setKeybindingMod(gocui.KeyEsc, gocui.ModNone, ct.keyfn(ct.cancelSearch), "searchfield") return nil } -func (ct *Cointop) setKeybindingMod(key interface{}, mod gocui.Modifier, callback func(g *gocui.Gui, v *gocui.View) error) error { +func (ct *Cointop) setKeybindingMod(key interface{}, mod gocui.Modifier, callback func(g *gocui.Gui, v *gocui.View) error, view string) error { var err error switch t := key.(type) { case gocui.Key: - err = ct.g.SetKeybinding("", t, mod, callback) + err = ct.g.SetKeybinding(view, t, mod, callback) case rune: - err = ct.g.SetKeybinding("", t, mod, callback) + err = ct.g.SetKeybinding(view, t, mod, callback) } return err } -func keynoop(g *gocui.Gui, v *gocui.View) error { +func (ct *Cointop) keyfn(fn func() error) func(g *gocui.Gui, v *gocui.View) error { + return func(g *gocui.Gui, v *gocui.View) error { + return fn() + } +} + +func (ct *Cointop) noop() error { return nil } diff --git a/cointop/layout.go b/cointop/layout.go index b0993fa..52bb6de 100644 --- a/cointop/layout.go +++ b/cointop/layout.go @@ -1,6 +1,7 @@ package cointop import ( + "fmt" "math" "github.com/jroimartin/gocui" @@ -71,7 +72,35 @@ func (ct *Cointop) layout(g *gocui.Gui) error { ct.updateStatusbar("") } - ct.intervalFetchData() + if v, err := g.SetView("searchfield", 0, maxY-2, ct.maxtablewidth, maxY); err != nil { + if err != gocui.ErrUnknownView { + return err + } + ct.searchfield = v + ct.searchfield.Editable = true + ct.searchfield.Wrap = true + ct.searchfield.Frame = false + ct.searchfield.FgColor = gocui.ColorWhite + + // run only once on init + ct.g = g + g.SetViewOnBottom("searchfield") + ct.setActiveView("table") + ct.intervalFetchData() + } + return nil +} + +func (ct *Cointop) setActiveView(v string) error { + ct.g.SetViewOnTop(v) + ct.g.SetCurrentView(v) + if v == "searchfield" { + ct.searchfield.Clear() + ct.searchfield.SetCursor(1, 0) + fmt.Fprintf(ct.searchfield, "%s", "/") + } else if v == "table" { + ct.g.SetViewOnTop("statusbar") + } return nil } diff --git a/cointop/marketbar.go b/cointop/marketbar.go index 3062989..98a5f3c 100644 --- a/cointop/marketbar.go +++ b/cointop/marketbar.go @@ -19,7 +19,7 @@ func (ct *Cointop) updateMarketbar() error { if chartname == "" { chartname = "Global" } - ct.Update(func() { + ct.update(func() { ct.marketview.Clear() fmt.Fprintln( ct.marketview, diff --git a/cointop/navigation.go b/cointop/navigation.go index c3c2e87..748c975 100644 --- a/cointop/navigation.go +++ b/cointop/navigation.go @@ -1,9 +1,5 @@ package cointop -import ( - "github.com/jroimartin/gocui" -) - func (ct *Cointop) getCurrentPage() int { return ct.page + 1 } @@ -12,11 +8,28 @@ func (ct *Cointop) getTotalPages() int { return (ct.getListCount() / ct.perpage) + 1 } +func (ct *Cointop) getTotalPerPage() int { + return ct.perpage +} + func (ct *Cointop) getListCount() int { return len(ct.allcoins) } -func (ct *Cointop) cursorDown(g *gocui.Gui, v *gocui.View) error { +func (ct *Cointop) setPage(page int) int { + if (page*ct.perpage) <= ct.getListCount() && page >= 0 { + ct.page = page + } + return ct.page +} + +func (ct *Cointop) highlightRow(idx int) error { + cx, _ := ct.tableview.Cursor() + ct.tableview.SetCursor(cx, idx) + return nil +} + +func (ct *Cointop) cursorDown() error { if ct.tableview == nil { return nil } @@ -37,7 +50,7 @@ func (ct *Cointop) cursorDown(g *gocui.Gui, v *gocui.View) error { return nil } -func (ct *Cointop) cursorUp(g *gocui.Gui, v *gocui.View) error { +func (ct *Cointop) cursorUp() error { if ct.tableview == nil { return nil } @@ -53,7 +66,7 @@ func (ct *Cointop) cursorUp(g *gocui.Gui, v *gocui.View) error { return nil } -func (ct *Cointop) pageDown(g *gocui.Gui, v *gocui.View) error { +func (ct *Cointop) pageDown() error { if ct.tableview == nil { return nil } @@ -79,7 +92,7 @@ func (ct *Cointop) pageDown(g *gocui.Gui, v *gocui.View) error { return nil } -func (ct *Cointop) pageUp(g *gocui.Gui, v *gocui.View) error { +func (ct *Cointop) pageUp() error { if ct.tableview == nil { return nil } @@ -103,7 +116,7 @@ func (ct *Cointop) pageUp(g *gocui.Gui, v *gocui.View) error { return nil } -func (ct *Cointop) navigateFirstLine(g *gocui.Gui, v *gocui.View) error { +func (ct *Cointop) navigateFirstLine() error { if ct.tableview == nil { return nil } @@ -119,7 +132,7 @@ func (ct *Cointop) navigateFirstLine(g *gocui.Gui, v *gocui.View) error { return nil } -func (ct *Cointop) navigateLastLine(g *gocui.Gui, v *gocui.View) error { +func (ct *Cointop) navigateLastLine() error { if ct.tableview == nil { return nil } @@ -138,7 +151,7 @@ func (ct *Cointop) navigateLastLine(g *gocui.Gui, v *gocui.View) error { return nil } -func (ct *Cointop) navigatePageFirstLine(g *gocui.Gui, v *gocui.View) error { +func (ct *Cointop) navigatePageFirstLine() error { if ct.tableview == nil { return nil } @@ -150,7 +163,7 @@ func (ct *Cointop) navigatePageFirstLine(g *gocui.Gui, v *gocui.View) error { return nil } -func (ct *Cointop) navigatePageMiddleLine(g *gocui.Gui, v *gocui.View) error { +func (ct *Cointop) navigatePageMiddleLine() error { if ct.tableview == nil { return nil } @@ -163,7 +176,7 @@ func (ct *Cointop) navigatePageMiddleLine(g *gocui.Gui, v *gocui.View) error { return nil } -func (ct *Cointop) navigatePageLastLine(g *gocui.Gui, v *gocui.View) error { +func (ct *Cointop) navigatePageLastLine() error { if ct.tableview == nil { return nil } @@ -176,32 +189,28 @@ func (ct *Cointop) navigatePageLastLine(g *gocui.Gui, v *gocui.View) error { return nil } -func (ct *Cointop) prevPage(g *gocui.Gui, v *gocui.View) error { - if (ct.page - 1) >= 0 { - ct.page = ct.page - 1 - } +func (ct *Cointop) prevPage() error { + ct.setPage(ct.page - 1) ct.updateTable() ct.rowChanged() return nil } -func (ct *Cointop) nextPage(g *gocui.Gui, v *gocui.View) error { - if ((ct.page + 1) * ct.perpage) <= ct.getListCount() { - ct.page = ct.page + 1 - } +func (ct *Cointop) nextPage() error { + ct.setPage(ct.page + 1) ct.updateTable() ct.rowChanged() return nil } -func (ct *Cointop) firstPage(g *gocui.Gui, v *gocui.View) error { +func (ct *Cointop) firstPage() error { ct.page = 0 ct.updateTable() ct.rowChanged() return nil } -func (ct *Cointop) lastPage(g *gocui.Gui, v *gocui.View) error { +func (ct *Cointop) lastPage() error { ct.page = ct.getListCount() / ct.perpage ct.updateTable() ct.rowChanged() diff --git a/cointop/refresh.go b/cointop/refresh.go index 655e0d8..c1da215 100644 --- a/cointop/refresh.go +++ b/cointop/refresh.go @@ -5,6 +5,11 @@ import ( "time" ) +func (ct *Cointop) refresh() error { + ct.forcerefresh <- true + return nil +} + func (ct *Cointop) refreshAll() error { ct.refreshmux.Lock() ct.setRefreshStatus() diff --git a/cointop/search.go b/cointop/search.go new file mode 100644 index 0000000..cd80d89 --- /dev/null +++ b/cointop/search.go @@ -0,0 +1,62 @@ +package cointop + +import ( + "log" + "regexp" + "strings" +) + +func (ct *Cointop) openSearch() error { + ct.setActiveView("searchfield") + return nil +} + +func (ct *Cointop) cancelSearch() error { + ct.setActiveView("table") + return nil +} + +func (ct *Cointop) doSearch() error { + ct.searchfield.Rewind() + b := make([]byte, 100) + n, err := ct.searchfield.Read(b) + defer ct.setActiveView("table") + if err != nil { + return nil + } + if n == 0 { + return nil + } + q := string(b) + // remove slash + regex := regexp.MustCompile(`/(.*)`) + matches := regex.FindStringSubmatch(q) + if len(matches) > 0 { + q = matches[1] + } + return ct.search(q) +} + +func (ct *Cointop) search(q string) error { + q = strings.TrimSpace(strings.ToLower(q)) + for i := range ct.allcoins { + coin := ct.allcoins[i] + if strings.ToLower(coin.Name) == q || strings.ToLower(coin.Symbol) == q { + ct.goToGlobalIndex(i) + return nil + } + } + return nil +} + +func (ct *Cointop) goToGlobalIndex(idx int) error { + perpage := ct.getTotalPerPage() + atpage := idx / perpage + ct.setPage(atpage) + rowIndex := (idx % perpage) + ct.highlightRow(rowIndex) + log.Println(rowIndex) + ct.updateTable() + ct.rowChanged() + return nil +} diff --git a/cointop/shortcuts.go b/cointop/shortcuts.go index 5e821e9..393996a 100644 --- a/cointop/shortcuts.go +++ b/cointop/shortcuts.go @@ -36,6 +36,7 @@ func actionsMap() map[string]bool { "sort_left_column": true, "sort_right_column": true, "toggle_row_chart": true, + "open_search": true, } } @@ -90,5 +91,6 @@ func defaultShortcuts() map[string]string { "q": "quit", "$": "last_page", "?": "help", + "/": "open_search", } } diff --git a/cointop/sort.go b/cointop/sort.go index 97c2330..7ae7257 100644 --- a/cointop/sort.go +++ b/cointop/sort.go @@ -68,7 +68,7 @@ func (ct *Cointop) sortfn(sortby string, desc bool) func(g *gocui.Gui, v *gocui. } ct.sort(sortby, desc, ct.coins) - ct.Update(func() { + ct.update(func() { ct.tableview.Clear() ct.updateTable() }) @@ -87,10 +87,10 @@ func (ct *Cointop) getSortColIndex() int { return 0 } -func (ct *Cointop) sortAsc(g *gocui.Gui, v *gocui.View) error { +func (ct *Cointop) sortAsc() error { ct.sortdesc = false ct.sort(ct.sortby, ct.sortdesc, ct.coins) - ct.Update(func() { + ct.update(func() { ct.tableview.Clear() ct.updateTable() }) @@ -98,10 +98,10 @@ func (ct *Cointop) sortAsc(g *gocui.Gui, v *gocui.View) error { return nil } -func (ct *Cointop) sortDesc(g *gocui.Gui, v *gocui.View) error { +func (ct *Cointop) sortDesc() error { ct.sortdesc = true ct.sort(ct.sortby, ct.sortdesc, ct.coins) - ct.Update(func() { + ct.update(func() { ct.tableview.Clear() ct.updateTable() }) @@ -109,7 +109,7 @@ func (ct *Cointop) sortDesc(g *gocui.Gui, v *gocui.View) error { return nil } -func (ct *Cointop) sortPrevCol(g *gocui.Gui, v *gocui.View) error { +func (ct *Cointop) sortPrevCol() error { nextsortby := colorder[0] i := ct.getSortColIndex() k := i - 1 @@ -118,7 +118,7 @@ func (ct *Cointop) sortPrevCol(g *gocui.Gui, v *gocui.View) error { } nextsortby = colorder[k] ct.sort(nextsortby, ct.sortdesc, ct.coins) - ct.Update(func() { + ct.update(func() { ct.tableview.Clear() ct.updateTable() }) @@ -126,7 +126,7 @@ func (ct *Cointop) sortPrevCol(g *gocui.Gui, v *gocui.View) error { return nil } -func (ct *Cointop) sortNextCol(g *gocui.Gui, v *gocui.View) error { +func (ct *Cointop) sortNextCol() error { nextsortby := colorder[0] l := len(colorder) i := ct.getSortColIndex() @@ -136,7 +136,7 @@ func (ct *Cointop) sortNextCol(g *gocui.Gui, v *gocui.View) error { } nextsortby = colorder[k] ct.sort(nextsortby, ct.sortdesc, ct.coins) - ct.Update(func() { + ct.update(func() { ct.tableview.Clear() ct.updateTable() }) diff --git a/cointop/statusbar.go b/cointop/statusbar.go index c456ceb..f8e3eed 100644 --- a/cointop/statusbar.go +++ b/cointop/statusbar.go @@ -7,7 +7,7 @@ import ( ) func (ct *Cointop) updateStatusbar(s string) { - ct.Update(func() { + ct.update(func() { ct.statusbarview.Clear() currpage := ct.getCurrentPage() totalpages := ct.getTotalPages() @@ -18,7 +18,7 @@ func (ct *Cointop) updateStatusbar(s string) { func (ct *Cointop) refreshRowLink() { url := ct.rowLink() - ct.Update(func() { + ct.update(func() { ct.updateStatusbar(fmt.Sprintf("[↵]%s", url)) }) } diff --git a/cointop/table.go b/cointop/table.go index 5863d13..a769dda 100644 --- a/cointop/table.go +++ b/cointop/table.go @@ -6,11 +6,9 @@ import ( "strings" "time" - "github.com/jroimartin/gocui" apt "github.com/miguelmota/cointop/pkg/api/types" "github.com/miguelmota/cointop/pkg/color" "github.com/miguelmota/cointop/pkg/humanize" - "github.com/miguelmota/cointop/pkg/open" "github.com/miguelmota/cointop/pkg/table" ) @@ -78,7 +76,7 @@ func (ct *Cointop) refreshTable() error { ) } - ct.Update(func() { + ct.update(func() { ct.tableview.Clear() ct.table.Format().Fprint(ct.tableview) }) @@ -108,8 +106,3 @@ func (ct *Cointop) rowLink() string { slug := strings.ToLower(strings.Replace(ct.highlightedRowCoin().Name, " ", "-", -1)) return fmt.Sprintf("https://coinmarketcap.com/currencies/%s", slug) } - -func (ct *Cointop) openLink(g *gocui.Gui, v *gocui.View) error { - open.URL(ct.rowLink()) - return nil -} diff --git a/cointop/update.go b/cointop/update.go index 94a1c72..049a3c9 100644 --- a/cointop/update.go +++ b/cointop/update.go @@ -2,8 +2,8 @@ package cointop import "github.com/jroimartin/gocui" -// Update update view -func (ct *Cointop) Update(f func()) { +// update update view +func (ct *Cointop) update(f func()) { ct.g.Update(func(g *gocui.Gui) error { f() return nil diff --git a/cointop/util.go b/cointop/util.go new file mode 100644 index 0000000..2186d0d --- /dev/null +++ b/cointop/util.go @@ -0,0 +1,8 @@ +package cointop + +import "github.com/miguelmota/cointop/pkg/open" + +func (ct *Cointop) openLink() error { + open.URL(ct.rowLink()) + return nil +}