From 0a0530de5a6829159fbbaccf9605da94864c7121 Mon Sep 17 00:00:00 2001 From: Miguel Mota Date: Sat, 20 Feb 2021 00:51:08 -0800 Subject: [PATCH] Add option to keep row focus on sort --- cointop/cointop.go | 3 ++ cointop/config.go | 19 ++++++- cointop/navigation.go | 84 ++++++++++++++++++++--------- cointop/sort.go | 13 ++++- cointop/table.go | 14 +++-- pkg/api/impl/coingecko/coingecko.go | 2 +- pkg/chartplot/chartplot.go | 3 ++ 7 files changed, 104 insertions(+), 34 deletions(-) diff --git a/cointop/cointop.go b/cointop/cointop.go index 0e54335f..56428e3d 100644 --- a/cointop/cointop.go +++ b/cointop/cointop.go @@ -53,6 +53,7 @@ type State struct { hideMarketbar bool hideChart bool hideStatusbar bool + keepRowFocusOnSort bool lastSelectedRowIndex int marketBarHeight int page int @@ -197,6 +198,7 @@ func NewCointop(config *Config) (*Cointop, error) { } ct := &Cointop{ + // defaults apiChoice: CoinGecko, apiKeys: new(APIKeys), forceRefresh: make(chan bool), @@ -222,6 +224,7 @@ func NewCointop(config *Config) (*Cointop, error) { hideMarketbar: config.HideMarketbar, hideChart: config.HideChart, hideStatusbar: config.HideStatusbar, + keepRowFocusOnSort: false, marketBarHeight: 1, onlyTable: config.OnlyTable, refreshRate: 60 * time.Second, diff --git a/cointop/config.go b/cointop/config.go index 4ef1e467..e8445b76 100644 --- a/cointop/config.go +++ b/cointop/config.go @@ -54,7 +54,7 @@ func (ct *Cointop) SetupConfig() error { if err := ct.parseConfig(); err != nil { return err } - if err := ct.loadTableColumnsFromConfig(); err != nil { + if err := ct.loadTableConfig(); err != nil { return err } if err := ct.loadShortcutsFromConfig(); err != nil { @@ -280,6 +280,8 @@ func (ct *Cointop) configToToml() ([]byte, error) { var coinsTableColumnsIfc interface{} = ct.State.coinsTableColumns tableMapIfc := map[string]interface{}{} tableMapIfc["columns"] = coinsTableColumnsIfc + var keepRowFocusOnSortIfc interface{} = ct.State.keepRowFocusOnSort + tableMapIfc["keep_row_focus_on_sort"] = keepRowFocusOnSortIfc var inputs = &config{ API: apiChoiceIfc, @@ -306,6 +308,20 @@ func (ct *Cointop) configToToml() ([]byte, error) { return b.Bytes(), nil } +// LoadTableConfig loads table config from toml config into state struct +func (ct *Cointop) loadTableConfig() error { + err := ct.loadTableColumnsFromConfig() + if err != nil { + return err + } + + keepRowFocusOnSortIfc, ok := ct.config.Table["keep_row_focus_on_sort"] + if ok { + ct.State.keepRowFocusOnSort = keepRowFocusOnSortIfc.(bool) + } + return nil +} + // LoadTableColumnsFromConfig loads preferred coins table columns from config file to struct func (ct *Cointop) loadTableColumnsFromConfig() error { ct.debuglog("loadTableColumnsFromConfig()") @@ -328,6 +344,7 @@ func (ct *Cointop) loadTableColumnsFromConfig() error { ct.State.coinsTableColumns = columns } } + return nil } diff --git a/cointop/navigation.go b/cointop/navigation.go index cbd96220..92db9a14 100644 --- a/cointop/navigation.go +++ b/cointop/navigation.go @@ -1,6 +1,7 @@ package cointop import ( + "fmt" "math" ) @@ -45,7 +46,7 @@ func (ct *Cointop) SetPage(page int) int { // CursorDown moves the cursor one row down func (ct *Cointop) CursorDown() error { ct.debuglog("cursorDown()") - // NOTE: return if already at the bottom + // return if already at the bottom if ct.IsLastRow() { return nil } @@ -65,7 +66,7 @@ func (ct *Cointop) CursorDown() error { // CursorUp moves the cursor one row up func (ct *Cointop) CursorUp() error { ct.debuglog("cursorUp()") - // NOTE: return if already at the top + // return if already at the top if ct.IsFirstRow() { return nil } @@ -86,7 +87,7 @@ func (ct *Cointop) CursorUp() error { // PageDown moves the cursor one page down func (ct *Cointop) PageDown() error { ct.debuglog("pageDown()") - // NOTE: return if already at the bottom + // return if already at the bottom if ct.IsLastRow() { return nil } @@ -94,24 +95,24 @@ func (ct *Cointop) PageDown() error { ox, oy := ct.Views.Table.Origin() // this is prev origin position cx := ct.Views.Table.CursorX() // relative cursor position sy := ct.Views.Table.Height() // rows in visible view - k := oy + sy + y := oy + sy l := ct.TableRowsLen() // end of table if (oy + sy + sy) > l { - k = l - sy + y = l - sy } // select last row if next jump is out of bounds - if k < 0 { - k = 0 + if y < 0 { + y = 0 sy = l } - if err := ct.Views.Table.SetOrigin(ox, k); err != nil { + if err := ct.Views.Table.SetOrigin(ox, y); err != nil { return err } // move cursor to last line if can't scroll further - if k == oy { + if y == oy { if err := ct.Views.Table.SetCursor(cx, sy-1); err != nil { return err } @@ -123,7 +124,7 @@ func (ct *Cointop) PageDown() error { // PageUp moves the cursor one page up func (ct *Cointop) PageUp() error { ct.debuglog("pageUp()") - // NOTE: return if already at the top + // return if already at the top if ct.IsFirstRow() { return nil } @@ -151,7 +152,7 @@ func (ct *Cointop) PageUp() error { // NavigateFirstLine moves the cursor to the first row of the table func (ct *Cointop) NavigateFirstLine() error { ct.debuglog("navigateFirstLine()") - // NOTE: return if already at the top + // return if already at the top if ct.IsFirstRow() { return nil } @@ -172,7 +173,7 @@ func (ct *Cointop) NavigateFirstLine() error { // NavigateLastLine moves the cursor to the last row of the table func (ct *Cointop) NavigateLastLine() error { ct.debuglog("navigateLastLine()") - // NOTE: return if already at the bottom + // return if already at the bottom if ct.IsLastRow() { return nil } @@ -196,7 +197,7 @@ func (ct *Cointop) NavigateLastLine() error { // NavigatePageFirstLine moves the cursor to the visible first row of the table func (ct *Cointop) NavigatePageFirstLine() error { ct.debuglog("navigatePageFirstLine()") - // NOTE: return if already at the correct line + // return if already at the correct line if ct.IsPageFirstLine() { return nil } @@ -212,7 +213,7 @@ func (ct *Cointop) NavigatePageFirstLine() error { // NavigatePageMiddleLine moves the cursor to the visible middle row of the table func (ct *Cointop) NavigatePageMiddleLine() error { ct.debuglog("navigatePageMiddleLine()") - // NOTE: return if already at the correct line + // return if already at the correct line if ct.IsPageMiddleLine() { return nil } @@ -229,7 +230,7 @@ func (ct *Cointop) NavigatePageMiddleLine() error { // NavigatePageLastLine moves the cursor to the visible last row of the table func (ct *Cointop) navigatePageLastLine() error { ct.debuglog("navigatePageLastLine()") - // NOTE: return if already at the correct line + // return if already at the correct line if ct.IsPageLastLine() { return nil } @@ -247,7 +248,7 @@ func (ct *Cointop) navigatePageLastLine() error { func (ct *Cointop) NextPage() error { ct.debuglog("nextPage()") - // NOTE: return if already at the last page + // return if already at the last page if ct.IsLastPage() { return nil } @@ -262,7 +263,7 @@ func (ct *Cointop) NextPage() error { func (ct *Cointop) PrevPage() error { ct.debuglog("prevPage()") - // NOTE: return if already at the first page + // return if already at the first page if ct.IsFirstPage() { return nil } @@ -297,7 +298,7 @@ func (ct *Cointop) PrevPageTop() error { func (ct *Cointop) FirstPage() error { ct.debuglog("firstPage()") - // NOTE: return if already at the first page + // return if already at the first page if ct.IsFirstPage() { return nil } @@ -312,7 +313,7 @@ func (ct *Cointop) FirstPage() error { func (ct *Cointop) LastPage() error { ct.debuglog("lastPage()") - // NOTE: return if already at the last page + // return if already at the last page if ct.IsLastPage() { return nil } @@ -402,26 +403,57 @@ func (ct *Cointop) GoToGlobalIndex(idx int) error { return nil } -// HighlightRow highlights the row at index -func (ct *Cointop) HighlightRow(idx int) error { +// HighlightRow highlights the row at index within page +func (ct *Cointop) HighlightRow(pageRowIndex int) error { ct.debuglog("highlightRow()") ct.Views.Table.SetOrigin(0, 0) ct.Views.Table.SetCursor(0, 0) ox := ct.Views.Table.OriginX() cx := ct.Views.Table.CursorX() - sy := ct.Views.Table.Height() + h := ct.Views.Table.Height() perpage := ct.TotalPerPage() - p := idx % perpage - oy := (p / sy) * sy - cy := p % sy + oy := 0 + cy := 0 + if h > 0 { + _ = perpage + cy = pageRowIndex % h + oy = pageRowIndex - cy + // end of page + if pageRowIndex >= perpage-h { + oy = perpage - h + cy = h - (perpage - pageRowIndex) + } + } + ct.debuglog(fmt.Sprintf("highlightRow idx:%v h:%v cy:%v oy:%v", pageRowIndex, h, cy, oy)) if oy > 0 { ct.Views.Table.SetOrigin(ox, oy) } ct.Views.Table.SetCursor(cx, cy) - return nil } +// GoToCoinRow navigates to the row of the matched coin +func (ct *Cointop) GoToCoinRow(coin *Coin) error { + ct.debuglog("goToCoinRow()") + if coin == nil { + return nil + } + idx := ct.GetGlobalCoinIndex(coin) + return ct.GoToGlobalIndex(idx) +} + +// GetGlobalCoinIndex returns the index of the coin in from the coins list +func (ct *Cointop) GetGlobalCoinIndex(coin *Coin) int { + var idx int + for i, v := range ct.State.allCoins { + if v == coin { + idx = i + break + } + } + return idx +} + // CursorDownOrNextPage moves the cursor down one row or goes to the next page if cursor is on the last row func (ct *Cointop) CursorDownOrNextPage() error { ct.debuglog("CursorDownOrNextPage()") diff --git a/cointop/sort.go b/cointop/sort.go index c42b4024..7d11dcbe 100644 --- a/cointop/sort.go +++ b/cointop/sort.go @@ -138,7 +138,18 @@ func (ct *Cointop) SortToggle(sortBy string, desc bool) error { func (ct *Cointop) Sortfn(sortBy string, desc bool) func(g *gocui.Gui, v *gocui.View) error { ct.debuglog("sortfn()") return func(g *gocui.Gui, v *gocui.View) error { - return ct.SortToggle(sortBy, desc) + coin := ct.HighlightedRowCoin() + err := ct.SortToggle(sortBy, desc) + if err != nil { + return err + } + if ct.State.keepRowFocusOnSort { + err = ct.GoToCoinRow(coin) + if err != nil { + return err + } + } + return nil } } diff --git a/cointop/table.go b/cointop/table.go index 8a469ffe..e3fa32b5 100644 --- a/cointop/table.go +++ b/cointop/table.go @@ -63,10 +63,14 @@ func (ct *Cointop) RefreshTable() error { } ct.table.HideColumHeaders = true - // highlight last row if current row is out of bounds (can happen when switching views) - currentrow := ct.HighlightedRowIndex() - if len(ct.State.coins) > currentrow { - ct.HighlightRow(currentrow) + // highlight last row if current row is out of bounds (can happen when switching views). + // make sure to not highlight row when actively navigating, otherwise + // table will appear glitchy since this is method is async. + if ct.State.lastSelectedView != "" && ct.State.lastSelectedView != ct.State.selectedView { + currentRowIdx := ct.HighlightedRowIndex() + if len(ct.State.coins) > currentRowIdx { + ct.HighlightRow(currentRowIdx) + } } ct.UpdateUI(func() error { @@ -161,7 +165,7 @@ func (ct *Cointop) GetTableCoinsSlice() []*Coin { return sliced } -// HighlightedRowIndex returns the index of the highlighted row +// HighlightedRowIndex returns the index of the highlighted row within the per-page limit func (ct *Cointop) HighlightedRowIndex() int { ct.debuglog("HighlightedRowIndex()") oy := ct.Views.Table.OriginY() diff --git a/pkg/api/impl/coingecko/coingecko.go b/pkg/api/impl/coingecko/coingecko.go index 9380b74e..2aa3ecf1 100644 --- a/pkg/api/impl/coingecko/coingecko.go +++ b/pkg/api/impl/coingecko/coingecko.go @@ -33,7 +33,7 @@ func NewCoinGecko() *Service { client := gecko.NewClient(nil) svc := &Service{ client: client, - maxResultsPerPage: 250, + maxResultsPerPage: 250, // max is 250 maxPages: 10, cacheMap: sync.Map{}, } diff --git a/pkg/chartplot/chartplot.go b/pkg/chartplot/chartplot.go index 8dbbd4a1..c9101b33 100644 --- a/pkg/chartplot/chartplot.go +++ b/pkg/chartplot/chartplot.go @@ -89,6 +89,9 @@ func (c *ChartPlot) GetChartPoints(width int) [][]rune { func interpolateData(data []float64, width int) []float64 { var res []float64 + if len(data) == 0 { + return res + } stepFactor := float64(len(data)-1) / float64(width-1) res = append(res, data[0]) for i := 1; i < width-1; i++ {