Navigation bug fixes. Fixes #41

pull/45/head
Miguel Mota 5 years ago
parent cb2e7f52bc
commit 5f073fecf9
No known key found for this signature in database
GPG Key ID: 67EC1161588A00F9

@ -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)
}
}

@ -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

@ -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 {

@ -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)

@ -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

@ -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 "$"
}

@ -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)
}
}

@ -2,5 +2,6 @@ package cointop
// RowChanged is called when the row is updated
func (ct *Cointop) rowChanged() {
ct.debuglog("rowChanged()")
ct.RefreshRowLink()
}

@ -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
}

@ -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()

@ -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

@ -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()

@ -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)
}
}

@ -23,6 +23,7 @@ func NewMarketbarView() *MarketbarView {
}
func (ct *Cointop) updateMarketbar() error {
ct.debuglog("updateMarketbar()")
if ct.Views.Marketbar.Backing() == nil {
return nil
}

@ -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()

@ -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 {

@ -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 {

@ -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 {

@ -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
}

@ -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

@ -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

@ -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

@ -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

@ -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 "

@ -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')

@ -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 {

@ -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 {

@ -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")
}

@ -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
}

@ -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 {

Loading…
Cancel
Save