Dynamic column widths

pull/94/head
Miguel Mota 3 years ago
parent ae65cc58d7
commit 184ebfb497

@ -15,11 +15,11 @@ var DefaultCoinTableHeaders = []string{
"name",
"symbol",
"price",
"marketcap",
"24h_volume",
"1h_change",
"24h_change",
"7d_change",
"24h_volume",
"market_cap",
"total_supply",
"available_supply",
"last_updated",
@ -32,7 +32,6 @@ func (ct *Cointop) ValidCoinsTableHeader(name string) bool {
return true
}
}
return false
}
@ -45,168 +44,201 @@ func (ct *Cointop) GetCoinsTableHeaders() []string {
func (ct *Cointop) GetCoinsTable() *table.Table {
maxX := ct.width()
t := table.NewTable().SetWidth(maxX)
var rows [][]*table.RowCell
headers := ct.GetCoinsTableHeaders()
ct.ClearSyncMap(ct.State.tableColumnWidths)
ct.ClearSyncMap(ct.State.tableColumnAlignLeft)
for _, coin := range ct.State.coins {
if coin == nil {
continue
}
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)))
name := TruncateString(coin.Name, 20)
symbol := TruncateString(coin.Symbol, 6)
symbolpadding := 8
// NOTE: this is to adjust padding by 1 because when all name rows are
// yellow it messes the spacing (need to debug)
if ct.IsFavoritesVisible() {
symbolpadding++
}
namecolor := ct.colorscheme.TableRow
color1h := ct.colorscheme.TableColumnChange
color24h := ct.colorscheme.TableColumnChange
color7d := ct.colorscheme.TableColumnChange
if coin.Favorite {
namecolor = ct.colorscheme.TableRowFavorite
}
if coin.PercentChange1H > 0 {
color1h = ct.colorscheme.TableColumnChangeUp
}
if coin.PercentChange1H < 0 {
color1h = ct.colorscheme.TableColumnChangeDown
}
if coin.PercentChange24H > 0 {
color24h = ct.colorscheme.TableColumnChangeUp
}
if coin.PercentChange24H < 0 {
color24h = ct.colorscheme.TableColumnChangeDown
}
if coin.PercentChange7D > 0 {
color7d = ct.colorscheme.TableColumnChangeUp
}
if coin.PercentChange7D < 0 {
color7d = ct.colorscheme.TableColumnChangeDown
}
unix, _ := strconv.ParseInt(coin.LastUpdated, 10, 64)
lastUpdated := time.Unix(unix, 0).Format("15:04:05 Jan 02")
headers := ct.GetCoinsTableHeaders()
var rowCells []*table.RowCell
for _, header := range headers {
leftMargin := 1
rightMargin := 1
switch header {
case "rank":
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)))
ct.SetTableColumnWidth(header, 8)
ct.SetTableColumnAlignLeft(header, false)
rowCells = append(rowCells, &table.RowCell{
LeftMargin: 0,
Width: 6,
LeftAlign: false,
Color: ct.colorscheme.Default,
Text: rank,
LeftMargin: leftMargin,
RightMargin: rightMargin,
LeftAlign: false,
Color: ct.colorscheme.Default,
Text: rank,
})
case "name":
name := TruncateString(coin.Name, 16)
namecolor := ct.colorscheme.TableRow
if coin.Favorite {
namecolor = ct.colorscheme.TableRowFavorite
}
ct.SetTableColumnWidthFromString(header, name)
ct.SetTableColumnAlignLeft(header, true)
rowCells = append(rowCells, &table.RowCell{
LeftMargin: 1,
Width: 22,
LeftAlign: true,
Color: namecolor,
Text: name,
LeftMargin: leftMargin,
RightMargin: rightMargin,
LeftAlign: true,
Color: namecolor,
Text: name,
})
case "symbol":
symbol := TruncateString(coin.Symbol, 6)
ct.SetTableColumnWidthFromString(header, symbol)
ct.SetTableColumnAlignLeft(header, true)
rowCells = append(rowCells,
&table.RowCell{
LeftMargin: 1,
Width: symbolpadding,
LeftAlign: true,
Color: ct.colorscheme.TableRow,
Text: symbol,
LeftMargin: leftMargin,
RightMargin: rightMargin,
LeftAlign: true,
Color: ct.colorscheme.TableRow,
Text: symbol,
})
case "price":
text := humanize.Commaf(coin.Price)
ct.SetTableColumnWidthFromString(header, text)
ct.SetTableColumnAlignLeft(header, false)
rowCells = append(rowCells,
&table.RowCell{
LeftMargin: 1,
Width: 12,
LeftAlign: false,
Color: ct.colorscheme.TableColumnPrice,
Text: humanize.Commaf(coin.Price),
})
case "marketcap":
rowCells = append(rowCells,
&table.RowCell{
LeftMargin: 1,
Width: 18,
LeftAlign: false,
Color: ct.colorscheme.TableRow,
Text: humanize.Commaf(coin.MarketCap),
LeftMargin: leftMargin,
RightMargin: rightMargin,
LeftAlign: false,
Color: ct.colorscheme.TableColumnPrice,
Text: text,
})
case "24h_volume":
text := humanize.Commaf(coin.Volume24H)
ct.SetTableColumnWidthFromString(header, text)
ct.SetTableColumnAlignLeft(header, false)
rowCells = append(rowCells,
&table.RowCell{
LeftMargin: 1,
Width: 16,
LeftAlign: false,
Color: ct.colorscheme.TableRow,
Text: humanize.Commaf(coin.Volume24H),
LeftMargin: leftMargin,
RightMargin: rightMargin,
LeftAlign: false,
Color: ct.colorscheme.TableRow,
Text: text,
})
case "1h_change":
color1h := ct.colorscheme.TableColumnChange
if coin.PercentChange1H > 0 {
color1h = ct.colorscheme.TableColumnChangeUp
}
if coin.PercentChange1H < 0 {
color1h = ct.colorscheme.TableColumnChangeDown
}
text := fmt.Sprintf("%.2f%%", coin.PercentChange1H)
ct.SetTableColumnWidthFromString(header, text)
ct.SetTableColumnAlignLeft(header, false)
rowCells = append(rowCells,
&table.RowCell{
LeftMargin: 1,
Width: 11,
LeftAlign: false,
Color: color1h,
Text: fmt.Sprintf("%.2f%%", coin.PercentChange1H),
LeftMargin: leftMargin,
RightMargin: rightMargin,
LeftAlign: false,
Color: color1h,
Text: text,
})
case "24h_change":
color24h := ct.colorscheme.TableColumnChange
if coin.PercentChange24H > 0 {
color24h = ct.colorscheme.TableColumnChangeUp
}
if coin.PercentChange24H < 0 {
color24h = ct.colorscheme.TableColumnChangeDown
}
text := fmt.Sprintf("%.2f%%", coin.PercentChange24H)
ct.SetTableColumnWidthFromString(header, text)
ct.SetTableColumnAlignLeft(header, false)
rowCells = append(rowCells,
&table.RowCell{
LeftMargin: 1,
Width: 10,
LeftAlign: false,
Color: color24h,
Text: fmt.Sprintf("%.2f%%", coin.PercentChange24H),
LeftMargin: leftMargin,
RightMargin: rightMargin,
LeftAlign: false,
Color: color24h,
Text: text,
})
case "7d_change":
color7d := ct.colorscheme.TableColumnChange
if coin.PercentChange7D > 0 {
color7d = ct.colorscheme.TableColumnChangeUp
}
if coin.PercentChange7D < 0 {
color7d = ct.colorscheme.TableColumnChangeDown
}
text := fmt.Sprintf("%.2f%%", coin.PercentChange7D)
ct.SetTableColumnWidthFromString(header, text)
ct.SetTableColumnAlignLeft(header, false)
rowCells = append(rowCells,
&table.RowCell{
LeftMargin: 1,
Width: 10,
LeftAlign: false,
Color: color7d,
Text: fmt.Sprintf("%.2f%%", coin.PercentChange7D),
LeftMargin: leftMargin,
RightMargin: rightMargin,
LeftAlign: false,
Color: color7d,
Text: text,
})
case "market_cap":
text := humanize.Commaf(coin.MarketCap)
ct.SetTableColumnWidthFromString(header, text)
ct.SetTableColumnAlignLeft(header, false)
rowCells = append(rowCells,
&table.RowCell{
LeftMargin: leftMargin,
RightMargin: rightMargin,
LeftAlign: false,
Color: ct.colorscheme.TableRow,
Text: text,
})
case "total_supply":
text := humanize.Commaf(coin.TotalSupply)
ct.SetTableColumnWidthFromString(header, text)
ct.SetTableColumnAlignLeft(header, false)
rowCells = append(rowCells,
&table.RowCell{
LeftMargin: 1,
Width: 22,
LeftAlign: false,
Color: ct.colorscheme.TableRow,
Text: humanize.Commaf(coin.TotalSupply),
LeftMargin: leftMargin,
RightMargin: rightMargin,
LeftAlign: false,
Color: ct.colorscheme.TableRow,
Text: text,
})
case "available_supply":
text := humanize.Commaf(coin.AvailableSupply)
ct.SetTableColumnWidthFromString(header, text)
ct.SetTableColumnAlignLeft(header, false)
rowCells = append(rowCells,
&table.RowCell{
LeftMargin: 1,
Width: 20,
LeftAlign: false,
Color: ct.colorscheme.TableRow,
Text: humanize.Commaf(coin.AvailableSupply),
LeftMargin: leftMargin,
RightMargin: rightMargin,
LeftAlign: false,
Color: ct.colorscheme.TableRow,
Text: text,
})
case "last_updated":
unix, _ := strconv.ParseInt(coin.LastUpdated, 10, 64)
lastUpdated := time.Unix(unix, 0).Format("15:04:05 Jan 02")
ct.SetTableColumnWidthFromString(header, lastUpdated)
ct.SetTableColumnAlignLeft(header, false)
rowCells = append(rowCells,
&table.RowCell{
LeftMargin: 1,
Width: 18,
LeftAlign: false,
Color: ct.colorscheme.TableRow,
Text: lastUpdated,
LeftMargin: leftMargin,
RightMargin: rightMargin,
LeftAlign: false,
Color: ct.colorscheme.TableRow,
Text: lastUpdated,
})
}
}
rows = append(rows, rowCells)
}
t.AddRowCells(
rowCells...,
// TODO: add %percent of cap
)
for _, row := range rows {
for i, header := range headers {
row[i].Width = ct.GetTableColumnWidth(header)
}
t.AddRowCells(row...)
}
return t

@ -75,6 +75,8 @@ type State struct {
sortBy string
tableOffsetX int
onlyTable bool
tableColumnWidths sync.Map
tableColumnAlignLeft sync.Map
chartHeight int
priceAlerts *PriceAlerts
priceAlertEditID string
@ -236,6 +238,8 @@ func NewCointop(config *Config) (*Cointop, error) {
portfolioTableColumns: DefaultPortfolioTableHeaders,
chartHeight: 10,
tableOffsetX: 0,
tableColumnWidths: sync.Map{},
tableColumnAlignLeft: sync.Map{},
priceAlerts: &PriceAlerts{
Entries: make([]*PriceAlert, 0),
SoundEnabled: true,

@ -239,6 +239,8 @@ func (c *Colorscheme) Default(a ...interface{}) string {
}
func (c *Colorscheme) toSprintf(name string) ISprintf {
c.cacheMutex.Lock()
defer c.cacheMutex.Unlock()
if cached, ok := c.cache[name]; ok {
return cached
}
@ -265,9 +267,7 @@ func (c *Colorscheme) toSprintf(name string) ISprintf {
}
}
c.cacheMutex.Lock()
c.cache[name] = fcolor.New(attrs...).SprintFunc()
c.cacheMutex.Unlock()
return c.cache[name]
}

@ -7,7 +7,6 @@ import (
"io/ioutil"
"os"
"path/filepath"
"reflect"
"sort"
"strconv"
"strings"
@ -243,12 +242,8 @@ func (ct *Cointop) configToToml() ([]byte, error) {
})
portfolioIfc["holdings"] = holdingsIfc
if reflect.DeepEqual(DefaultPortfolioTableHeaders, ct.State.portfolioTableColumns) {
portfolioIfc["columns"] = []string{}
} else {
var columnsIfc interface{} = ct.State.portfolioTableColumns
portfolioIfc["columns"] = columnsIfc
}
var columnsIfc interface{} = ct.State.portfolioTableColumns
portfolioIfc["columns"] = columnsIfc
var currencyIfc interface{} = ct.State.currencyConversion
var defaultViewIfc interface{} = ct.State.defaultView
@ -281,12 +276,7 @@ func (ct *Cointop) configToToml() ([]byte, error) {
var coinsTableColumnsIfc interface{} = ct.State.coinsTableColumns
tableMapIfc := map[string]interface{}{}
if reflect.DeepEqual(DefaultCoinTableHeaders, ct.State.coinsTableColumns) {
tableMapIfc["columns"] = []string{}
} else {
tableMapIfc["columns"] = coinsTableColumnsIfc
}
tableMapIfc["columns"] = coinsTableColumnsIfc
var inputs = &config{
API: apiChoiceIfc,
@ -374,6 +364,8 @@ func (ct *Cointop) loadDefaultViewFromConfig() error {
ct.SetSelectedView(PortfolioView)
case "favorites":
ct.SetSelectedView(FavoritesView)
case "alerts", "price_alerts":
ct.SetSelectedView(PriceAlertsView)
case "default":
fallthrough
default:

@ -44,7 +44,7 @@ func (ct *Cointop) UpdateHelp() {
body = fmt.Sprintf("%s%s\n", body, row)
}
versionLine := fmt.Sprintf("cointop %s - (C) 2017-2020 Miguel Mota", ct.Version())
versionLine := fmt.Sprintf("cointop %s - (C) 2017-2021 Miguel Mota", ct.Version())
licenseLine := "Released under the Apache 2.0 License."
instructionsLine := "List of keyboard shortcuts"
infoLine := "See git.io/cointop for more info.\n Press ESC to return."

@ -93,21 +93,9 @@ func (ct *Cointop) ParseKeys(s string) (interface{}, gocui.Modifier) {
key = gocui.KeyCtrlZ
case "~":
key = gocui.KeyCtrlTilde
case "[":
fallthrough
case "lsqrbracket":
fallthrough
case "leftsqrbracket":
fallthrough
case "leftsquarebracket":
case "[", "lsqrbracket", "leftsqrbracket", "leftsquarebracket":
key = gocui.KeyCtrlLsqBracket
case "]":
fallthrough
case "rsqrbracket":
fallthrough
case "rightsqrbracket":
fallthrough
case "rightsquarebracket":
case "]", "rsqrbracket", "rightsqrbracket", "rightsquarebracket":
key = gocui.KeyCtrlRsqBracket
case "space":
key = gocui.KeyCtrlSpace
@ -130,41 +118,19 @@ func (ct *Cointop) ParseKeys(s string) (interface{}, gocui.Modifier) {
s = strings.ToLower(s)
switch s {
case "arrowup":
fallthrough
case "uparrow":
fallthrough
case "up":
case "arrowup", "uparrow", "up":
key = gocui.KeyArrowUp
case "arrowdown":
fallthrough
case "downarrow":
fallthrough
case "down":
case "arrowdown", "downarrow", "down":
key = gocui.KeyArrowDown
case "arrowleft":
fallthrough
case "leftarrow":
fallthrough
case "left":
case "arrowleft", "leftarrow", "left":
key = gocui.KeyArrowLeft
case "arrowright":
fallthrough
case "rightarrow":
fallthrough
case "right":
case "arrowright", "rightarrow", "right":
key = gocui.KeyArrowRight
case "enter":
fallthrough
case "return":
case "enter", "return":
key = gocui.KeyEnter
case "space":
fallthrough
case "spacebar":
case "space", "spacebar":
key = gocui.KeySpace
case "esc":
fallthrough
case "escape":
case "esc", "escape":
key = gocui.KeyEsc
case "f1":
key = gocui.KeyF1
@ -186,15 +152,9 @@ func (ct *Cointop) ParseKeys(s string) (interface{}, gocui.Modifier) {
key = gocui.KeyF9
case "tab":
key = gocui.KeyTab
case "pageup":
fallthrough
case "pgup":
case "pageup", "pgup":
key = gocui.KeyPgup
case "pagedown":
fallthrough
case "pgdown":
fallthrough
case "pgdn":
case "pagedown", "pgdown", "pgdn":
key = gocui.KeyPgdn
case "home":
key = gocui.KeyHome
@ -248,9 +208,7 @@ func (ct *Cointop) Keybindings(g *gocui.Gui) error {
fn = ct.Keyfn(ct.SortPrevCol)
case "sort_right_column":
fn = ct.Keyfn(ct.SortNextCol)
case "help":
fallthrough
case "toggle_show_help":
case "help", "toggle_show_help":
fn = ct.Keyfn(ct.ToggleHelp)
view = ""
case "show_help":
@ -276,7 +234,7 @@ func (ct *Cointop) Keybindings(g *gocui.Gui) error {
case "move_to_page_visible_last_row":
fn = ct.Keyfn(ct.navigatePageLastLine)
case "sort_column_market_cap":
fn = ct.Sortfn("marketcap", true)
fn = ct.Sortfn("market_cap", true)
case "move_to_page_visible_middle_row":
fn = ct.Keyfn(ct.NavigatePageMiddleLine)
case "scroll_left":

@ -6,7 +6,6 @@ import (
"fmt"
"math"
"os"
"regexp"
"sort"
"strconv"
"strings"
@ -54,157 +53,195 @@ func (ct *Cointop) GetPortfolioTable() *table.Table {
total := ct.GetPortfolioTotal()
maxX := ct.width()
t := table.NewTable().SetWidth(maxX)
var rows [][]*table.RowCell
headers := ct.GetPortfolioTableHeaders()
ct.ClearSyncMap(ct.State.tableColumnWidths)
ct.ClearSyncMap(ct.State.tableColumnAlignLeft)
for _, coin := range ct.State.coins {
star := ct.colorscheme.TableRow(" ")
name := TruncateString(coin.Name, 20)
symbol := TruncateString(coin.Symbol, 6)
if coin.Favorite {
star = ct.colorscheme.TableRowFavorite("*")
}
rank := fmt.Sprintf("%s%v", star, ct.colorscheme.TableRow(fmt.Sprintf("%6v ", coin.Rank)))
namecolor := ct.colorscheme.TableRow
if coin.Favorite {
namecolor = ct.colorscheme.TableRowFavorite
}
colorbalance := ct.colorscheme.TableColumnPrice
color1h := ct.colorscheme.TableColumnChange
color24h := ct.colorscheme.TableColumnChange
color7d := ct.colorscheme.TableColumnChange
if coin.PercentChange1H > 0 {
color1h = ct.colorscheme.TableColumnChangeUp
}
if coin.PercentChange1H < 0 {
color1h = ct.colorscheme.TableColumnChangeDown
}
if coin.PercentChange24H > 0 {
color24h = ct.colorscheme.TableColumnChangeUp
}
if coin.PercentChange24H < 0 {
color24h = ct.colorscheme.TableColumnChangeDown
}
if coin.PercentChange7D > 0 {
color7d = ct.colorscheme.TableColumnChangeUp
}
if coin.PercentChange7D < 0 {
color7d = ct.colorscheme.TableColumnChangeDown
}
percentHoldings := (coin.Balance / total) * 1e2
if math.IsNaN(percentHoldings) {
percentHoldings = 0
}
unix, _ := strconv.ParseInt(coin.LastUpdated, 10, 64)
lastUpdated := time.Unix(unix, 0).Format("15:04:05 Jan 02")
headers := ct.GetPortfolioTableHeaders()
leftMargin := 1
rightMargin := 1
var rowCells []*table.RowCell
for _, header := range headers {
switch header {
case "rank":
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)))
ct.SetTableColumnWidth(header, 8)
ct.SetTableColumnAlignLeft(header, false)
rowCells = append(rowCells, &table.RowCell{
LeftMargin: 0,
Width: 6,
LeftAlign: false,
Color: ct.colorscheme.Default,
Text: rank,
LeftMargin: leftMargin,
RightMargin: rightMargin,
LeftAlign: false,
Color: ct.colorscheme.Default,
Text: rank,
})
case "name":
name := TruncateString(coin.Name, 18)
namecolor := ct.colorscheme.TableRow
if coin.Favorite {
namecolor = ct.colorscheme.TableRowFavorite
}
ct.SetTableColumnWidthFromString(header, name)
ct.SetTableColumnAlignLeft(header, true)
rowCells = append(rowCells,
&table.RowCell{
LeftMargin: 1,
Width: 22,
LeftAlign: true,
Color: namecolor,
Text: name,
LeftMargin: leftMargin,
RightMargin: rightMargin,
LeftAlign: true,
Color: namecolor,
Text: name,
})
case "symbol":
symbol := TruncateString(coin.Symbol, 6)
ct.SetTableColumnWidthFromString(header, symbol)
ct.SetTableColumnAlignLeft(header, true)
rowCells = append(rowCells,
&table.RowCell{
LeftMargin: 1,
Width: 6,
LeftAlign: true,
Color: ct.colorscheme.TableRow,
Text: symbol,
LeftMargin: leftMargin,
RightMargin: rightMargin,
LeftAlign: true,
Color: ct.colorscheme.TableRow,
Text: symbol,
})
case "price":
text := humanize.Commaf(coin.Price)
symbolPadding := 1
ct.SetTableColumnWidth(header, len(text)+symbolPadding)
ct.SetTableColumnAlignLeft(header, false)
rowCells = append(rowCells,
&table.RowCell{
LeftMargin: 1,
Width: 14,
LeftAlign: false,
Color: ct.colorscheme.TableRow,
Text: humanize.Commaf(coin.Price),
LeftMargin: leftMargin,
RightMargin: rightMargin,
LeftAlign: false,
Color: ct.colorscheme.TableRow,
Text: text,
})
case "holdings":
text := strconv.FormatFloat(coin.Holdings, 'f', -1, 64)
ct.SetTableColumnWidthFromString(header, text)
ct.SetTableColumnAlignLeft(header, false)
rowCells = append(rowCells,
&table.RowCell{
LeftMargin: 1,
Width: 16,
LeftAlign: false,
Color: ct.colorscheme.TableRow,
Text: strconv.FormatFloat(coin.Holdings, 'f', -1, 64),
LeftMargin: leftMargin,
RightMargin: rightMargin,
LeftAlign: false,
Color: ct.colorscheme.TableRow,
Text: text,
})
case "balance":
text := humanize.Commaf(coin.Balance)
ct.SetTableColumnWidthFromString(header, text)
ct.SetTableColumnAlignLeft(header, false)
colorBalance := ct.colorscheme.TableColumnPrice
rowCells = append(rowCells,
&table.RowCell{
LeftMargin: 1,
Width: 16,
LeftAlign: false,
Color: colorbalance,
Text: humanize.Commaf(coin.Balance),
LeftMargin: leftMargin,
RightMargin: rightMargin,
LeftAlign: false,
Color: colorBalance,
Text: text,
})
case "1h_change":
color1h := ct.colorscheme.TableColumnChange
if coin.PercentChange1H > 0 {
color1h = ct.colorscheme.TableColumnChangeUp
}
if coin.PercentChange1H < 0 {
color1h = ct.colorscheme.TableColumnChangeDown
}
text := fmt.Sprintf("%.2f%%", coin.PercentChange1H)
ct.SetTableColumnWidthFromString(header, text)
ct.SetTableColumnAlignLeft(header, false)
rowCells = append(rowCells,
&table.RowCell{
LeftMargin: 1,
Width: 11,
LeftAlign: false,
Color: color1h,
Text: fmt.Sprintf("%.2f%%", coin.PercentChange1H),
LeftMargin: leftMargin,
RightMargin: rightMargin,
LeftAlign: false,
Color: color1h,
Text: text,
})
case "24h_change":
color24h := ct.colorscheme.TableColumnChange
if coin.PercentChange24H > 0 {
color24h = ct.colorscheme.TableColumnChangeUp
}
if coin.PercentChange24H < 0 {
color24h = ct.colorscheme.TableColumnChangeDown
}
text := fmt.Sprintf("%.2f%%", coin.PercentChange24H)
ct.SetTableColumnWidthFromString(header, text)
ct.SetTableColumnAlignLeft(header, false)
rowCells = append(rowCells,
&table.RowCell{
LeftMargin: 1,
Width: 10,
LeftAlign: false,
Color: color24h,
Text: fmt.Sprintf("%.2f%%", coin.PercentChange24H),
LeftMargin: leftMargin,
RightMargin: rightMargin,
LeftAlign: false,
Color: color24h,
Text: text,
})
case "7d_change":
color7d := ct.colorscheme.TableColumnChange
if coin.PercentChange7D > 0 {
color7d = ct.colorscheme.TableColumnChangeUp
}
if coin.PercentChange7D < 0 {
color7d = ct.colorscheme.TableColumnChangeDown
}
text := fmt.Sprintf("%.2f%%", coin.PercentChange7D)
ct.SetTableColumnWidthFromString(header, text)
ct.SetTableColumnAlignLeft(header, false)
rowCells = append(rowCells,
&table.RowCell{
LeftMargin: 1,
Width: 10,
LeftAlign: false,
Color: color7d,
Text: fmt.Sprintf("%.2f%%", coin.PercentChange7D),
LeftMargin: leftMargin,
RightMargin: rightMargin,
LeftAlign: false,
Color: color7d,
Text: text,
})
case "percent_holdings":
percentHoldings := (coin.Balance / total) * 1e2
if math.IsNaN(percentHoldings) {
percentHoldings = 0
}
text := fmt.Sprintf("%.2f%%", percentHoldings)
ct.SetTableColumnWidthFromString(header, text)
ct.SetTableColumnAlignLeft(header, false)
rowCells = append(rowCells,
&table.RowCell{
LeftMargin: 1,
Width: 14,
LeftAlign: false,
Color: ct.colorscheme.TableRow,
Text: fmt.Sprintf("%.2f%%", percentHoldings),
LeftMargin: leftMargin,
RightMargin: rightMargin,
LeftAlign: false,
Color: ct.colorscheme.TableRow,
Text: text,
})
case "last_updated":
unix, _ := strconv.ParseInt(coin.LastUpdated, 10, 64)
lastUpdated := time.Unix(unix, 0).Format("15:04:05 Jan 02")
ct.SetTableColumnWidthFromString(header, lastUpdated)
ct.SetTableColumnAlignLeft(header, false)
rowCells = append(rowCells,
&table.RowCell{
LeftMargin: 1,
Width: 18,
LeftAlign: false,
Color: ct.colorscheme.TableRow,
Text: lastUpdated,
LeftMargin: leftMargin,
RightMargin: rightMargin,
LeftAlign: false,
Color: ct.colorscheme.TableRow,
Text: lastUpdated,
})
}
}
t.AddRowCells(rowCells...)
rows = append(rows, rowCells)
}
for _, row := range rows {
for i, header := range headers {
row[i].Width = ct.GetTableColumnWidth(header)
}
t.AddRowCells(row...)
}
return t
@ -752,14 +789,3 @@ func (ct *Cointop) PrintTotalHoldings(options *TablePrintOptions) error {
func (ct *Cointop) IsPortfolioVisible() bool {
return ct.State.selectedView == PortfolioView
}
// NormalizeFloatString normalizes a float as a string
func normalizeFloatString(input string) string {
re := regexp.MustCompile(`(\d+\.\d+|\.\d+|\d+)`)
result := re.FindStringSubmatch(input)
if len(result) > 0 {
return result[0]
}
return ""
}

@ -45,7 +45,10 @@ func (ct *Cointop) GetPriceAlertsTable() *table.Table {
ct.debuglog("getPriceAlertsTable()")
maxX := ct.width()
t := table.NewTable().SetWidth(maxX)
var rows [][]*table.RowCell
headers := ct.GetPriceAlertsTableHeaders()
ct.ClearSyncMap(ct.State.tableColumnWidths)
ct.ClearSyncMap(ct.State.tableColumnAlignLeft)
for _, entry := range ct.State.priceAlerts.Entries {
if entry.Expired {
continue
@ -58,53 +61,83 @@ func (ct *Cointop) GetPriceAlertsTable() *table.Table {
if !ok {
continue
}
name := TruncateString(entry.CoinName, 20)
symbol := TruncateString(coin.Symbol, 6)
namecolor := ct.colorscheme.TableRow
frequency := entry.Frequency
_, ok = PriceAlertOperatorMap[entry.Operator]
if !ok {
continue
}
targetPrice := fmt.Sprintf("%s %s", entry.Operator, humanize.Commaf(entry.TargetPrice))
t.AddRowCells(
&table.RowCell{
LeftMargin: 1,
Width: 22,
LeftAlign: true,
Color: namecolor,
Text: name,
},
&table.RowCell{
LeftMargin: 1,
Width: 10,
LeftAlign: true,
Color: ct.colorscheme.TableRow,
Text: symbol,
},
&table.RowCell{
LeftMargin: 1,
Width: 14,
LeftAlign: false,
Color: ct.colorscheme.TableColumnPrice,
Text: targetPrice,
},
&table.RowCell{
LeftMargin: 1,
Width: 11,
LeftAlign: false,
Color: ct.colorscheme.TableRow,
Text: humanize.Commaf(coin.Price),
},
&table.RowCell{
LeftMargin: 4,
Width: 10,
LeftAlign: true,
Color: ct.colorscheme.TableRow,
Text: frequency,
},
)
leftMargin := 1
rightMargin := 1
var rowCells []*table.RowCell
for _, header := range headers {
switch header {
case "name":
name := TruncateString(entry.CoinName, 16)
ct.SetTableColumnWidthFromString(header, name)
ct.SetTableColumnAlignLeft(header, true)
namecolor := ct.colorscheme.TableRow
rowCells = append(rowCells, &table.RowCell{
LeftMargin: leftMargin,
RightMargin: rightMargin,
LeftAlign: true,
Color: namecolor,
Text: name,
})
case "symbol":
symbol := TruncateString(coin.Symbol, 6)
ct.SetTableColumnWidthFromString(header, symbol)
ct.SetTableColumnAlignLeft(header, true)
rowCells = append(rowCells, &table.RowCell{
LeftMargin: leftMargin,
RightMargin: rightMargin,
LeftAlign: true,
Color: ct.colorscheme.TableRow,
Text: symbol,
})
case "target_price":
targetPrice := fmt.Sprintf("%s %s", entry.Operator, humanize.Commaf(entry.TargetPrice))
ct.SetTableColumnWidthFromString(header, targetPrice)
ct.SetTableColumnAlignLeft(header, false)
rowCells = append(rowCells, &table.RowCell{
LeftMargin: leftMargin,
RightMargin: rightMargin,
LeftAlign: false,
Color: ct.colorscheme.TableColumnPrice,
Text: targetPrice,
})
case "price":
text := humanize.Commaf(coin.Price)
ct.SetTableColumnWidthFromString(header, text)
ct.SetTableColumnAlignLeft(header, false)
rowCells = append(rowCells, &table.RowCell{
LeftMargin: leftMargin,
RightMargin: rightMargin,
LeftAlign: false,
Color: ct.colorscheme.TableRow,
Text: text,
})
case "frequency":
frequency := entry.Frequency
ct.SetTableColumnWidthFromString(header, frequency)
ct.SetTableColumnAlignLeft(header, true)
rowCells = append(rowCells, &table.RowCell{
LeftMargin: leftMargin,
RightMargin: rightMargin,
LeftAlign: true,
Color: ct.colorscheme.TableRow,
Text: frequency,
})
}
}
rows = append(rows, rowCells)
}
for _, row := range rows {
for i, header := range headers {
row[i].Width = ct.GetTableColumnWidth(header)
}
t.AddRowCells(row...)
}
return t

@ -47,7 +47,7 @@ func (ct *Cointop) Sort(sortBy string, desc bool, list []*Coin, renderHeaders bo
return a.Holdings < b.Holdings
case "balance":
return a.Balance < b.Balance
case "marketcap":
case "market_cap":
return a.MarketCap < b.MarketCap
case "24h_volume":
return a.Volume24H < b.Volume24H

@ -26,7 +26,7 @@ func TableColumnOrder() []string {
"price",
"holdings",
"balance",
"marketcap",
"market_cap",
"24h_volume",
"1h_change",
"7d_change",

@ -2,11 +2,116 @@ package cointop
import (
"fmt"
"math"
"strings"
"unicode/utf8"
"github.com/miguelmota/cointop/pkg/pad"
"github.com/miguelmota/cointop/pkg/ui"
)
// ArrowUp is up arrow unicode character
var ArrowUp = "▲"
// ArrowDown is down arrow unicode character
var ArrowDown = "▼"
// HeaderColumn is header column struct
type HeaderColumn struct {
Slug string
Label string
PlainLabel string
}
// HeaderColumns are the header column widths
var HeaderColumns = map[string]*HeaderColumn{
"rank": &HeaderColumn{
Slug: "rank",
Label: "[r]ank",
PlainLabel: "rank",
},
"name": &HeaderColumn{
Slug: "name",
Label: "[n]ame",
PlainLabel: "name",
},
"symbol": &HeaderColumn{
Slug: "symbol",
Label: "[s]ymbol",
PlainLabel: "symbol",
},
"target_price": &HeaderColumn{
Slug: "target_price",
Label: "[t]target price",
PlainLabel: "target price",
},
"price": &HeaderColumn{
Slug: "price",
Label: "[p]rice",
PlainLabel: "price",
},
"frequency": &HeaderColumn{
Slug: "frequency",
Label: "frequency",
PlainLabel: "frequency",
},
"holdings": &HeaderColumn{
Slug: "holdings",
Label: "[h]oldings",
PlainLabel: "holdings",
},
"balance": &HeaderColumn{
Slug: "balance",
Label: "[b]alance",
PlainLabel: "balance",
},
"market_cap": &HeaderColumn{
Slug: "market_cap",
Label: "[m]arket cap",
PlainLabel: "market cap",
},
"24h_volume": &HeaderColumn{
Slug: "24h_volume",
Label: "24H [v]olume",
PlainLabel: "24H volume",
},
"1h_change": &HeaderColumn{
Slug: "1h_change",
Label: "[1]H%",
PlainLabel: "1H%",
},
"24h_change": &HeaderColumn{
Slug: "24h_change",
Label: "[2]4H%",
PlainLabel: "24H%",
},
"7d_change": &HeaderColumn{
Slug: "7d_change",
Label: "[7]D%",
PlainLabel: "7D%",
},
"total_supply": &HeaderColumn{
Slug: "total_supply",
Label: "[t]otal supply",
PlainLabel: "total supply",
},
"available_supply": &HeaderColumn{
Slug: "available_supply",
Label: "[a]vailable supply",
PlainLabel: "available supply",
},
"percent_holdings": &HeaderColumn{
Slug: "percent_holdings",
Label: "[%]holdings",
PlainLabel: "%holdings",
},
"last_updated": &HeaderColumn{
Slug: "last_updated",
Label: "last [u]pdated",
PlainLabel: "last updated",
},
}
// TableHeaderView is structure for table header view
type TableHeaderView = ui.View
@ -20,56 +125,8 @@ func NewTableHeaderView() *TableHeaderView {
func (ct *Cointop) UpdateTableHeader() error {
ct.debuglog("UpdateTableHeader()")
type t struct {
colorfn func(a ...interface{}) string
displaytext string
padleft int
padright int
arrow string
}
baseColor := ct.colorscheme.TableHeaderSprintf()
offset := 0
lb := "["
rb := "]"
noSort := ct.IsPriceAlertsVisible()
if noSort {
offset = 2
lb = ""
rb = ""
}
possibleHeaders := map[string]*t{
"rank": {baseColor, fmt.Sprintf("%sr%sank", lb, rb), 0, 1 + offset, " "},
"name": {baseColor, fmt.Sprintf("%sn%same", lb, rb), 0, 11 + offset, " "},
"symbol": {baseColor, fmt.Sprintf("%ss%symbol", lb, rb), 4, 0 + offset, " "},
"target_price": {baseColor, fmt.Sprintf("%st%sarget price", lb, rb), 2, 0 + offset, " "},
"price": {baseColor, fmt.Sprintf("%sp%srice", lb, rb), 2, 0 + offset, " "},
"frequency": {baseColor, "frequency", 1, 0, " "},
"holdings": {baseColor, fmt.Sprintf("%sh%soldings", lb, rb), 5, 0 + offset, " "},
"balance": {baseColor, fmt.Sprintf("%sb%salance", lb, rb), 5, 0, " "},
"marketcap": {baseColor, fmt.Sprintf("%sm%sarket cap", lb, rb), 5, 0 + offset, " "},
"24h_volume": {baseColor, fmt.Sprintf("24H %sv%solume", lb, rb), 3, 0 + offset, " "},
"1h_change": {baseColor, fmt.Sprintf("%s1%sH%%", lb, rb), 5, 0 + offset, " "},
"24h_change": {baseColor, fmt.Sprintf("%s2%s4H%%", lb, rb), 3, 0 + offset, " "},
"7d_change": {baseColor, fmt.Sprintf("%s7%sD%%", lb, rb), 4, 0 + offset, " "},
"total_supply": {baseColor, fmt.Sprintf("%st%sotal supply", lb, rb), 7, 0 + offset, " "},
"available_supply": {baseColor, fmt.Sprintf("%sa%svailable supply", lb, rb), 1, 0 + offset, " "},
"percent_holdings": {baseColor, fmt.Sprintf("%s%%%sholdings", lb, rb), 2, 0 + offset, " "},
"last_updated": {baseColor, fmt.Sprintf("last %su%spdated", lb, rb), 3, 0, " "},
}
for k := range possibleHeaders {
possibleHeaders[k].arrow = " "
if ct.State.sortBy == k {
possibleHeaders[k].colorfn = ct.colorscheme.TableHeaderColumnActiveSprintf()
if ct.State.sortDesc {
possibleHeaders[k].arrow = "▼"
} else {
possibleHeaders[k].arrow = "▲"
}
}
}
var cols []string
switch ct.State.selectedView {
case PortfolioView:
@ -81,24 +138,56 @@ func (ct *Cointop) UpdateTableHeader() error {
}
var headers []string
for _, v := range cols {
s, ok := possibleHeaders[v]
for i, col := range cols {
hc, ok := HeaderColumns[col]
if !ok {
continue
}
var str string
d := s.arrow + s.displaytext
if v == "price" || v == "balance" {
d = s.arrow + ct.CurrencySymbol() + s.displaytext
width := ct.GetTableColumnWidth(col)
if width == 0 {
continue
}
str = fmt.Sprintf(
arrow := " "
colorfn := baseColor
if !noSort {
if ct.State.sortBy == col {
colorfn = ct.colorscheme.TableHeaderColumnActiveSprintf()
if ct.State.sortDesc {
arrow = ArrowDown
} else {
arrow = ArrowUp
}
}
}
label := hc.Label
if noSort {
label = hc.PlainLabel
}
leftAlign := ct.GetTableColumnAlignLeft(col)
switch col {
case "price", "balance":
label = ct.CurrencySymbol() + label
}
if leftAlign {
label = label + arrow
} else {
label = arrow + label
}
padfn := pad.Left
padLeft := 1
if !noSort && i == 0 {
padLeft = 0
}
if leftAlign {
padfn = pad.Right
}
colStr := fmt.Sprintf(
"%s%s%s",
strings.Repeat(" ", s.padleft),
s.colorfn(d),
strings.Repeat(" ", s.padright),
strings.Repeat(" ", padLeft),
colorfn(padfn(label, width+(1-padLeft), " ")),
strings.Repeat(" ", 1),
)
headers = append(headers, str)
headers = append(headers, colStr)
}
ct.UpdateUI(func() error {
@ -107,3 +196,49 @@ func (ct *Cointop) UpdateTableHeader() error {
return nil
}
// SetTableColumnAlignLeft sets the column alignment direction for header
func (ct *Cointop) SetTableColumnAlignLeft(header string, alignLeft bool) {
ct.State.tableColumnAlignLeft.Store(header, alignLeft)
}
// GetTableColumnAlignLeft gets the column alignment direction for header
func (ct *Cointop) GetTableColumnAlignLeft(header string) bool {
ifc, ok := ct.State.tableColumnAlignLeft.Load(header)
if ok {
return ifc.(bool)
}
return false
}
// SetTableColumnWidth sets the column width for header
func (ct *Cointop) SetTableColumnWidth(header string, width int) {
prevIfc, ok := ct.State.tableColumnWidths.Load(header)
var prev int
if ok {
prev = prevIfc.(int)
} else {
hc := HeaderColumns[header]
prev = utf8.RuneCountInString(hc.Label) + 1
switch header {
case "price", "balance":
prev++
}
}
ct.State.tableColumnWidths.Store(header, int(math.Max(float64(width), float64(prev))))
}
// SetTableColumnWidthFromString sets the column width for header given size of string
func (ct *Cointop) SetTableColumnWidthFromString(header string, text string) {
ct.SetTableColumnWidth(header, utf8.RuneCountInString(text))
}
// GetTableColumnWidth gets the column width for header
func (ct *Cointop) GetTableColumnWidth(header string) int {
ifc, ok := ct.State.tableColumnWidths.Load(header)
if ok {
return ifc.(int)
}
return 0
}

@ -4,7 +4,9 @@ import (
"bytes"
"encoding/gob"
"fmt"
"regexp"
"strings"
"sync"
"github.com/miguelmota/cointop/pkg/open"
)
@ -41,3 +43,22 @@ func TruncateString(value string, maxLen int) string {
}
return value
}
// ClearSyncMap clears a sync.Map
func (ct *Cointop) ClearSyncMap(syncMap sync.Map) {
syncMap.Range(func(key interface{}, value interface{}) bool {
syncMap.Delete(key)
return true
})
}
// NormalizeFloatString normalizes a float as a string
func normalizeFloatString(input string) string {
re := regexp.MustCompile(`(\d+\.\d+|\.\d+|\d+)`)
result := re.FindStringSubmatch(input)
if len(result) > 0 {
return result[0]
}
return ""
}

@ -1,5 +1,7 @@
package pad
import "unicode/utf8"
func times(str string, n int) (out string) {
for i := 0; i < n; i++ {
out += str
@ -10,10 +12,10 @@ func times(str string, n int) (out string) {
// Left left-pads the string with pad up to len runes
// len may be exceeded if
func Left(str string, length int, pad string) string {
return times(pad, length-len(str)) + str
return times(pad, length-utf8.RuneCountInString(str)) + str
}
// Right right-pads the string with pad up to len runes
func Right(str string, length int, pad string) string {
return str + times(pad, length-len(str))
return str + times(pad, length-utf8.RuneCountInString(str))
}

@ -248,20 +248,22 @@ func (t *Table) Fprint(w io.Writer) {
// RowCell is a row cell struct
type RowCell struct {
LeftMargin int
Width int
LeftAlign bool
Color func(a ...interface{}) string
Text string
LeftMargin int
RightMargin int
Width int
LeftAlign bool
Color func(a ...interface{}) string
Text string
}
// String returns row cell as string
func (rc *RowCell) String() string {
t := strings.Repeat(" ", rc.LeftMargin) + rc.Text
t := rc.Text
if rc.LeftAlign {
t = pad.Right(t, rc.Width, " ")
} else {
t = fmt.Sprintf("%"+fmt.Sprintf("%v", rc.Width)+"s", t)
}
t = strings.Repeat(" ", rc.LeftMargin) + t + strings.Repeat(" ", rc.RightMargin)
return rc.Color(t)
}

Loading…
Cancel
Save