From 5f073fecf957602e41437b131c801a26ea1f9dd4 Mon Sep 17 00:00:00 2001 From: Miguel Mota Date: Sun, 15 Sep 2019 00:08:36 -0700 Subject: [PATCH] Navigation bug fixes. Fixes #41 --- cointop/cache.go | 20 ++++++ cointop/chart.go | 9 +++ cointop/coin.go | 2 + cointop/cointop.go | 1 + cointop/config.go | 19 +++++ cointop/conversion.go | 41 +++++++---- cointop/debug.go | 24 +++++-- cointop/events.go | 1 + cointop/favorites.go | 18 ++++- cointop/help.go | 4 ++ cointop/keybindings.go | 2 +- cointop/layout.go | 1 + cointop/list.go | 23 ++++--- cointop/marketbar.go | 1 + cointop/navigation.go | 149 ++++++++++++++++++++++++++++++++++++++-- cointop/portfolio.go | 31 +++++++-- cointop/quit.go | 2 + cointop/refresh.go | 5 ++ cointop/save.go | 5 ++ cointop/search.go | 4 ++ cointop/selection.go | 2 + cointop/size.go | 5 ++ cointop/sort.go | 8 +++ cointop/statusbar.go | 2 + cointop/stdin.go | 1 + cointop/table.go | 110 +++++++++++++++++++---------- cointop/table_header.go | 1 + cointop/update.go | 8 +-- cointop/util.go | 3 +- cointop/version.go | 2 +- 30 files changed, 418 insertions(+), 86 deletions(-) diff --git a/cointop/cache.go b/cointop/cache.go index d869a66..a278b13 100644 --- a/cointop/cache.go +++ b/cointop/cache.go @@ -3,8 +3,28 @@ package cointop import ( "fmt" "strings" + "time" + + "github.com/miguelmota/cointop/cointop/common/filecache" ) func (ct *Cointop) cacheKey(key string) string { + ct.debuglog("cacheKey()") return strings.ToLower(fmt.Sprintf("%s_%s", ct.apiChoice, key)) } + +func (ct *Cointop) cacheAllCoinsSlugMap() { + ct.debuglog("cacheAllCoinsSlugMap()") + allCoinsSlugMap := make(map[string]*Coin) + ct.State.allCoinsSlugMap.Range(func(key, value interface{}) bool { + allCoinsSlugMap[key.(string)] = value.(*Coin) + return true + }) + + // NOTE: do not override with empty data on startup + if len(allCoinsSlugMap) != 0 { + cachekey := ct.cacheKey("allCoinsSlugMap") + ct.cache.Set(cachekey, allCoinsSlugMap, 10*time.Second) + filecache.Set(cachekey, allCoinsSlugMap, 24*time.Hour) + } +} diff --git a/cointop/chart.go b/cointop/chart.go index 12271d0..e888bc9 100644 --- a/cointop/chart.go +++ b/cointop/chart.go @@ -58,6 +58,7 @@ func chartRangesMap() map[string]time.Duration { // UpdateChart updates the chart view func (ct *Cointop) UpdateChart() error { + ct.debuglog("updateChart()") if ct.Views.Chart.Backing() == nil { return nil } @@ -106,6 +107,7 @@ func (ct *Cointop) UpdateChart() error { // ChartPoints calculates the the chart points func (ct *Cointop) ChartPoints(symbol string, name string) error { + ct.debuglog("chartPoints()") maxX := ct.ClampedWidth() chartPointsLock.Lock() @@ -209,6 +211,7 @@ func (ct *Cointop) ChartPoints(symbol string, name string) error { // PortfolioChart renders the portfolio chart func (ct *Cointop) PortfolioChart() error { + ct.debuglog("portfolioChart()") maxX := ct.ClampedWidth() chartPointsLock.Lock() defer chartPointsLock.Unlock() @@ -319,6 +322,7 @@ func (ct *Cointop) PortfolioChart() error { // NextChartRange sets the chart to the next range option func (ct *Cointop) NextChartRange() error { + ct.debuglog("nextChartRange()") sel := 0 max := len(ct.chartRanges) for i, k := range ct.chartRanges { @@ -339,6 +343,7 @@ func (ct *Cointop) NextChartRange() error { // PrevChartRange sets the chart to the prevous range option func (ct *Cointop) PrevChartRange() error { + ct.debuglog("prevChartRange()") sel := 0 for i, k := range ct.chartRanges { if k == ct.State.selectedChartRange { @@ -357,6 +362,7 @@ func (ct *Cointop) PrevChartRange() error { // FirstChartRange sets the chart to the first range option func (ct *Cointop) FirstChartRange() error { + ct.debuglog("firstChartRange()") ct.State.selectedChartRange = ct.chartRanges[0] go ct.UpdateChart() return nil @@ -364,6 +370,7 @@ func (ct *Cointop) FirstChartRange() error { // LastChartRange sets the chart to the last range option func (ct *Cointop) LastChartRange() error { + ct.debuglog("lastChartRange()") ct.State.selectedChartRange = ct.chartRanges[len(ct.chartRanges)-1] go ct.UpdateChart() return nil @@ -371,6 +378,7 @@ func (ct *Cointop) LastChartRange() error { // ToggleCoinChart toggles between the global chart and the coin chart func (ct *Cointop) ToggleCoinChart() error { + ct.debuglog("toggleCoinChart()") highlightedcoin := ct.HighlightedRowCoin() if ct.State.selectedCoin == highlightedcoin { ct.State.selectedCoin = nil @@ -386,6 +394,7 @@ func (ct *Cointop) ToggleCoinChart() error { // ShowChartLoader shows chart loading indicator func (ct *Cointop) ShowChartLoader() error { + ct.debuglog("showChartLoader()") ct.update(func() { if ct.Views.Chart.Backing() == nil { return diff --git a/cointop/coin.go b/cointop/coin.go index 55eb807..248c010 100644 --- a/cointop/coin.go +++ b/cointop/coin.go @@ -24,6 +24,7 @@ type Coin struct { } func (ct *Cointop) allCoins() []*Coin { + ct.debuglog("allCoins()") if ct.State.filterByFavorites { var list []*Coin for i := range ct.State.allCoins { @@ -50,6 +51,7 @@ func (ct *Cointop) allCoins() []*Coin { } func (ct *Cointop) coinBySymbol(symbol string) *Coin { + ct.debuglog("coinBySymbol()") for i := range ct.State.allCoins { coin := ct.State.allCoins[i] if coin.Symbol == symbol { diff --git a/cointop/cointop.go b/cointop/cointop.go index be075c6..191ebb3 100644 --- a/cointop/cointop.go +++ b/cointop/cointop.go @@ -332,6 +332,7 @@ func NewCointop(config *Config) (*Cointop, error) { // Run runs cointop func (ct *Cointop) Run() error { + ct.debuglog("run()") g, err := gocui.NewGui(gocui.Output256) if err != nil { return fmt.Errorf("new gocui: %v", err) diff --git a/cointop/config.go b/cointop/config.go index f7c7a54..b9fc65f 100644 --- a/cointop/config.go +++ b/cointop/config.go @@ -27,6 +27,7 @@ type config struct { } func (ct *Cointop) setupConfig() error { + ct.debuglog("setupConfig()") if err := ct.createConfigIfNotExists(); err != nil { return err } @@ -65,6 +66,7 @@ func (ct *Cointop) setupConfig() error { } func (ct *Cointop) createConfigIfNotExists() error { + ct.debuglog("createConfigIfNotExists()") err := ct.makeConfigDir() if err != nil { return err @@ -88,6 +90,7 @@ func (ct *Cointop) createConfigIfNotExists() error { } func (ct *Cointop) configDirPath() string { + ct.debuglog("configDirPath()") path := NormalizePath(ct.configFilepath) separator := string(filepath.Separator) parts := strings.Split(path, separator) @@ -95,10 +98,12 @@ func (ct *Cointop) configDirPath() string { } func (ct *Cointop) configPath() string { + ct.debuglog("configPath()") return NormalizePath(ct.configFilepath) } func (ct *Cointop) makeConfigDir() error { + ct.debuglog("makeConfigDir()") path := ct.configDirPath() if _, err := os.Stat(path); os.IsNotExist(err) { return os.MkdirAll(path, os.ModePerm) @@ -108,6 +113,7 @@ func (ct *Cointop) makeConfigDir() error { } func (ct *Cointop) makeConfigFile() error { + ct.debuglog("makeConfigFile()") path := ct.configPath() if _, err := os.Stat(path); os.IsNotExist(err) { fo, err := os.Create(path) @@ -127,6 +133,7 @@ func (ct *Cointop) makeConfigFile() error { } func (ct *Cointop) saveConfig() error { + ct.debuglog("saveConfig()") ct.saveMux.Lock() defer ct.saveMux.Unlock() path := ct.configPath() @@ -144,6 +151,7 @@ func (ct *Cointop) saveConfig() error { } func (ct *Cointop) parseConfig() error { + ct.debuglog("parseConfig()") var conf config path := ct.configPath() if _, err := toml.DecodeFile(path, &conf); err != nil { @@ -155,6 +163,7 @@ func (ct *Cointop) parseConfig() error { } func (ct *Cointop) configToToml() ([]byte, error) { + ct.debuglog("configToToml()") shortcutsIfcs := map[string]interface{}{} for k, v := range ct.State.shortcutKeys { var i interface{} = v @@ -218,6 +227,7 @@ func (ct *Cointop) configToToml() ([]byte, error) { } func (ct *Cointop) loadShortcutsFromConfig() error { + ct.debuglog("loadShortcutsFromConfig()") for k, ifc := range ct.config.Shortcuts { if v, ok := ifc.(string); ok { if !ct.ActionExists(v) { @@ -233,6 +243,7 @@ func (ct *Cointop) loadShortcutsFromConfig() error { } func (ct *Cointop) loadCurrencyFromConfig() error { + ct.debuglog("loadCurrencyFromConfig()") if currency, ok := ct.config.Currency.(string); ok { ct.State.currencyConversion = strings.ToUpper(currency) } @@ -240,6 +251,7 @@ func (ct *Cointop) loadCurrencyFromConfig() error { } func (ct *Cointop) loadDefaultViewFromConfig() error { + ct.debuglog("loadDefaultViewFromConfig()") if defaultView, ok := ct.config.DefaultView.(string); ok { defaultView = strings.ToLower(defaultView) switch defaultView { @@ -260,6 +272,7 @@ func (ct *Cointop) loadDefaultViewFromConfig() error { } func (ct *Cointop) loadAPIKeysFromConfig() error { + ct.debuglog("loadAPIKeysFromConfig()") for key, value := range ct.config.CoinMarketCap { k := strings.TrimSpace(strings.ToLower(key)) if k == "pro_api_key" { @@ -270,6 +283,7 @@ func (ct *Cointop) loadAPIKeysFromConfig() error { } func (ct *Cointop) loadColorschemeFromConfig() error { + ct.debuglog("loadColorschemeFromConfig()") if colorscheme, ok := ct.config.Colorscheme.(string); ok { ct.colorschemeName = colorscheme } @@ -278,6 +292,7 @@ func (ct *Cointop) loadColorschemeFromConfig() error { } func (ct *Cointop) loadRefreshRateFromConfig() error { + ct.debuglog("loadRefreshRateFromConfig()") if refreshRate, ok := ct.config.RefreshRate.(int64); ok { ct.State.refreshRate = time.Duration(uint(refreshRate)) * time.Second } @@ -286,6 +301,7 @@ func (ct *Cointop) loadRefreshRateFromConfig() error { } func (ct *Cointop) getColorschemeColors() (map[string]interface{}, error) { + ct.debuglog("getColorschemeColors()") var colors map[string]interface{} if ct.colorschemeName == "" { ct.colorschemeName = defaultColorscheme @@ -316,6 +332,7 @@ func (ct *Cointop) getColorschemeColors() (map[string]interface{}, error) { } func (ct *Cointop) loadAPIChoiceFromConfig() error { + ct.debuglog("loadAPIKeysFromConfig()") apiChoice, ok := ct.config.API.(string) if ok { apiChoice = strings.TrimSpace(strings.ToLower(apiChoice)) @@ -325,6 +342,7 @@ func (ct *Cointop) loadAPIChoiceFromConfig() error { } func (ct *Cointop) loadFavoritesFromConfig() error { + ct.debuglog("loadFavoritesFromConfig()") for k, arr := range ct.config.Favorites { // DEPRECATED: favorites by 'symbol' is deprecated because of collisions. Kept for backward compatibility. if k == "symbols" { @@ -345,6 +363,7 @@ func (ct *Cointop) loadFavoritesFromConfig() error { } func (ct *Cointop) loadPortfolioFromConfig() error { + ct.debuglog("loadPortfolioFromConfig()") for name, holdingsIfc := range ct.config.Portfolio { var holdings float64 var ok bool diff --git a/cointop/conversion.go b/cointop/conversion.go index 9179689..b378a66 100644 --- a/cointop/conversion.go +++ b/cointop/conversion.go @@ -131,6 +131,7 @@ func (ct *Cointop) sortedSupportedCurrencyConversions() []string { } func (ct *Cointop) updateConvertMenu() { + ct.debuglog("updateConvertMenu()") header := ct.colorscheme.MenuHeader(fmt.Sprintf(" Currency Conversion %s\n\n", pad.Left("[q] close menu ", ct.maxTableWidth-20, " "))) helpline := " Press the corresponding key to select currency for conversion\n\n" cnt := 0 @@ -184,27 +185,30 @@ func (ct *Cointop) updateConvertMenu() { }) } -func (ct *Cointop) setCurrencyConverstion(convert string) func() error { +func (ct *Cointop) setCurrencyConverstionFn(convert string) func() error { + ct.debuglog("setCurrencyConverstionFn()") return func() error { - ct.State.currencyConversion = convert ct.hideConvertMenu() + + // NOTE: return if the currency selection wasn't changed + if ct.State.currencyConversion == convert { + return nil + } + + ct.State.currencyConversion = convert + + if err := ct.save(); err != nil { + return err + } + go ct.refreshAll() return nil } } -// currencySymbol returns the symbol for the currency -func currencySymbol(currency string) string { - symbol, ok := currencySymbolMap[strings.ToUpper(currency)] - if ok { - return symbol - } - - return "$" -} - // currencySymbol returns the symbol for the currency func (ct *Cointop) currencySymbol() string { + ct.debuglog("currencySymbol()") symbol, ok := currencySymbolMap[strings.ToUpper(ct.State.currencyConversion)] if ok { return symbol @@ -214,6 +218,7 @@ func (ct *Cointop) currencySymbol() string { } func (ct *Cointop) showConvertMenu() error { + ct.debuglog("showConvertMenu()") ct.State.convertMenuVisible = true ct.updateConvertMenu() ct.SetActiveView(ct.Views.ConvertMenu.Name()) @@ -221,6 +226,7 @@ func (ct *Cointop) showConvertMenu() error { } func (ct *Cointop) hideConvertMenu() error { + ct.debuglog("hideConvertMenu()") ct.State.convertMenuVisible = false ct.SetViewOnBottom(ct.Views.ConvertMenu.Name()) ct.SetActiveView(ct.Views.Table.Name()) @@ -237,9 +243,20 @@ func (ct *Cointop) hideConvertMenu() error { } func (ct *Cointop) toggleConvertMenu() error { + ct.debuglog("toggleConvertMenu()") ct.State.convertMenuVisible = !ct.State.convertMenuVisible if ct.State.convertMenuVisible { return ct.showConvertMenu() } return ct.hideConvertMenu() } + +// currencySymbol returns the symbol for the currency +func currencySymbol(currency string) string { + symbol, ok := currencySymbolMap[strings.ToUpper(currency)] + if ok { + return symbol + } + + return "$" +} diff --git a/cointop/debug.go b/cointop/debug.go index 7bcf101..6687f4d 100644 --- a/cointop/debug.go +++ b/cointop/debug.go @@ -1,12 +1,28 @@ package cointop import ( + "fmt" + "os" + "time" + log "github.com/sirupsen/logrus" ) -func (ct *Cointop) debuglog(s string) { - if ct.debug { - // TODO: do status debug bar - log.Println(s) +func (ct *Cointop) debuglog(msg string) { + if !ct.debug { + return + } + + filename := "/tmp/cointop.log" + f, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600) + if err != nil { + log.Fatal(err) + } + + defer f.Close() + + text := fmt.Sprintf("%v %s\n", time.Now().Unix(), msg) + if _, err = f.WriteString(text); err != nil { + log.Fatal(err) } } diff --git a/cointop/events.go b/cointop/events.go index e332d0a..f2a185a 100644 --- a/cointop/events.go +++ b/cointop/events.go @@ -2,5 +2,6 @@ package cointop // RowChanged is called when the row is updated func (ct *Cointop) rowChanged() { + ct.debuglog("rowChanged()") ct.RefreshRowLink() } diff --git a/cointop/favorites.go b/cointop/favorites.go index e400dc3..d770b3a 100644 --- a/cointop/favorites.go +++ b/cointop/favorites.go @@ -1,7 +1,9 @@ package cointop +import "sort" + func (ct *Cointop) toggleFavorite() error { - ct.State.portfolioVisible = false + ct.debuglog("toggleFavorite()") coin := ct.HighlightedRowCoin() if coin == nil { return nil @@ -16,16 +18,17 @@ func (ct *Cointop) toggleFavorite() error { coin.Favorite = true } - go ct.updateTable() - if err := ct.save(); err != nil { return err } + go ct.updateTable() + return nil } func (ct *Cointop) toggleShowFavorites() error { + ct.debuglog("toggleShowFavorites()") ct.State.portfolioVisible = false ct.State.filterByFavorites = !ct.State.filterByFavorites go ct.updateTable() @@ -33,6 +36,7 @@ func (ct *Cointop) toggleShowFavorites() error { } func (ct *Cointop) getFavoritesSlice() []*Coin { + ct.debuglog("getFavoritesSlice()") sliced := []*Coin{} for i := range ct.State.allCoins { coin := ct.State.allCoins[i] @@ -41,5 +45,13 @@ func (ct *Cointop) getFavoritesSlice() []*Coin { } } + sort.Slice(sliced, func(i, j int) bool { + return sliced[i].MarketCap > sliced[j].MarketCap + }) + + for i, coin := range sliced { + coin.Rank = i + 1 + } + return sliced } diff --git a/cointop/help.go b/cointop/help.go index d60083e..0be8b41 100644 --- a/cointop/help.go +++ b/cointop/help.go @@ -18,6 +18,7 @@ func NewHelpView() *HelpView { } func (ct *Cointop) updateHelp() { + ct.debuglog("updateHelp()") keys := make([]string, 0, len(ct.State.shortcutKeys)) for k := range ct.State.shortcutKeys { keys = append(keys, k) @@ -69,6 +70,7 @@ func (ct *Cointop) updateHelp() { } func (ct *Cointop) showHelp() error { + ct.debuglog("showHelp()") ct.State.helpVisible = true ct.updateHelp() ct.SetActiveView(ct.Views.Help.Name()) @@ -76,6 +78,7 @@ func (ct *Cointop) showHelp() error { } func (ct *Cointop) hideHelp() error { + ct.debuglog("hideHelp()") ct.State.helpVisible = false ct.SetViewOnBottom(ct.Views.Help.Name()) ct.SetActiveView(ct.Views.Table.Name()) @@ -92,6 +95,7 @@ func (ct *Cointop) hideHelp() error { } func (ct *Cointop) toggleHelp() error { + ct.debuglog("toggleHelp()") ct.State.helpVisible = !ct.State.helpVisible if ct.State.helpVisible { return ct.showHelp() diff --git a/cointop/keybindings.go b/cointop/keybindings.go index 14b6c2d..094ab77 100644 --- a/cointop/keybindings.go +++ b/cointop/keybindings.go @@ -367,7 +367,7 @@ func (ct *Cointop) keybindings(g *gocui.Gui) error { // TODO: use scrolling table keys := ct.sortedSupportedCurrencyConversions() for i, k := range keys { - ct.setKeybindingMod(rune(alphanumericcharacters[i]), gocui.ModNone, ct.keyfn(ct.setCurrencyConverstion(k)), ct.Views.ConvertMenu.Name()) + ct.setKeybindingMod(rune(alphanumericcharacters[i]), gocui.ModNone, ct.keyfn(ct.setCurrencyConverstionFn(k)), ct.Views.ConvertMenu.Name()) } return nil diff --git a/cointop/layout.go b/cointop/layout.go index c3341f9..984a8c0 100644 --- a/cointop/layout.go +++ b/cointop/layout.go @@ -13,6 +13,7 @@ var lastWidth int // layout sets initial layout func (ct *Cointop) layout(g *gocui.Gui) error { + ct.debuglog("layout()") maxY := ct.height() maxX := ct.ClampedWidth() diff --git a/cointop/list.go b/cointop/list.go index f494a97..40c2755 100644 --- a/cointop/list.go +++ b/cointop/list.go @@ -5,13 +5,13 @@ import ( "time" types "github.com/miguelmota/cointop/cointop/common/api/types" - "github.com/miguelmota/cointop/cointop/common/filecache" ) var coinslock sync.Mutex var updatecoinsmux sync.Mutex func (ct *Cointop) updateCoins() error { + ct.debuglog("updateCoins()") coinslock.Lock() defer coinslock.Unlock() cachekey := ct.cacheKey("allCoinsSlugMap") @@ -46,6 +46,7 @@ func (ct *Cointop) updateCoins() error { } func (ct *Cointop) processCoinsMap(coinsMap map[string]types.Coin) { + ct.debuglog("processCoinsMap()") var coins []types.Coin for _, v := range coinsMap { coins = append(coins, v) @@ -55,18 +56,11 @@ func (ct *Cointop) processCoinsMap(coinsMap map[string]types.Coin) { } func (ct *Cointop) processCoins(coins []types.Coin) { + ct.debuglog("processCoins()") updatecoinsmux.Lock() defer updatecoinsmux.Unlock() - allCoinsSlugMap := make(map[string]*Coin) - ct.State.allCoinsSlugMap.Range(func(key, value interface{}) bool { - allCoinsSlugMap[key.(string)] = value.(*Coin) - return true - }) - - cachekey := ct.cacheKey("allCoinsSlugMap") - ct.cache.Set(cachekey, allCoinsSlugMap, 10*time.Second) - filecache.Set(cachekey, allCoinsSlugMap, 24*time.Hour) + ct.cacheAllCoinsSlugMap() for _, v := range coins { k := v.Name @@ -149,5 +143,12 @@ func (ct *Cointop) processCoins(coins []types.Coin) { } func (ct *Cointop) getListCount() int { - return len(ct.allCoins()) + ct.debuglog("getListCount()") + if ct.State.filterByFavorites { + return len(ct.State.favorites) + } else if ct.State.portfolioVisible { + return len(ct.State.portfolio.Entries) + } else { + return len(ct.State.allCoins) + } } diff --git a/cointop/marketbar.go b/cointop/marketbar.go index 8896b46..29a53ef 100644 --- a/cointop/marketbar.go +++ b/cointop/marketbar.go @@ -23,6 +23,7 @@ func NewMarketbarView() *MarketbarView { } func (ct *Cointop) updateMarketbar() error { + ct.debuglog("updateMarketbar()") if ct.Views.Marketbar.Backing() == nil { return nil } diff --git a/cointop/navigation.go b/cointop/navigation.go index 6e2546b..54beaf6 100644 --- a/cointop/navigation.go +++ b/cointop/navigation.go @@ -1,18 +1,22 @@ package cointop func (ct *Cointop) currentPage() int { + ct.debuglog("currentPage()") return ct.State.page + 1 } func (ct *Cointop) currentDisplayPage() int { + ct.debuglog("currentDisplayPage()") return ct.State.page + 1 } func (ct *Cointop) totalPages() int { + ct.debuglog("totalPages()") return ct.getListCount() / ct.State.perPage } func (ct *Cointop) totalPagesDisplay() int { + ct.debuglog("totalPagesDisplay()") return ct.totalPages() + 1 } @@ -21,6 +25,7 @@ func (ct *Cointop) totalPerPage() int { } func (ct *Cointop) setPage(page int) int { + ct.debuglog("setPage()") if (page*ct.State.perPage) < ct.getListCount() && page >= 0 { ct.State.page = page } @@ -28,15 +33,18 @@ func (ct *Cointop) setPage(page int) int { } func (ct *Cointop) cursorDown() error { + ct.debuglog("cursorDown()") if ct.Views.Table.Backing() == nil { return nil } - _, y := ct.Views.Table.Backing().Origin() - cx, cy := ct.Views.Table.Backing().Cursor() - numRows := len(ct.State.coins) - 1 - if (cy + y + 1) > numRows { + + // NOTE: return if already at the bottom + if ct.isLastRow() { return nil } + + cx, cy := ct.Views.Table.Backing().Cursor() + if err := ct.Views.Table.Backing().SetCursor(cx, cy+1); err != nil { ox, oy := ct.Views.Table.Backing().Origin() // set origin scrolls @@ -49,11 +57,19 @@ func (ct *Cointop) cursorDown() error { } func (ct *Cointop) cursorUp() error { + ct.debuglog("cursorUp()") if ct.Views.Table.Backing() == nil { return nil } + + // NOTE: return if already at the top + if ct.isFirstRow() { + return nil + } + ox, oy := ct.Views.Table.Backing().Origin() cx, cy := ct.Views.Table.Backing().Cursor() + if err := ct.Views.Table.Backing().SetCursor(cx, cy-1); err != nil && oy > 0 { // set origin scrolls if err := ct.Views.Table.Backing().SetOrigin(ox, oy-1); err != nil { @@ -65,9 +81,16 @@ func (ct *Cointop) cursorUp() error { } func (ct *Cointop) pageDown() error { + ct.debuglog("pageDown()") if ct.Views.Table.Backing() == nil { return nil } + + // NOTE: return if already at the bottom + if ct.isLastRow() { + return nil + } + ox, oy := ct.Views.Table.Backing().Origin() // this is prev origin position cx, _ := ct.Views.Table.Backing().Cursor() // relative cursor position _, sy := ct.Views.Table.Backing().Size() // rows in visible view @@ -97,9 +120,16 @@ func (ct *Cointop) pageDown() error { } func (ct *Cointop) pageUp() error { + ct.debuglog("pageUp()") if ct.Views.Table.Backing() == nil { return nil } + + // NOTE: return if already at the top + if ct.isFirstRow() { + return nil + } + ox, oy := ct.Views.Table.Backing().Origin() cx, _ := ct.Views.Table.Backing().Cursor() // relative cursor position _, sy := ct.Views.Table.Backing().Size() // rows in visible view @@ -121,9 +151,16 @@ func (ct *Cointop) pageUp() error { } func (ct *Cointop) navigateFirstLine() error { + ct.debuglog("navigateFirstLine()") if ct.Views.Table.Backing() == nil { return nil } + + // NOTE: return if already at the top + if ct.isFirstRow() { + return nil + } + ox, _ := ct.Views.Table.Backing().Origin() cx, _ := ct.Views.Table.Backing().Cursor() if err := ct.Views.Table.Backing().SetOrigin(ox, 0); err != nil { @@ -137,9 +174,16 @@ func (ct *Cointop) navigateFirstLine() error { } func (ct *Cointop) navigateLastLine() error { + ct.debuglog("navigateLastLine()") if ct.Views.Table.Backing() == nil { return nil } + + // NOTE: return if already at the bottom + if ct.isLastRow() { + return nil + } + ox, _ := ct.Views.Table.Backing().Origin() cx, _ := ct.Views.Table.Backing().Cursor() _, sy := ct.Views.Table.Backing().Size() @@ -156,9 +200,16 @@ func (ct *Cointop) navigateLastLine() error { } func (ct *Cointop) navigatePageFirstLine() error { + ct.debuglog("navigatePageFirstLine()") if ct.Views.Table.Backing() == nil { return nil } + + // NOTE: return if already at the correct line + if ct.isPageFirstLine() { + return nil + } + cx, _ := ct.Views.Table.Backing().Cursor() if err := ct.Views.Table.Backing().SetCursor(cx, 0); err != nil { return err @@ -168,9 +219,16 @@ func (ct *Cointop) navigatePageFirstLine() error { } func (ct *Cointop) navigatePageMiddleLine() error { + ct.debuglog("navigatePageMiddleLine()") if ct.Views.Table.Backing() == nil { return nil } + + // NOTE: return if already at the correct line + if ct.isPageMiddleLine() { + return nil + } + cx, _ := ct.Views.Table.Backing().Cursor() _, sy := ct.Views.Table.Backing().Size() if err := ct.Views.Table.Backing().SetCursor(cx, (sy/2)-1); err != nil { @@ -181,9 +239,16 @@ func (ct *Cointop) navigatePageMiddleLine() error { } func (ct *Cointop) navigatePageLastLine() error { + ct.debuglog("navigatePageLastLine()") if ct.Views.Table.Backing() == nil { return nil } + + // NOTE: return if already at the correct line + if ct.isPageLastLine() { + return nil + } + cx, _ := ct.Views.Table.Backing().Cursor() _, sy := ct.Views.Table.Backing().Size() if err := ct.Views.Table.Backing().SetCursor(cx, sy-1); err != nil { @@ -194,9 +259,13 @@ func (ct *Cointop) navigatePageLastLine() error { } func (ct *Cointop) prevPage() error { + ct.debuglog("prevPage()") + + // NOTE: return if already at the first page if ct.isFirstPage() { return nil } + ct.setPage(ct.State.page - 1) ct.updateTable() ct.rowChanged() @@ -204,6 +273,13 @@ func (ct *Cointop) prevPage() error { } func (ct *Cointop) nextPage() error { + ct.debuglog("nextPage()") + + // NOTE: return if already at the last page + if ct.isLastPage() { + return nil + } + ct.setPage(ct.State.page + 1) ct.updateTable() ct.rowChanged() @@ -211,17 +287,79 @@ func (ct *Cointop) nextPage() error { } func (ct *Cointop) firstPage() error { + ct.debuglog("firstPage()") + + // NOTE: return if already at the first page + if ct.isFirstPage() { + return nil + } + ct.State.page = 0 ct.updateTable() ct.rowChanged() return nil } +func (ct *Cointop) isFirstRow() bool { + ct.debuglog("isFirstRow()") + + _, y := ct.Views.Table.Backing().Origin() + _, cy := ct.Views.Table.Backing().Cursor() + + return (cy + y) == 0 +} + +func (ct *Cointop) isLastRow() bool { + ct.debuglog("isLastRow()") + + _, y := ct.Views.Table.Backing().Origin() + _, cy := ct.Views.Table.Backing().Cursor() + numRows := len(ct.State.coins) - 1 + + return (cy + y + 1) > numRows +} + func (ct *Cointop) isFirstPage() bool { + ct.debuglog("isFirstPage()") return ct.State.page == 0 } +func (ct *Cointop) isLastPage() bool { + ct.debuglog("isLastPage()") + return ct.State.page == ct.totalPages() +} + +func (ct *Cointop) isPageFirstLine() bool { + ct.debuglog("isPageFirstLine()") + + _, cy := ct.Views.Table.Backing().Cursor() + return cy == 0 +} + +func (ct *Cointop) isPageMiddleLine() bool { + ct.debuglog("isPageMiddleLine()") + + _, cy := ct.Views.Table.Backing().Cursor() + _, sy := ct.Views.Table.Backing().Size() + return (sy/2)-1 == cy +} + +func (ct *Cointop) isPageLastLine() bool { + ct.debuglog("isPageLastLine()") + + _, cy := ct.Views.Table.Backing().Cursor() + _, sy := ct.Views.Table.Backing().Size() + return cy+1 == sy +} + func (ct *Cointop) lastPage() error { + ct.debuglog("lastPage()") + + // NOTE: return if already at the last page + if ct.isLastPage() { + return nil + } + ct.State.page = ct.getListCount() / ct.State.perPage ct.updateTable() ct.rowChanged() @@ -229,6 +367,7 @@ func (ct *Cointop) lastPage() error { } func (ct *Cointop) goToPageRowIndex(idx int) error { + ct.debuglog("goToPageRowIndex()") if ct.Views.Table.Backing() == nil { return nil } @@ -241,6 +380,7 @@ func (ct *Cointop) goToPageRowIndex(idx int) error { } func (ct *Cointop) goToGlobalIndex(idx int) error { + ct.debuglog("goToGlobalIndex()") perpage := ct.totalPerPage() atpage := idx / perpage ct.setPage(atpage) @@ -251,6 +391,7 @@ func (ct *Cointop) goToGlobalIndex(idx int) error { } func (ct *Cointop) highlightRow(idx int) error { + ct.debuglog("highlightRow()") ct.Views.Table.Backing().SetOrigin(0, 0) ct.Views.Table.Backing().SetCursor(0, 0) ox, _ := ct.Views.Table.Backing().Origin() diff --git a/cointop/portfolio.go b/cointop/portfolio.go index ccd6513..d81e721 100644 --- a/cointop/portfolio.go +++ b/cointop/portfolio.go @@ -22,6 +22,7 @@ func NewPortfolioUpdateMenuView() *PortfolioUpdateMenuView { } func (ct *Cointop) togglePortfolio() error { + ct.debuglog("togglePortfolio()") if ct.State.portfolioVisible { ct.goToPageRowIndex(ct.State.lastSelectedRowIndex) } else { @@ -37,6 +38,7 @@ func (ct *Cointop) togglePortfolio() error { } func (ct *Cointop) toggleShowPortfolio() error { + ct.debuglog("toggleShowPortfolio()") ct.State.filterByFavorites = false ct.State.portfolioVisible = true go ct.UpdateChart() @@ -45,6 +47,7 @@ func (ct *Cointop) toggleShowPortfolio() error { } func (ct *Cointop) togglePortfolioUpdateMenu() error { + ct.debuglog("togglePortfolioUpdateMenu()") ct.State.portfolioUpdateMenuVisible = !ct.State.portfolioUpdateMenuVisible if ct.State.portfolioUpdateMenuVisible { return ct.showPortfolioUpdateMenu() @@ -53,10 +56,17 @@ func (ct *Cointop) togglePortfolioUpdateMenu() error { return ct.hidePortfolioUpdateMenu() } +func (ct *Cointop) coinHoldings(coin *Coin) float64 { + entry, _ := ct.PortfolioEntry(coin) + return entry.Holdings +} + func (ct *Cointop) updatePortfolioUpdateMenu() { + ct.debuglog("updatePortfolioUpdateMenu()") coin := ct.HighlightedRowCoin() exists := ct.PortfolioEntryExists(coin) - value := strconv.FormatFloat(coin.Holdings, 'f', -1, 64) + value := strconv.FormatFloat(ct.coinHoldings(coin), 'f', -1, 64) + ct.debuglog(fmt.Sprintf("holdings %v", value)) var mode string var current string var submitText string @@ -82,6 +92,7 @@ func (ct *Cointop) updatePortfolioUpdateMenu() { } func (ct *Cointop) showPortfolioUpdateMenu() error { + ct.debuglog("showPortfolioUpdateMenu()") coin := ct.HighlightedRowCoin() if coin == nil { ct.togglePortfolio() @@ -96,6 +107,7 @@ func (ct *Cointop) showPortfolioUpdateMenu() error { } func (ct *Cointop) hidePortfolioUpdateMenu() error { + ct.debuglog("hidePortfolioUpdateMenu()") ct.State.portfolioUpdateMenuVisible = false ct.SetViewOnBottom(ct.Views.PortfolioUpdateMenu.Name()) ct.SetViewOnBottom(ct.Views.Input.Name()) @@ -117,6 +129,7 @@ func (ct *Cointop) hidePortfolioUpdateMenu() error { // sets portfolio entry holdings from inputed value func (ct *Cointop) setPortfolioHoldings() error { + ct.debuglog("setPortfolioHoldings()") defer ct.hidePortfolioUpdateMenu() coin := ct.HighlightedRowCoin() @@ -143,7 +156,6 @@ func (ct *Cointop) setPortfolioHoldings() error { if shouldDelete { ct.removePortfolioEntry(coin.Name) ct.updateTable() - ct.goToGlobalIndex(0) } else { ct.updateTable() ct.goToPageRowIndex(ct.State.lastSelectedRowIndex) @@ -154,6 +166,7 @@ func (ct *Cointop) setPortfolioHoldings() error { // PortfolioEntry returns a portfolio entry func (ct *Cointop) PortfolioEntry(c *Coin) (*PortfolioEntry, bool) { + //ct.debuglog("portfolioEntry()") if c == nil { return &PortfolioEntry{}, true } @@ -178,7 +191,7 @@ func (ct *Cointop) PortfolioEntry(c *Coin) (*PortfolioEntry, bool) { } func (ct *Cointop) setPortfolioEntry(coin string, holdings float64) { - + ct.debuglog("setPortfolioEntry()") ic, _ := ct.State.allCoinsSlugMap.Load(strings.ToLower(coin)) c, _ := ic.(*Coin) p, isNew := ct.PortfolioEntry(c) @@ -198,25 +211,30 @@ func (ct *Cointop) setPortfolioEntry(coin string, holdings float64) { } func (ct *Cointop) removePortfolioEntry(coin string) { + ct.debuglog("removePortfolioEntry()") delete(ct.State.portfolio.Entries, strings.ToLower(coin)) } // PortfolioEntryExists returns true if portfolio entry exists func (ct *Cointop) PortfolioEntryExists(c *Coin) bool { + ct.debuglog("portfolioEntryExists()") _, isNew := ct.PortfolioEntry(c) return !isNew } func (ct *Cointop) portfolioEntriesCount() int { + ct.debuglog("portfolioEntriesCount()") return len(ct.State.portfolio.Entries) } func (ct *Cointop) getPortfolioSlice() []*Coin { + ct.debuglog("getPortfolioSlice()") sliced := []*Coin{} + if ct.portfolioEntriesCount() == 0 { + return sliced + } + for i := range ct.State.allCoins { - if ct.portfolioEntriesCount() == 0 { - break - } coin := ct.State.allCoins[i] p, isNew := ct.PortfolioEntry(coin) if isNew { @@ -245,6 +263,7 @@ func (ct *Cointop) getPortfolioSlice() []*Coin { } func (ct *Cointop) getPortfolioTotal() float64 { + ct.debuglog("getPortfolioTotal()") portfolio := ct.getPortfolioSlice() var total float64 for _, p := range portfolio { diff --git a/cointop/quit.go b/cointop/quit.go index b7574ed..0ca09b2 100644 --- a/cointop/quit.go +++ b/cointop/quit.go @@ -13,6 +13,7 @@ func (ct *Cointop) Quit() error { // QuitView exists the current view func (ct *Cointop) QuitView() error { + ct.debuglog("quitView()") if ct.State.portfolioVisible { ct.State.portfolioVisible = false return ct.updateTable() @@ -30,6 +31,7 @@ func (ct *Cointop) QuitView() error { // Exit safely exits the program func (ct *Cointop) Exit() { + ct.debuglog("exit()") if ct.g != nil { ct.g.Close() } else { diff --git a/cointop/refresh.go b/cointop/refresh.go index f238bae..90db178 100644 --- a/cointop/refresh.go +++ b/cointop/refresh.go @@ -6,6 +6,7 @@ import ( ) func (ct *Cointop) refresh() error { + ct.debuglog("refresh()") go func() { <-ct.limiter ct.forceRefresh <- true @@ -14,6 +15,7 @@ func (ct *Cointop) refresh() error { } func (ct *Cointop) refreshAll() error { + ct.debuglog("refreshAll()") ct.refreshMux.Lock() defer ct.refreshMux.Unlock() ct.setRefreshStatus() @@ -28,6 +30,7 @@ func (ct *Cointop) refreshAll() error { } func (ct *Cointop) setRefreshStatus() { + ct.debuglog("setRefreshStatus()") go func() { ct.loadingTicks("refreshing", 900) ct.rowChanged() @@ -35,6 +38,7 @@ func (ct *Cointop) setRefreshStatus() { } func (ct *Cointop) loadingTicks(s string, t int) { + ct.debuglog("loadingTicks()") interval := 150 k := 0 for i := 0; i < (t / interval); i++ { @@ -48,6 +52,7 @@ func (ct *Cointop) loadingTicks(s string, t int) { } func (ct *Cointop) intervalFetchData() { + ct.debuglog("intervalFetchData()") go func() { for { select { diff --git a/cointop/save.go b/cointop/save.go index ac8f6a6..a1896e9 100644 --- a/cointop/save.go +++ b/cointop/save.go @@ -3,14 +3,19 @@ package cointop import "log" func (ct *Cointop) save() error { + ct.debuglog("save()") ct.setSavingStatus() if err := ct.saveConfig(); err != nil { log.Fatal(err) } + + ct.cacheAllCoinsSlugMap() + return nil } func (ct *Cointop) setSavingStatus() { + ct.debuglog("setSavingStatus()") if ct.g == nil { return } diff --git a/cointop/search.go b/cointop/search.go index 722234d..26209e7 100644 --- a/cointop/search.go +++ b/cointop/search.go @@ -28,18 +28,21 @@ func NewInputView() *InputView { } func (ct *Cointop) openSearch() error { + ct.debuglog("openSearch()") ct.State.searchFieldVisible = true ct.SetActiveView(ct.Views.SearchField.Name()) return nil } func (ct *Cointop) cancelSearch() error { + ct.debuglog("cancelSearch()") ct.State.searchFieldVisible = false ct.SetActiveView(ct.Views.Table.Name()) return nil } func (ct *Cointop) doSearch() error { + ct.debuglog("doSearch()") ct.Views.SearchField.Backing().Rewind() b := make([]byte, 100) n, err := ct.Views.SearchField.Backing().Read(b) @@ -66,6 +69,7 @@ func (ct *Cointop) doSearch() error { } func (ct *Cointop) search(q string) error { + ct.debuglog("search()") q = strings.TrimSpace(strings.ToLower(q)) idx := -1 min := -1 diff --git a/cointop/selection.go b/cointop/selection.go index f70d448..a6950f3 100644 --- a/cointop/selection.go +++ b/cointop/selection.go @@ -1,6 +1,7 @@ package cointop func (ct *Cointop) selectedCoinName() string { + ct.debuglog("selectedCoinName()") coin := ct.State.selectedCoin if coin != nil { return coin.Name @@ -10,6 +11,7 @@ func (ct *Cointop) selectedCoinName() string { } func (ct *Cointop) selectedCoinSymbol() string { + ct.debuglog("selectedCoinSymbol()") coin := ct.State.selectedCoin if coin != nil { return coin.Symbol diff --git a/cointop/size.go b/cointop/size.go index 0ecc6b5..e6036dd 100644 --- a/cointop/size.go +++ b/cointop/size.go @@ -2,23 +2,27 @@ package cointop // Size returns window width and height func (ct *Cointop) size() (int, int) { + ct.debuglog("size()") return ct.g.Size() } // Width returns window width func (ct *Cointop) width() int { + ct.debuglog("width()") w, _ := ct.size() return w } // Height returns window height func (ct *Cointop) height() int { + ct.debuglog("height()") _, h := ct.size() return h } // viewWidth returns view width func (ct *Cointop) viewWidth(view string) int { + ct.debuglog("viewWidth()") v, err := ct.g.View(view) if err != nil { return 0 @@ -29,6 +33,7 @@ func (ct *Cointop) viewWidth(view string) int { // ClampedWidth returns the clamped width func (ct *Cointop) ClampedWidth() int { + ct.debuglog("clampedWidth()") w := ct.width() if w > ct.maxTableWidth { return ct.maxTableWidth diff --git a/cointop/sort.go b/cointop/sort.go index 57bb3a3..912a00c 100644 --- a/cointop/sort.go +++ b/cointop/sort.go @@ -10,6 +10,7 @@ import ( var sortlock sync.Mutex func (ct *Cointop) sort(sortBy string, desc bool, list []*Coin, renderHeaders bool) { + ct.debuglog("sort()") sortlock.Lock() defer sortlock.Unlock() if list == nil { @@ -72,18 +73,21 @@ func (ct *Cointop) sort(sortBy string, desc bool, list []*Coin, renderHeaders bo } func (ct *Cointop) sortAsc() error { + ct.debuglog("sortAsc()") ct.State.sortDesc = false ct.updateTable() return nil } func (ct *Cointop) sortDesc() error { + ct.debuglog("sortDesc()") ct.State.sortDesc = true ct.updateTable() return nil } func (ct *Cointop) sortPrevCol() error { + ct.debuglog("sortPrevCol()") nextsortBy := ct.tableColumnOrder[0] i := ct.getSortColIndex() k := i - 1 @@ -98,6 +102,7 @@ func (ct *Cointop) sortPrevCol() error { } func (ct *Cointop) sortNextCol() error { + ct.debuglog("sortNextCol()") nextsortBy := ct.tableColumnOrder[0] l := len(ct.tableColumnOrder) i := ct.getSortColIndex() @@ -113,6 +118,7 @@ func (ct *Cointop) sortNextCol() error { } func (ct *Cointop) sortToggle(sortBy string, desc bool) error { + ct.debuglog("sortToggle()") if ct.State.sortBy == sortBy { desc = !ct.State.sortDesc } @@ -123,12 +129,14 @@ 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) } } func (ct *Cointop) getSortColIndex() int { + ct.debuglog("getSortColIndex()") for i, col := range ct.tableColumnOrder { if ct.State.sortBy == col { return i diff --git a/cointop/statusbar.go b/cointop/statusbar.go index 85028e9..65f917c 100644 --- a/cointop/statusbar.go +++ b/cointop/statusbar.go @@ -31,6 +31,7 @@ func (statusbar *StatusbarView) Update(str string) error { // updateStatusbar updates the statusbar view func (ct *Cointop) updateStatusbar(s string) error { + ct.debuglog("updateStatusbar()") currpage := ct.currentDisplayPage() totalpages := ct.totalPagesDisplay() var quitText string @@ -66,6 +67,7 @@ func (ct *Cointop) updateStatusbar(s string) error { // RefreshRowLink updates the row link in the statusbar func (ct *Cointop) RefreshRowLink() error { + ct.debuglog("refreshRowLink()") var shortcut string if !open.CommandExists() { shortcut = "[O]Open " diff --git a/cointop/stdin.go b/cointop/stdin.go index e1d61bc..ebc5f2a 100644 --- a/cointop/stdin.go +++ b/cointop/stdin.go @@ -11,6 +11,7 @@ import ( // readAPIKeyFromStdin reads the user inputed API from the stdin prompt func (ct *Cointop) readAPIKeyFromStdin(name string) string { + ct.debuglog("readAPIKeyFromStdin()") reader := bufio.NewReader(os.Stdin) fmt.Printf("Enter %s API Key: ", name) text, err := reader.ReadString('\n') diff --git a/cointop/table.go b/cointop/table.go index 802be48..1000394 100644 --- a/cointop/table.go +++ b/cointop/table.go @@ -47,6 +47,7 @@ const dots = "..." // RefreshTable refreshes the table func (ct *Cointop) RefreshTable() error { + ct.debuglog("refreshTable()") maxX := ct.width() ct.table = table.New().SetWidth(maxX) ct.table.HideColumHeaders = true @@ -76,12 +77,21 @@ func (ct *Cointop) RefreshTable() error { color24h = ct.colorscheme.TableColumnChangeDown } name := coin.Name - star := " " + star := ct.colorscheme.TableRow(" ") + if coin.Favorite { + star = ct.colorscheme.TableRowFavorite("*") + } + rank := fmt.Sprintf("%s%v", star, ct.colorscheme.TableRow(fmt.Sprintf("%6v ", coin.Rank))) if len(name) > 20 { name = fmt.Sprintf("%s%s", name[0:18], dots) } + namecolor := ct.colorscheme.TableRow + if coin.Favorite { + namecolor = ct.colorscheme.TableRowFavorite + } + percentHoldings := (coin.Balance / total) * 1e2 if math.IsNaN(percentHoldings) { percentHoldings = 0 @@ -89,7 +99,7 @@ func (ct *Cointop) RefreshTable() error { ct.table.AddRow( rank, - ct.colorscheme.TableRow(pad.Right(fmt.Sprintf("%.22s", name), 21, " ")), + namecolor(pad.Right(fmt.Sprintf("%.22s", name), 21, " ")), ct.colorscheme.TableRow(pad.Right(fmt.Sprintf("%.6s", coin.Symbol), 5, " ")), ct.colorscheme.TableRow(fmt.Sprintf("%13s", humanize.Commaf(coin.Price))), ct.colorscheme.TableRow(fmt.Sprintf("%15s", strconv.FormatFloat(coin.Holdings, 'f', -1, 64))), @@ -201,54 +211,29 @@ func (ct *Cointop) RefreshTable() error { // updateTable updates the table func (ct *Cointop) updateTable() error { - sliced := []*Coin{} - + ct.debuglog("updateTable()") ct.State.allCoinsSlugMap.Range(func(key, value interface{}) bool { k := key.(string) if v, ok := value.(*Coin); ok { - if ct.State.favorites[v.Name] { - v.Favorite = true - ct.State.allCoinsSlugMap.Store(k, v) - } + v.Favorite = ct.State.favorites[v.Name] + ct.State.allCoinsSlugMap.Store(k, v) } return true }) if ct.State.filterByFavorites { - sliced = ct.getFavoritesSlice() - ct.State.coins = sliced + ct.State.coins = ct.getFavoritesSlice() } else if ct.State.portfolioVisible { - sliced = ct.getPortfolioSlice() - ct.State.coins = sliced + ct.State.coins = ct.getPortfolioSlice() } else { - start := ct.State.page * ct.State.perPage - end := start + ct.State.perPage - allCoins := ct.allCoins() - size := len(allCoins) - if start < 0 { - start = 0 - } - if end >= size-1 { - start = int(math.Floor(float64(start/100)) * 100) - end = size - 1 - } - if start < 0 { - start = 0 - } - if end >= size { - end = size - 1 - } - if end < 0 { - end = 0 + // TODO: maintain state of previous sorting + if ct.State.sortBy == "holdings" { + ct.State.sortBy = "rank" + ct.State.sortDesc = false } - if start >= end { - return nil - } - if end > 0 { - sliced = allCoins[start:end] - } - ct.State.coins = sliced + + ct.State.coins = ct.getTableCoinsSlice() } ct.sort(ct.State.sortBy, ct.State.sortDesc, ct.State.coins, true) @@ -256,8 +241,52 @@ func (ct *Cointop) updateTable() error { return nil } +// getTableCoinsSlice ... +func (ct *Cointop) getTableCoinsSlice() []*Coin { + ct.debuglog("getTableCoinsSlice()") + sliced := []*Coin{} + start := ct.State.page * ct.State.perPage + end := start + ct.State.perPage + allCoins := ct.allCoins() + size := len(allCoins) + if start < 0 { + start = 0 + } + if end >= size-1 { + start = int(math.Floor(float64(start/100)) * 100) + end = size - 1 + } + if start < 0 { + start = 0 + } + if end >= size { + end = size - 1 + } + if end < 0 { + end = 0 + } + if start >= end { + return nil + } + if end > 0 { + sliced = allCoins[start:end] + + // NOTE: restore rank + for _, coin := range sliced { + icoin, _ := ct.State.allCoinsSlugMap.Load(coin.Name) + if icoin != nil { + c, _ := icoin.(*Coin) + coin.Rank = c.Rank + } + } + } + + return sliced +} + // HighlightedRowIndex returns the index of the highlighted row func (ct *Cointop) HighlightedRowIndex() int { + ct.debuglog("highlightedRowIndex()") _, y := ct.Views.Table.Backing().Origin() _, cy := ct.Views.Table.Backing().Cursor() idx := y + cy @@ -272,6 +301,7 @@ func (ct *Cointop) HighlightedRowIndex() int { // HighlightedRowCoin returns the coin at the index of the highlighted row func (ct *Cointop) HighlightedRowCoin() *Coin { + ct.debuglog("highlightedRowCoin()") idx := ct.HighlightedRowIndex() if len(ct.State.coins) == 0 { return nil @@ -281,6 +311,7 @@ func (ct *Cointop) HighlightedRowCoin() *Coin { // HighlightedPageRowIndex returns the index of page row of the highlighted row func (ct *Cointop) HighlightedPageRowIndex() int { + ct.debuglog("highlightedPageRowIndex()") _, cy := ct.Views.Table.Backing().Cursor() idx := cy if idx < 0 { @@ -292,6 +323,7 @@ func (ct *Cointop) HighlightedPageRowIndex() int { // RowLink returns the row url link func (ct *Cointop) RowLink() string { + ct.debuglog("rowLink()") coin := ct.HighlightedRowCoin() if coin == nil { return "" @@ -302,6 +334,7 @@ func (ct *Cointop) RowLink() string { // RowLinkShort returns a shortened version of the row url link func (ct *Cointop) RowLinkShort() string { + ct.debuglog("rowLinkShort()") link := ct.RowLink() if link != "" { u, err := url.Parse(link) @@ -325,6 +358,7 @@ func (ct *Cointop) RowLinkShort() string { // ToggleTableFullscreen toggles the table fullscreen mode func (ct *Cointop) ToggleTableFullscreen() error { + ct.debuglog("toggleTableFullscreen()") ct.State.onlyTable = !ct.State.onlyTable if ct.State.onlyTable { } else { diff --git a/cointop/table_header.go b/cointop/table_header.go index 1f2ac7f..6900087 100644 --- a/cointop/table_header.go +++ b/cointop/table_header.go @@ -17,6 +17,7 @@ func NewTableHeaderView() *TableHeaderView { // updateTableHeader renders the table header func (ct *Cointop) updateTableHeader() { + ct.debuglog("updateTableHeader()") var cols []string type t struct { diff --git a/cointop/update.go b/cointop/update.go index 5a4c439..ec878db 100644 --- a/cointop/update.go +++ b/cointop/update.go @@ -1,18 +1,16 @@ package cointop import ( - "sync" + "fmt" "github.com/jroimartin/gocui" log "github.com/sirupsen/logrus" ) -var updateMutex sync.Mutex - // update takes a callback which updates the view func (ct *Cointop) update(f func()) { - updateMutex.Lock() - defer updateMutex.Unlock() + ct.debuglog(fmt.Sprintf("update()")) + if ct.g == nil { log.Fatal("gocui is not initialized") } diff --git a/cointop/util.go b/cointop/util.go index f4c291d..90d409c 100644 --- a/cointop/util.go +++ b/cointop/util.go @@ -13,6 +13,7 @@ import ( // OpenLink opens the url in a browser func (ct *Cointop) OpenLink() error { + ct.debuglog("openLink()") open.URL(ct.RowLink()) return nil } @@ -58,7 +59,7 @@ func NormalizePath(path string) string { } // Slugify returns a slugified string -func (ct *Cointop) Slugify(s string) string { +func Slugify(s string) string { s = strings.TrimSpace(strings.ToLower(s)) return s } diff --git a/cointop/version.go b/cointop/version.go index d386810..e6cbeaa 100644 --- a/cointop/version.go +++ b/cointop/version.go @@ -6,7 +6,7 @@ import ( ) // TODO: make dynamic based on git tag -const version = "1.3.5" +const version = "1.3.6" // Version returns the cointop version func (ct *Cointop) Version() string {