From 394d35473d11a9fdb5447468fba41b15473714e2 Mon Sep 17 00:00:00 2001 From: Miguel Mota Date: Mon, 16 Nov 2020 19:12:24 -0800 Subject: [PATCH] Consolidate UI packages --- cmd/cointop/cointop.go | 2 +- {cointop/cmd => cmd/commands}/clean.go | 2 +- {cointop/cmd => cmd/commands}/cmd.go | 0 {cointop/cmd => cmd/commands}/dominance.go | 0 {cointop/cmd => cmd/commands}/holdings.go | 0 {cointop/cmd => cmd/commands}/price.go | 0 {cointop/cmd => cmd/commands}/reset.go | 2 +- {cointop/cmd => cmd/commands}/root.go | 0 {cointop/cmd => cmd/commands}/server.go | 2 +- .../cmd => cmd/commands}/server_windows.go | 0 {cointop/cmd => cmd/commands}/test.go | 0 {cointop/cmd => cmd/commands}/version.go | 0 cointop/active_view.go | 18 ++ cointop/api/coingecko/README.md | 1 - cointop/chart.go | 112 ++------ cointop/cointop.go | 40 +-- cointop/colorscheme.go | 12 - cointop/config.go | 2 +- cointop/conversion.go | 41 ++- .../{shortcuts.go => default_shortcuts.go} | 0 cointop/dominance.go | 2 +- cointop/help.go | 36 +-- cointop/layout.go | 143 ++++------- cointop/list.go | 2 +- cointop/marketbar.go | 30 +-- cointop/navigation.go | 170 ++++-------- cointop/portfolio.go | 49 ++-- cointop/price.go | 4 +- cointop/quit.go | 2 +- cointop/search.go | 24 +- cointop/statusbar.go | 29 +-- cointop/table.go | 34 +-- cointop/table_header.go | 23 +- cointop/update.go | 6 +- cointop/util.go | 2 +- cointop/view.go | 83 ------ main.go | 2 +- {cointop/common => pkg}/api/api.go | 4 +- .../api/impl/coingecko/coingecko.go | 8 +- .../api/impl/coinmarketcap/coinmarketcap.go | 4 +- .../api/impl/cryptocompare/cryptocompare.go | 0 {cointop/common => pkg}/api/interface.go | 2 +- {cointop/common => pkg}/api/types/types.go | 0 {cointop/common => pkg}/api/util/util.go | 0 .../api/vendors}/coingecko/LICENSE.txt | 0 .../api/vendors}/coingecko/format/format.go | 0 .../api/vendors}/coingecko/v3/types/model.go | 0 .../api/vendors}/coingecko/v3/types/types.go | 0 .../api/vendors}/coingecko/v3/v3.go | 4 +- .../common => pkg}/asciitable/asciitable.go | 0 pkg/chartplot/chartplot.go | 84 ++++++ {cointop/common => pkg}/color/color.go | 0 .../common => pkg}/filecache/filecache.go | 0 {cointop/common => pkg}/humanize/humanize.go | 0 .../common => pkg}/levenshtein/levenshtein.go | 0 {cointop/common => pkg}/open/open.go | 0 {cointop/common => pkg}/open/open_windows.go | 0 {cointop/common => pkg}/pad/pad.go | 0 {cointop/common => pkg}/pathutil/pathutil.go | 0 .../common => pkg}/pathutil/pathutil_test.go | 0 {cointop => pkg}/ssh/server.go | 2 +- {cointop/common => pkg}/table/README.md | 0 {cointop/common => pkg}/table/align.go | 0 {cointop/common => pkg}/table/align/align.go | 0 {cointop/common => pkg}/table/column.go | 0 {cointop/common => pkg}/table/row.go | 0 {cointop/common => pkg}/table/sort.go | 0 {cointop/common => pkg}/table/table.go | 2 +- .../common/gizak => pkg}/termui/.gitignore | 0 .../common/gizak => pkg}/termui/.travis.yml | 0 .../common/gizak => pkg}/termui/Gopkg.toml | 0 {cointop/common/gizak => pkg}/termui/LICENSE | 0 .../common/gizak => pkg}/termui/README.md | 0 .../common/gizak => pkg}/termui/barchart.go | 0 {cointop/common/gizak => pkg}/termui/block.go | 0 .../gizak => pkg}/termui/block_common.go | 0 .../gizak => pkg}/termui/block_windows.go | 0 .../common/gizak => pkg}/termui/buffer.go | 0 .../common/gizak => pkg}/termui/canvas.go | 0 .../common/gizak => pkg}/termui/config.py | 0 {cointop/common/gizak => pkg}/termui/doc.go | 0 .../common/gizak => pkg}/termui/events.go | 0 {cointop/common/gizak => pkg}/termui/gauge.go | 0 {cointop/common/gizak => pkg}/termui/grid.go | 0 .../common/gizak => pkg}/termui/helper.go | 0 .../common/gizak => pkg}/termui/linechart.go | 0 .../gizak => pkg}/termui/linechart_others.go | 0 .../gizak => pkg}/termui/linechart_windows.go | 0 {cointop/common/gizak => pkg}/termui/list.go | 0 .../common/gizak => pkg}/termui/mbarchart.go | 0 .../common/gizak => pkg}/termui/mkdocs.yml | 0 {cointop/common/gizak => pkg}/termui/par.go | 0 {cointop/common/gizak => pkg}/termui/pos.go | 0 .../common/gizak => pkg}/termui/render.go | 0 .../common/gizak => pkg}/termui/sparkline.go | 0 {cointop/common/gizak => pkg}/termui/table.go | 0 .../gizak => pkg}/termui/textbuilder.go | 0 {cointop/common/gizak => pkg}/termui/theme.go | 0 .../common/gizak => pkg}/termui/widget.go | 0 {cointop/common => pkg}/timeutil/timeutil.go | 0 pkg/ui/ui.go | 94 +++++++ pkg/ui/view.go | 242 ++++++++++++++++++ 102 files changed, 720 insertions(+), 601 deletions(-) rename {cointop/cmd => cmd/commands}/clean.go (91%) rename {cointop/cmd => cmd/commands}/cmd.go (100%) rename {cointop/cmd => cmd/commands}/dominance.go (100%) rename {cointop/cmd => cmd/commands}/holdings.go (100%) rename {cointop/cmd => cmd/commands}/price.go (100%) rename {cointop/cmd => cmd/commands}/reset.go (92%) rename {cointop/cmd => cmd/commands}/root.go (100%) rename {cointop/cmd => cmd/commands}/server.go (96%) rename {cointop/cmd => cmd/commands}/server_windows.go (100%) rename {cointop/cmd => cmd/commands}/test.go (100%) rename {cointop/cmd => cmd/commands}/version.go (100%) create mode 100644 cointop/active_view.go delete mode 100644 cointop/api/coingecko/README.md rename cointop/{shortcuts.go => default_shortcuts.go} (100%) delete mode 100644 cointop/view.go rename {cointop/common => pkg}/api/api.go (64%) rename {cointop/common => pkg}/api/impl/coingecko/coingecko.go (96%) rename {cointop/common => pkg}/api/impl/coinmarketcap/coinmarketcap.go (97%) rename {cointop/common => pkg}/api/impl/cryptocompare/cryptocompare.go (100%) rename {cointop/common => pkg}/api/interface.go (93%) rename {cointop/common => pkg}/api/types/types.go (100%) rename {cointop/common => pkg}/api/util/util.go (100%) rename {cointop/api => pkg/api/vendors}/coingecko/LICENSE.txt (100%) rename {cointop/api => pkg/api/vendors}/coingecko/format/format.go (100%) rename {cointop/api => pkg/api/vendors}/coingecko/v3/types/model.go (100%) rename {cointop/api => pkg/api/vendors}/coingecko/v3/types/types.go (100%) rename {cointop/api => pkg/api/vendors}/coingecko/v3/v3.go (98%) rename {cointop/common => pkg}/asciitable/asciitable.go (100%) create mode 100644 pkg/chartplot/chartplot.go rename {cointop/common => pkg}/color/color.go (100%) rename {cointop/common => pkg}/filecache/filecache.go (100%) rename {cointop/common => pkg}/humanize/humanize.go (100%) rename {cointop/common => pkg}/levenshtein/levenshtein.go (100%) rename {cointop/common => pkg}/open/open.go (100%) rename {cointop/common => pkg}/open/open_windows.go (100%) rename {cointop/common => pkg}/pad/pad.go (100%) rename {cointop/common => pkg}/pathutil/pathutil.go (100%) rename {cointop/common => pkg}/pathutil/pathutil_test.go (100%) rename {cointop => pkg}/ssh/server.go (98%) rename {cointop/common => pkg}/table/README.md (100%) rename {cointop/common => pkg}/table/align.go (100%) rename {cointop/common => pkg}/table/align/align.go (100%) rename {cointop/common => pkg}/table/column.go (100%) rename {cointop/common => pkg}/table/row.go (100%) rename {cointop/common => pkg}/table/sort.go (100%) rename {cointop/common => pkg}/table/table.go (98%) rename {cointop/common/gizak => pkg}/termui/.gitignore (100%) rename {cointop/common/gizak => pkg}/termui/.travis.yml (100%) rename {cointop/common/gizak => pkg}/termui/Gopkg.toml (100%) rename {cointop/common/gizak => pkg}/termui/LICENSE (100%) rename {cointop/common/gizak => pkg}/termui/README.md (100%) rename {cointop/common/gizak => pkg}/termui/barchart.go (100%) rename {cointop/common/gizak => pkg}/termui/block.go (100%) rename {cointop/common/gizak => pkg}/termui/block_common.go (100%) rename {cointop/common/gizak => pkg}/termui/block_windows.go (100%) rename {cointop/common/gizak => pkg}/termui/buffer.go (100%) rename {cointop/common/gizak => pkg}/termui/canvas.go (100%) rename {cointop/common/gizak => pkg}/termui/config.py (100%) rename {cointop/common/gizak => pkg}/termui/doc.go (100%) rename {cointop/common/gizak => pkg}/termui/events.go (100%) rename {cointop/common/gizak => pkg}/termui/gauge.go (100%) rename {cointop/common/gizak => pkg}/termui/grid.go (100%) rename {cointop/common/gizak => pkg}/termui/helper.go (100%) rename {cointop/common/gizak => pkg}/termui/linechart.go (100%) rename {cointop/common/gizak => pkg}/termui/linechart_others.go (100%) rename {cointop/common/gizak => pkg}/termui/linechart_windows.go (100%) rename {cointop/common/gizak => pkg}/termui/list.go (100%) rename {cointop/common/gizak => pkg}/termui/mbarchart.go (100%) rename {cointop/common/gizak => pkg}/termui/mkdocs.yml (100%) rename {cointop/common/gizak => pkg}/termui/par.go (100%) rename {cointop/common/gizak => pkg}/termui/pos.go (100%) rename {cointop/common/gizak => pkg}/termui/render.go (100%) rename {cointop/common/gizak => pkg}/termui/sparkline.go (100%) rename {cointop/common/gizak => pkg}/termui/table.go (100%) rename {cointop/common/gizak => pkg}/termui/textbuilder.go (100%) rename {cointop/common/gizak => pkg}/termui/theme.go (100%) rename {cointop/common/gizak => pkg}/termui/widget.go (100%) rename {cointop/common => pkg}/timeutil/timeutil.go (100%) create mode 100644 pkg/ui/ui.go create mode 100644 pkg/ui/view.go diff --git a/cmd/cointop/cointop.go b/cmd/cointop/cointop.go index 4466657..bca7b13 100644 --- a/cmd/cointop/cointop.go +++ b/cmd/cointop/cointop.go @@ -1,7 +1,7 @@ package main import ( - "github.com/miguelmota/cointop/cointop/cmd" + cmd "github.com/miguelmota/cointop/cmd/commands" ) func main() { diff --git a/cointop/cmd/clean.go b/cmd/commands/clean.go similarity index 91% rename from cointop/cmd/clean.go rename to cmd/commands/clean.go index 14558b2..3c7affd 100644 --- a/cointop/cmd/clean.go +++ b/cmd/commands/clean.go @@ -2,7 +2,7 @@ package cmd import ( "github.com/miguelmota/cointop/cointop" - "github.com/miguelmota/cointop/cointop/common/filecache" + "github.com/miguelmota/cointop/pkg/filecache" "github.com/spf13/cobra" ) diff --git a/cointop/cmd/cmd.go b/cmd/commands/cmd.go similarity index 100% rename from cointop/cmd/cmd.go rename to cmd/commands/cmd.go diff --git a/cointop/cmd/dominance.go b/cmd/commands/dominance.go similarity index 100% rename from cointop/cmd/dominance.go rename to cmd/commands/dominance.go diff --git a/cointop/cmd/holdings.go b/cmd/commands/holdings.go similarity index 100% rename from cointop/cmd/holdings.go rename to cmd/commands/holdings.go diff --git a/cointop/cmd/price.go b/cmd/commands/price.go similarity index 100% rename from cointop/cmd/price.go rename to cmd/commands/price.go diff --git a/cointop/cmd/reset.go b/cmd/commands/reset.go similarity index 92% rename from cointop/cmd/reset.go rename to cmd/commands/reset.go index b2efc57..5b5a3f1 100644 --- a/cointop/cmd/reset.go +++ b/cmd/commands/reset.go @@ -2,7 +2,7 @@ package cmd import ( "github.com/miguelmota/cointop/cointop" - "github.com/miguelmota/cointop/cointop/common/filecache" + "github.com/miguelmota/cointop/pkg/filecache" "github.com/spf13/cobra" ) diff --git a/cointop/cmd/root.go b/cmd/commands/root.go similarity index 100% rename from cointop/cmd/root.go rename to cmd/commands/root.go diff --git a/cointop/cmd/server.go b/cmd/commands/server.go similarity index 96% rename from cointop/cmd/server.go rename to cmd/commands/server.go index d30da16..349b142 100644 --- a/cointop/cmd/server.go +++ b/cmd/commands/server.go @@ -6,7 +6,7 @@ import ( "fmt" "time" - cssh "github.com/miguelmota/cointop/cointop/ssh" + cssh "github.com/miguelmota/cointop/pkg/ssh" "github.com/spf13/cobra" ) diff --git a/cointop/cmd/server_windows.go b/cmd/commands/server_windows.go similarity index 100% rename from cointop/cmd/server_windows.go rename to cmd/commands/server_windows.go diff --git a/cointop/cmd/test.go b/cmd/commands/test.go similarity index 100% rename from cointop/cmd/test.go rename to cmd/commands/test.go diff --git a/cointop/cmd/version.go b/cmd/commands/version.go similarity index 100% rename from cointop/cmd/version.go rename to cmd/commands/version.go diff --git a/cointop/active_view.go b/cointop/active_view.go new file mode 100644 index 0000000..ac99a0a --- /dev/null +++ b/cointop/active_view.go @@ -0,0 +1,18 @@ +package cointop + +// SetActiveView sets the active view +func (ct *Cointop) SetActiveView(v string) error { + ct.g.SetViewOnTop(v) + ct.g.SetCurrentView(v) + if v == ct.Views.SearchField.Name() { + ct.Views.SearchField.SetCursor(1, 0) + ct.Views.SearchField.Update("/") + } else if v == ct.Views.Table.Name() { + ct.g.SetViewOnTop(ct.Views.Statusbar.Name()) + } + if v == ct.Views.PortfolioUpdateMenu.Name() { + ct.g.SetViewOnTop(ct.Views.Input.Name()) + ct.g.SetCurrentView(ct.Views.Input.Name()) + } + return nil +} diff --git a/cointop/api/coingecko/README.md b/cointop/api/coingecko/README.md deleted file mode 100644 index 3386ca8..0000000 --- a/cointop/api/coingecko/README.md +++ /dev/null @@ -1 +0,0 @@ -This is a fork diff --git a/cointop/chart.go b/cointop/chart.go index 04bcfec..dfd3dbf 100644 --- a/cointop/chart.go +++ b/cointop/chart.go @@ -6,18 +6,18 @@ import ( "sync" "time" - "github.com/miguelmota/cointop/cointop/common/gizak/termui" - "github.com/miguelmota/cointop/cointop/common/timeutil" + "github.com/miguelmota/cointop/pkg/chartplot" + "github.com/miguelmota/cointop/pkg/timeutil" + "github.com/miguelmota/cointop/pkg/ui" ) // ChartView is structure for chart view -type ChartView struct { - *View -} +type ChartView = ui.View // NewChartView returns a new chart view func NewChartView() *ChartView { - return &ChartView{NewView("chart")} + var view *ChartView = ui.NewView("chart") + return view } var chartLock sync.Mutex @@ -58,10 +58,6 @@ 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 - } - chartLock.Lock() defer chartLock.Unlock() @@ -83,21 +79,15 @@ func (ct *Cointop) UpdateChart() error { var s string for j := range ct.State.chartPoints[i] { p := ct.State.chartPoints[i][j] - s = fmt.Sprintf("%s%c", s, p.Ch) + s = fmt.Sprintf("%s%c", s, p) } body = fmt.Sprintf("%s%s\n", body, s) } } - ct.Update(func() error { - if ct.Views.Chart.Backing() == nil { - return nil - } - - ct.Views.Chart.Backing().Clear() - fmt.Fprint(ct.Views.Chart.Backing(), ct.colorscheme.Chart(body)) - return nil + ct.UpdateUI(func() error { + return ct.Views.Chart.Update(ct.colorscheme.Chart(body)) }) return nil @@ -114,12 +104,8 @@ func (ct *Cointop) ChartPoints(symbol string, name string) error { // TODO: not do this (SoC) go ct.UpdateMarketbar() - chart := termui.NewLineChart() - chart.Height = ct.State.chartHeight - chart.Border = false - - // NOTE: empty list means don't show x-axis labels - chart.DataLabels = []string{""} + chart := chartplot.NewChartPlot() + chart.SetHeight(ct.State.chartHeight) rangeseconds := ct.chartRangesMap[ct.State.selectedChartRange] if ct.State.selectedChartRange == "YTD" { @@ -165,8 +151,6 @@ func (ct *Cointop) ChartPoints(symbol string, name string) error { return nil } - // NOTE: edit `termui.LineChart.shortenFloatVal(float64)` to not - // use exponential notation. for i := range graphData.Price { price := graphData.Price[i][1] data = append(data, price) @@ -181,32 +165,8 @@ func (ct *Cointop) ChartPoints(symbol string, name string) error { } } - chart.Data = data - termui.Body = termui.NewGrid() - termui.Body.Width = maxX - termui.Body.AddRows( - termui.NewRow( - termui.NewCol(12, 0, chart), - ), - ) - - var points [][]termui.Cell - // calculate layout - termui.Body.Align() - w := termui.Body.Width - h := chart.Height - row := termui.Body.Rows[0] - b := row.Buffer() - for i := 0; i < h; i = i + 1 { - var rowpoints []termui.Cell - for j := 0; j < w; j = j + 1 { - p := b.At(j, i) - rowpoints = append(rowpoints, p) - } - points = append(points, rowpoints) - } - - ct.State.chartPoints = points + chart.SetData(data) + ct.State.chartPoints = chart.GetChartPoints(maxX) return nil } @@ -221,12 +181,8 @@ func (ct *Cointop) PortfolioChart() error { // TODO: not do this (SoC) go ct.UpdateMarketbar() - chart := termui.NewLineChart() - chart.Height = ct.State.chartHeight - chart.Border = false - - // NOTE: empty list means don't show x-axis labels - chart.DataLabels = []string{""} + chart := chartplot.NewChartPlot() + chart.SetHeight(ct.State.chartHeight) rangeseconds := ct.chartRangesMap[ct.State.selectedChartRange] if ct.State.selectedChartRange == "YTD" { @@ -298,32 +254,8 @@ func (ct *Cointop) PortfolioChart() error { } } - chart.Data = data - termui.Body = termui.NewGrid() - termui.Body.Width = maxX - termui.Body.AddRows( - termui.NewRow( - termui.NewCol(12, 0, chart), - ), - ) - - var points [][]termui.Cell - // calculate layout - termui.Body.Align() - w := termui.Body.Width - h := chart.Height - row := termui.Body.Rows[0] - b := row.Buffer() - for i := 0; i < h; i = i + 1 { - var rowpoints []termui.Cell - for j := 0; j < w; j = j + 1 { - p := b.At(j, i) - rowpoints = append(rowpoints, p) - } - points = append(points, rowpoints) - } - - ct.State.chartPoints = points + chart.SetData(data) + ct.State.chartPoints = chart.GetChartPoints(maxX) return nil } @@ -434,15 +366,9 @@ func (ct *Cointop) ToggleCoinChart() error { // ShowChartLoader shows chart loading indicator func (ct *Cointop) ShowChartLoader() error { ct.debuglog("ShowChartLoader()") - ct.Update(func() error { - if ct.Views.Chart.Backing() == nil { - return nil - } - + ct.UpdateUI(func() error { content := "\n\nLoading..." - ct.Views.Chart.Backing().Clear() - fmt.Fprint(ct.Views.Chart.Backing(), ct.colorscheme.Chart(content)) - return nil + return ct.Views.Chart.Update(ct.colorscheme.Chart(content)) }) return nil diff --git a/cointop/cointop.go b/cointop/cointop.go index f908da6..eb3c715 100644 --- a/cointop/cointop.go +++ b/cointop/cointop.go @@ -9,12 +9,12 @@ import ( "sync" "time" - "github.com/miguelmota/cointop/cointop/common/api" - "github.com/miguelmota/cointop/cointop/common/api/types" - "github.com/miguelmota/cointop/cointop/common/filecache" - "github.com/miguelmota/cointop/cointop/common/gizak/termui" - "github.com/miguelmota/cointop/cointop/common/pathutil" - "github.com/miguelmota/cointop/cointop/common/table" + "github.com/miguelmota/cointop/pkg/api" + "github.com/miguelmota/cointop/pkg/api/types" + "github.com/miguelmota/cointop/pkg/filecache" + "github.com/miguelmota/cointop/pkg/pathutil" + "github.com/miguelmota/cointop/pkg/table" + "github.com/miguelmota/cointop/pkg/ui" "github.com/miguelmota/gocui" "github.com/patrickmn/go-cache" ) @@ -44,7 +44,7 @@ type State struct { allCoinsSlugMap sync.Map cacheDir string coins []*Coin - chartPoints [][]termui.Cell + chartPoints [][]rune currencyConversion string convertMenuVisible bool defaultView string @@ -79,6 +79,7 @@ type State struct { // Cointop cointop type Cointop struct { g *gocui.Gui + ui *ui.UI ActionsMap map[string]bool apiKeys *APIKeys cache *cache.Cache @@ -406,26 +407,27 @@ func NewCointop(config *Config) (*Cointop, error) { // Run runs cointop func (ct *Cointop) Run() error { ct.debuglog("run()") - g, err := gocui.NewGui(gocui.Output256) + ui, err := ui.NewUI() if err != nil { - return fmt.Errorf("new gocui: %v", err) + return err } - g.FgColor = ct.colorscheme.BaseFg() - g.BgColor = ct.colorscheme.BaseBg() - ct.g = g - defer g.Close() + ui.SetFgColor(ct.colorscheme.BaseFg()) + ui.SetBgColor(ct.colorscheme.BaseBg()) + ct.ui = ui + ct.g = ui.GetGocui() + defer ui.Close() - g.InputEsc = true - g.Mouse = true - g.Highlight = true - g.SetManagerFunc(ct.layout) - if err := ct.Keybindings(g); err != nil { + ui.SetInputEsc(true) + ui.SetMouse(true) + ui.SetHighlight(true) + ui.SetManagerFunc(ct.layout) + if err := ct.Keybindings(ct.g); err != nil { return fmt.Errorf("keybindings: %v", err) } ct.State.running = true - if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { + if err := ui.MainLoop(); err != nil && err != gocui.ErrQuit { return fmt.Errorf("main loop: %v", err) } diff --git a/cointop/colorscheme.go b/cointop/colorscheme.go index d311dd0..d1b3c68 100644 --- a/cointop/colorscheme.go +++ b/cointop/colorscheme.go @@ -229,18 +229,6 @@ func (c *Colorscheme) TableRowFavoriteSprintf() ISprintf { return c.toSprintf("table_row_favorite") } -// SetViewColor ... -func (c *Colorscheme) SetViewColor(view *gocui.View, name string) { - view.FgColor = c.gocuiFgColor(name) - view.BgColor = c.gocuiBgColor(name) -} - -// SetViewActiveColor ... -func (c *Colorscheme) SetViewActiveColor(view *gocui.View, name string) { - view.SelFgColor = c.gocuiFgColor(name) - view.SelBgColor = c.gocuiBgColor(name) -} - func (c *Colorscheme) toSprintf(name string) ISprintf { if cached, ok := c.cache[name]; ok { return cached diff --git a/cointop/config.go b/cointop/config.go index 8f0b82e..16d8e40 100644 --- a/cointop/config.go +++ b/cointop/config.go @@ -10,7 +10,7 @@ import ( "time" "github.com/BurntSushi/toml" - "github.com/miguelmota/cointop/cointop/common/pathutil" + "github.com/miguelmota/cointop/pkg/pathutil" ) var fileperm = os.FileMode(0644) diff --git a/cointop/conversion.go b/cointop/conversion.go index 67f0d74..404464e 100644 --- a/cointop/conversion.go +++ b/cointop/conversion.go @@ -6,8 +6,9 @@ import ( "sort" "strings" - color "github.com/miguelmota/cointop/cointop/common/color" - "github.com/miguelmota/cointop/cointop/common/pad" + color "github.com/miguelmota/cointop/pkg/color" + "github.com/miguelmota/cointop/pkg/pad" + "github.com/miguelmota/cointop/pkg/ui" ) // FiatCurrencyNames is a mpa of currency symbols to names. @@ -105,13 +106,12 @@ var CurrencySymbolMap = map[string]string{ var alphanumericcharacters = []rune{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'} // ConvertMenuView is structure for convert menu view -type ConvertMenuView struct { - *View -} +type ConvertMenuView = ui.View // NewConvertMenuView returns a new convert menu view func NewConvertMenuView() *ConvertMenuView { - return &ConvertMenuView{NewView("convertmenu")} + var view *ConvertMenuView = ui.NewView("convertmenu") + return view } // IsSupportedCurrencyConversion returns true if it's a supported currency conversion @@ -158,7 +158,7 @@ func (ct *Cointop) SortedSupportedCurrencyConversions() []string { } // UpdateConvertMenu updates the convert menu -func (ct *Cointop) UpdateConvertMenu() { +func (ct *Cointop) UpdateConvertMenu() error { 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" @@ -204,16 +204,12 @@ func (ct *Cointop) UpdateConvertMenu() { } content := fmt.Sprintf("%s%s%s", header, helpline, body) - ct.Update(func() error { - if ct.Views.ConvertMenu.Backing() == nil { - return nil - } - - ct.Views.ConvertMenu.Backing().Clear() - ct.Views.ConvertMenu.Backing().Frame = true - fmt.Fprintln(ct.Views.ConvertMenu.Backing(), content) - return nil + ct.UpdateUI(func() error { + ct.Views.ConvertMenu.SetFrame(true) + return ct.Views.ConvertMenu.Update(content) }) + + return nil } // SetCurrencyConverstion sets the currency conversion @@ -274,16 +270,11 @@ func (ct *Cointop) ShowConvertMenu() error { func (ct *Cointop) HideConvertMenu() error { ct.debuglog("hideConvertMenu()") ct.State.convertMenuVisible = false - ct.SetViewOnBottom(ct.Views.ConvertMenu.Name()) + ct.ui.SetViewOnBottom(ct.Views.ConvertMenu) ct.SetActiveView(ct.Views.Table.Name()) - ct.Update(func() error { - if ct.Views.ConvertMenu.Backing() == nil { - return nil - } - - ct.Views.ConvertMenu.Backing().Clear() - ct.Views.ConvertMenu.Backing().Frame = false - fmt.Fprintln(ct.Views.ConvertMenu.Backing(), "") + ct.UpdateUI(func() error { + ct.Views.ConvertMenu.SetFrame(false) + return ct.Views.ConvertMenu.Update("") return nil }) return nil diff --git a/cointop/shortcuts.go b/cointop/default_shortcuts.go similarity index 100% rename from cointop/shortcuts.go rename to cointop/default_shortcuts.go diff --git a/cointop/dominance.go b/cointop/dominance.go index fb2ded4..e400936 100644 --- a/cointop/dominance.go +++ b/cointop/dominance.go @@ -3,7 +3,7 @@ package cointop import ( "fmt" - "github.com/miguelmota/cointop/cointop/common/api" + "github.com/miguelmota/cointop/pkg/api" ) // DominanceConfig is the config options for the dominance command diff --git a/cointop/help.go b/cointop/help.go index 835d2f3..cece452 100644 --- a/cointop/help.go +++ b/cointop/help.go @@ -4,17 +4,17 @@ import ( "fmt" "sort" - "github.com/miguelmota/cointop/cointop/common/pad" + "github.com/miguelmota/cointop/pkg/pad" + "github.com/miguelmota/cointop/pkg/ui" ) // HelpView is structure for help view -type HelpView struct { - *View -} +type HelpView = ui.View // NewHelpView returns a new help view func NewHelpView() *HelpView { - return &HelpView{NewView("help")} + var view *HelpView = ui.NewView("help") + return view } // UpdateHelp updates the help views @@ -60,15 +60,9 @@ func (ct *Cointop) UpdateHelp() { infoLine := "See git.io/cointop for more info.\n Press ESC to return." content := fmt.Sprintf("%s %s\n %s\n\n %s\n\n%s\n %s", header, versionLine, licenseLine, instructionsLine, body, infoLine) - ct.Update(func() error { - if ct.Views.Help.Backing() == nil { - return nil - } - - ct.Views.Help.Backing().Clear() - ct.Views.Help.Backing().Frame = true - fmt.Fprintln(ct.Views.Help.Backing(), content) - return nil + ct.UpdateUI(func() error { + ct.Views.Help.SetFrame(true) + return ct.Views.Help.Update(content) }) } @@ -85,17 +79,11 @@ func (ct *Cointop) ShowHelp() error { func (ct *Cointop) HideHelp() error { ct.debuglog("hideHelp()") ct.State.helpVisible = false - ct.SetViewOnBottom(ct.Views.Help.Name()) + ct.ui.SetViewOnBottom(ct.Views.Help) ct.SetActiveView(ct.Views.Table.Name()) - ct.Update(func() error { - if ct.Views.Help.Backing() == nil { - return nil - } - - ct.Views.Help.Backing().Clear() - ct.Views.Help.Backing().Frame = false - fmt.Fprintln(ct.Views.Help.Backing(), "") - return nil + ct.UpdateUI(func() error { + ct.Views.Help.SetFrame(false) + return ct.Views.Help.Update("") }) return nil } diff --git a/cointop/layout.go b/cointop/layout.go index d8c5c51..d4b72c2 100644 --- a/cointop/layout.go +++ b/cointop/layout.go @@ -3,8 +3,6 @@ package cointop import ( "fmt" "strings" - - "github.com/miguelmota/gocui" ) // TODO: break up into small functions @@ -12,7 +10,7 @@ import ( var lastWidth int // layout sets initial layout -func (ct *Cointop) layout(g *gocui.Gui) error { +func (ct *Cointop) layout() error { ct.debuglog("layout()") maxY := ct.height() maxX := ct.ClampedWidth() @@ -43,13 +41,10 @@ func (ct *Cointop) layout(g *gocui.Gui) error { } if !ct.State.hideMarketbar { - if v, err := g.SetView(ct.Views.Marketbar.Name(), 0, topOffset, maxX, 2); err != nil { - if err != gocui.ErrUnknownView { - return err - } - ct.Views.Marketbar.SetBacking(v) - ct.Views.Marketbar.Backing().Frame = false - ct.colorscheme.SetViewColor(ct.Views.Marketbar.Backing(), "marketbar") + if err := ct.ui.SetView(ct.Views.Marketbar, 0, topOffset, maxX, 2); err != nil { + ct.Views.Marketbar.SetFrame(false) + ct.Views.Marketbar.SetFgColor(ct.colorscheme.gocuiFgColor(ct.Views.Marketbar.Name())) + ct.Views.Marketbar.SetBgColor(ct.colorscheme.gocuiBgColor(ct.Views.Marketbar.Name())) go func() { ct.UpdateMarketbar() _, found := ct.cache.Get(ct.Views.Marketbar.Name()) @@ -61,7 +56,7 @@ func (ct *Cointop) layout(g *gocui.Gui) error { } } else { if ct.Views.Marketbar.Backing() != nil { - if err := g.DeleteView(ct.Views.Marketbar.Name()); err != nil { + if err := ct.g.DeleteView(ct.Views.Marketbar.Name()); err != nil { return err } ct.Views.Marketbar.SetBacking(nil) @@ -71,14 +66,11 @@ func (ct *Cointop) layout(g *gocui.Gui) error { topOffset = topOffset + marketbarHeight if !ct.State.hideChart { - if v, err := g.SetView(ct.Views.Chart.Name(), 0, topOffset, maxX, topOffset+chartHeight+marketbarHeight); err != nil { - if err != gocui.ErrUnknownView { - return err - } - v.Clear() - ct.Views.Chart.SetBacking(v) - ct.Views.Chart.Backing().Frame = false - ct.colorscheme.SetViewColor(ct.Views.Chart.Backing(), "chart") + if err := ct.ui.SetView(ct.Views.Chart, 0, topOffset, maxX, topOffset+chartHeight+marketbarHeight); err != nil { + ct.Views.Chart.Clear() + ct.Views.Chart.SetFrame(false) + ct.Views.Chart.SetFgColor(ct.colorscheme.gocuiFgColor(ct.Views.Chart.Name())) + ct.Views.Chart.SetBgColor(ct.colorscheme.gocuiBgColor(ct.Views.Chart.Name())) go func() { ct.UpdateChart() cachekey := strings.ToLower(fmt.Sprintf("%s_%s", "globaldata", strings.Replace(ct.State.selectedChartRange, " ", "", -1))) @@ -91,7 +83,7 @@ func (ct *Cointop) layout(g *gocui.Gui) error { } } else { if ct.Views.Chart.Backing() != nil { - if err := g.DeleteView(ct.Views.Chart.Name()); err != nil { + if err := ct.g.DeleteView(ct.Views.Chart.Name()); err != nil { return err } ct.Views.Chart.SetBacking(nil) @@ -99,25 +91,19 @@ func (ct *Cointop) layout(g *gocui.Gui) error { } topOffset = topOffset + chartHeight - if v, err := g.SetView(ct.Views.TableHeader.Name(), 0, topOffset, ct.maxTableWidth, topOffset+2); err != nil { - if err != gocui.ErrUnknownView { - return err - } - ct.Views.TableHeader.SetBacking(v) - ct.Views.TableHeader.Backing().Frame = false - ct.colorscheme.SetViewColor(ct.Views.TableHeader.Backing(), "table_header") + if err := ct.ui.SetView(ct.Views.TableHeader, 0, topOffset, ct.maxTableWidth, topOffset+2); err != nil { + ct.Views.TableHeader.SetFrame(false) + ct.Views.TableHeader.SetFgColor(ct.colorscheme.gocuiFgColor(ct.Views.TableHeader.Name())) + ct.Views.TableHeader.SetBgColor(ct.colorscheme.gocuiBgColor(ct.Views.TableHeader.Name())) go ct.UpdateTableHeader() } topOffset = topOffset + headerHeight - if v, err := g.SetView(ct.Views.Table.Name(), 0, topOffset, ct.maxTableWidth, maxY-statusbarHeight); err != nil { - if err != gocui.ErrUnknownView { - return err - } - ct.Views.Table.SetBacking(v) - ct.Views.Table.Backing().Frame = false - ct.Views.Table.Backing().Highlight = true - ct.colorscheme.SetViewActiveColor(ct.Views.Table.Backing(), "table_row_active") + if err := ct.ui.SetView(ct.Views.Table, 0, topOffset, ct.maxTableWidth, maxY-statusbarHeight); err != nil { + ct.Views.Table.SetFrame(false) + ct.Views.Table.SetHighlight(true) + ct.Views.Table.SetSelFgColor(ct.colorscheme.gocuiFgColor("table_row_active")) + ct.Views.Table.SetSelBgColor(ct.colorscheme.gocuiBgColor("table_row_active")) _, found := ct.cache.Get("allCoinsSlugMap") if found { ct.cache.Delete("allCoinsSlugMap") @@ -129,80 +115,61 @@ func (ct *Cointop) layout(g *gocui.Gui) error { } if !ct.State.hideStatusbar { - if v, err := g.SetView(ct.Views.Statusbar.Name(), 0, maxY-statusbarHeight-1, ct.maxTableWidth, maxY); err != nil { - if err != gocui.ErrUnknownView { - return err - } - ct.Views.Statusbar.SetBacking(v) - ct.Views.Statusbar.Backing().Frame = false - ct.colorscheme.SetViewColor(ct.Views.Statusbar.Backing(), "statusbar") + if err := ct.ui.SetView(ct.Views.Statusbar, 0, maxY-statusbarHeight-1, ct.maxTableWidth, maxY); err != nil { + ct.Views.Statusbar.SetFrame(false) + ct.Views.Statusbar.SetFgColor(ct.colorscheme.gocuiFgColor(ct.Views.Statusbar.Name())) + ct.Views.Statusbar.SetBgColor(ct.colorscheme.gocuiBgColor(ct.Views.Statusbar.Name())) go ct.UpdateStatusbar("") } } else { if ct.Views.Statusbar.Backing() != nil { - if err := g.DeleteView(ct.Views.Statusbar.Name()); err != nil { + if err := ct.g.DeleteView(ct.Views.Statusbar.Name()); err != nil { return err } ct.Views.Statusbar.SetBacking(nil) } } - if v, err := g.SetView(ct.Views.SearchField.Name(), 0, maxY-2, ct.maxTableWidth, maxY); err != nil { - if err != gocui.ErrUnknownView { - return err - } - ct.Views.SearchField.SetBacking(v) - ct.Views.SearchField.Backing().Editable = true - ct.Views.SearchField.Backing().Wrap = true - ct.Views.SearchField.Backing().Frame = false - ct.colorscheme.SetViewColor(ct.Views.SearchField.Backing(), "searchbar") + if err := ct.ui.SetView(ct.Views.SearchField, 0, maxY-2, ct.maxTableWidth, maxY); err != nil { + ct.Views.SearchField.SetEditable(true) + ct.Views.SearchField.SetWrap(true) + ct.Views.SearchField.SetFrame(false) + ct.Views.SearchField.SetFgColor(ct.colorscheme.gocuiFgColor("searchbar")) + ct.Views.SearchField.SetBgColor(ct.colorscheme.gocuiBgColor("searchbar")) } - if v, err := g.SetView(ct.Views.Help.Name(), 1, 1, ct.maxTableWidth-1, maxY-1); err != nil { - if err != gocui.ErrUnknownView { - return err - } - ct.Views.Help.SetBacking(v) - ct.Views.Help.Backing().Frame = false - ct.colorscheme.SetViewColor(ct.Views.Help.Backing(), "menu") + if err := ct.ui.SetView(ct.Views.Help, 1, 1, ct.maxTableWidth-1, maxY-1); err != nil { + ct.Views.Help.SetFrame(false) + ct.Views.Help.SetFgColor(ct.colorscheme.gocuiFgColor("menu")) + ct.Views.Help.SetBgColor(ct.colorscheme.gocuiBgColor("menu")) } - if v, err := g.SetView(ct.Views.PortfolioUpdateMenu.Name(), 1, 1, ct.maxTableWidth-1, maxY-1); err != nil { - if err != gocui.ErrUnknownView { - return err - } - ct.Views.PortfolioUpdateMenu.SetBacking(v) - ct.Views.PortfolioUpdateMenu.Backing().Frame = false - ct.colorscheme.SetViewColor(ct.Views.PortfolioUpdateMenu.Backing(), "menu") + if err := ct.ui.SetView(ct.Views.PortfolioUpdateMenu, 1, 1, ct.maxTableWidth-1, maxY-1); err != nil { + ct.Views.PortfolioUpdateMenu.SetFrame(false) + ct.Views.PortfolioUpdateMenu.SetFgColor(ct.colorscheme.gocuiFgColor("menu")) + ct.Views.PortfolioUpdateMenu.SetBgColor(ct.colorscheme.gocuiBgColor("menu")) } - if v, err := g.SetView(ct.Views.Input.Name(), 3, 6, 30, 8); err != nil { - if err != gocui.ErrUnknownView { - return err - } - ct.Views.Input.SetBacking(v) - ct.Views.Input.Backing().Frame = true - ct.Views.Input.Backing().Editable = true - ct.Views.Input.Backing().Wrap = true - ct.colorscheme.SetViewColor(ct.Views.Input.Backing(), "menu") + if err := ct.ui.SetView(ct.Views.Input, 3, 6, 30, 8); err != nil { + ct.Views.Input.SetFrame(true) + ct.Views.Input.SetEditable(true) + ct.Views.Input.SetWrap(true) + ct.Views.Input.SetFgColor(ct.colorscheme.gocuiFgColor("menu")) + ct.Views.Input.SetBgColor(ct.colorscheme.gocuiBgColor("menu")) } - if v, err := g.SetView(ct.Views.ConvertMenu.Name(), 1, 1, ct.maxTableWidth-1, maxY-1); err != nil { - if err != gocui.ErrUnknownView { - return err - } - ct.Views.ConvertMenu.SetBacking(v) - ct.Views.ConvertMenu.Backing().Frame = false - ct.colorscheme.SetViewColor(ct.Views.ConvertMenu.Backing(), "menu") + if err := ct.ui.SetView(ct.Views.ConvertMenu, 1, 1, ct.maxTableWidth-1, maxY-1); err != nil { + ct.Views.ConvertMenu.SetFrame(false) + ct.Views.ConvertMenu.SetFgColor(ct.colorscheme.gocuiFgColor("menu")) + ct.Views.ConvertMenu.SetBgColor(ct.colorscheme.gocuiBgColor("menu")) // run only once on init. // this bit of code should be at the bottom - ct.g = g - g.SetViewOnBottom(ct.Views.SearchField.Name()) // hide - g.SetViewOnBottom(ct.Views.Help.Name()) // hide - g.SetViewOnBottom(ct.Views.ConvertMenu.Name()) // hide - g.SetViewOnBottom(ct.Views.PortfolioUpdateMenu.Name()) // hide - g.SetViewOnBottom(ct.Views.Input.Name()) // hide + ct.ui.SetViewOnBottom(ct.Views.SearchField) // hide + ct.ui.SetViewOnBottom(ct.Views.Help) // hide + ct.ui.SetViewOnBottom(ct.Views.ConvertMenu) // hide + ct.ui.SetViewOnBottom(ct.Views.PortfolioUpdateMenu) // hide + ct.ui.SetViewOnBottom(ct.Views.Input) // hide ct.SetActiveView(ct.Views.Table.Name()) ct.intervalFetchData() } diff --git a/cointop/list.go b/cointop/list.go index 30679ed..5fa4395 100644 --- a/cointop/list.go +++ b/cointop/list.go @@ -4,7 +4,7 @@ import ( "sync" "time" - types "github.com/miguelmota/cointop/cointop/common/api/types" + types "github.com/miguelmota/cointop/pkg/api/types" ) var coinslock sync.Mutex diff --git a/cointop/marketbar.go b/cointop/marketbar.go index 9b0f94f..28c73e7 100644 --- a/cointop/marketbar.go +++ b/cointop/marketbar.go @@ -5,29 +5,25 @@ import ( "math" "time" - types "github.com/miguelmota/cointop/cointop/common/api/types" - "github.com/miguelmota/cointop/cointop/common/color" - "github.com/miguelmota/cointop/cointop/common/humanize" - "github.com/miguelmota/cointop/cointop/common/pad" + types "github.com/miguelmota/cointop/pkg/api/types" + "github.com/miguelmota/cointop/pkg/color" + "github.com/miguelmota/cointop/pkg/humanize" + "github.com/miguelmota/cointop/pkg/pad" + "github.com/miguelmota/cointop/pkg/ui" ) // MarketbarView is structure for marketbar view -type MarketbarView struct { - *View -} +type MarketbarView = ui.View // NewMarketbarView returns a new marketbar view func NewMarketbarView() *MarketbarView { - return &MarketbarView{NewView("marketbar")} + var view *MarketbarView = ui.NewView("marketbar") + return view } // UpdateMarketbar updates the market bar view func (ct *Cointop) UpdateMarketbar() error { ct.debuglog("updateMarketbar()") - if ct.Views.Marketbar.Backing() == nil { - return nil - } - maxX := ct.width() logo := "❯❯❯cointop" if ct.colorschemeName == "cointop" { @@ -147,14 +143,8 @@ func (ct *Cointop) UpdateMarketbar() error { content = pad.Right(content, maxX, " ") content = ct.colorscheme.Marketbar(content) - ct.Update(func() error { - if ct.Views.Marketbar.Backing() == nil { - return nil - } - - ct.Views.Marketbar.Backing().Clear() - fmt.Fprintln(ct.Views.Marketbar.Backing(), content) - return nil + ct.UpdateUI(func() error { + return ct.Views.Marketbar.Update(content) }) return nil diff --git a/cointop/navigation.go b/cointop/navigation.go index 6586921..0fb5b0e 100644 --- a/cointop/navigation.go +++ b/cointop/navigation.go @@ -41,21 +41,16 @@ func (ct *Cointop) SetPage(page int) int { // CursorDown moves the cursor one row down func (ct *Cointop) CursorDown() error { ct.debuglog("cursorDown()") - if ct.Views.Table.Backing() == nil { - return nil - } - // 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() + cx, cy := ct.Views.Table.Cursor() + if err := ct.Views.Table.SetCursor(cx, cy+1); err != nil { + ox, oy := ct.Views.Table.Origin() // set origin scrolls - if err := ct.Views.Table.Backing().SetOrigin(ox, oy+1); err != nil { + if err := ct.Views.Table.SetOrigin(ox, oy+1); err != nil { return err } } @@ -66,21 +61,17 @@ func (ct *Cointop) CursorDown() error { // CursorUp moves the cursor one row up 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() + ox, oy := ct.Views.Table.Origin() + cx, cy := ct.Views.Table.Cursor() - if err := ct.Views.Table.Backing().SetCursor(cx, cy-1); err != nil && oy > 0 { + if err := ct.Views.Table.SetCursor(cx, cy-1); err != nil && oy > 0 { // set origin scrolls - if err := ct.Views.Table.Backing().SetOrigin(ox, oy-1); err != nil { + if err := ct.Views.Table.SetOrigin(ox, oy-1); err != nil { return err } } @@ -91,18 +82,14 @@ func (ct *Cointop) CursorUp() error { // PageDown moves the cursor one page down 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 + ox, oy := ct.Views.Table.Origin() // this is prev origin position + cx := ct.Views.Table.CursorX() // relative cursor position + sy := ct.Views.Table.Height() // rows in visible view k := oy + sy l := len(ct.State.coins) // end of table @@ -115,12 +102,13 @@ func (ct *Cointop) PageDown() error { sy = l } - if err := ct.Views.Table.Backing().SetOrigin(ox, k); err != nil { + if err := ct.Views.Table.SetOrigin(ox, k); err != nil { return err } + // move cursor to last line if can't scroll further if k == oy { - if err := ct.Views.Table.Backing().SetCursor(cx, sy-1); err != nil { + if err := ct.Views.Table.SetCursor(cx, sy-1); err != nil { return err } } @@ -131,28 +119,24 @@ func (ct *Cointop) PageDown() error { // PageUp moves the cursor one page up 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 + ox, oy := ct.Views.Table.Origin() + cx := ct.Views.Table.CursorX() // relative cursor position + sy := ct.Views.Table.Height() // rows in visible view k := oy - sy if k < 0 { k = 0 } - if err := ct.Views.Table.Backing().SetOrigin(ox, k); err != nil { + if err := ct.Views.Table.SetOrigin(ox, k); err != nil { return err } // move cursor to first line if can't scroll further if k == oy { - if err := ct.Views.Table.Backing().SetCursor(cx, 0); err != nil { + if err := ct.Views.Table.SetCursor(cx, 0); err != nil { return err } } @@ -163,21 +147,17 @@ func (ct *Cointop) PageUp() error { // NavigateFirstLine moves the cursor to the first row of the table 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 { + ox := ct.Views.Table.OriginX() + cx := ct.Views.Table.CursorX() + if err := ct.Views.Table.SetOrigin(ox, 0); err != nil { return err } - if err := ct.Views.Table.Backing().SetCursor(cx, 0); err != nil { + if err := ct.Views.Table.SetCursor(cx, 0); err != nil { return err } @@ -188,24 +168,20 @@ func (ct *Cointop) NavigateFirstLine() error { // NavigateLastLine moves the cursor to the last row of the table 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() + ox := ct.Views.Table.OriginX() + cx := ct.Views.Table.CursorX() + sy := ct.Views.Table.Height() l := len(ct.State.coins) k := l - sy - if err := ct.Views.Table.Backing().SetOrigin(ox, k); err != nil { + if err := ct.Views.Table.SetOrigin(ox, k); err != nil { return err } - if err := ct.Views.Table.Backing().SetCursor(cx, sy-1); err != nil { + if err := ct.Views.Table.SetCursor(cx, sy-1); err != nil { return err } @@ -216,17 +192,13 @@ func (ct *Cointop) NavigateLastLine() error { // NavigatePageFirstLine moves the cursor to the visible first row of the table func (ct *Cointop) NavigatePageFirstLine() error { ct.debuglog("navigatePageFirstLine()") - 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 { + cx := ct.Views.Table.CursorX() + if err := ct.Views.Table.SetCursor(cx, 0); err != nil { return err } ct.RowChanged() @@ -236,18 +208,14 @@ func (ct *Cointop) NavigatePageFirstLine() error { // NavigatePageMiddleLine moves the cursor to the visible middle row of the table func (ct *Cointop) NavigatePageMiddleLine() error { ct.debuglog("navigatePageMiddleLine()") - 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 { + cx := ct.Views.Table.CursorX() + sy := ct.Views.Table.Height() + if err := ct.Views.Table.SetCursor(cx, (sy/2)-1); err != nil { return err } ct.RowChanged() @@ -257,18 +225,14 @@ func (ct *Cointop) NavigatePageMiddleLine() error { // NavigatePageLastLine moves the cursor to the visible last row of the table func (ct *Cointop) navigatePageLastLine() error { ct.debuglog("navigatePageLastLine()") - 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 { + cx, _ := ct.Views.Table.Cursor() + sy := ct.Views.Table.Height() + if err := ct.Views.Table.SetCursor(cx, sy-1); err != nil { return err } ct.RowChanged() @@ -358,28 +322,20 @@ func (ct *Cointop) LastPage() error { // IsFirstRow returns true if cursor is on first row func (ct *Cointop) IsFirstRow() bool { ct.debuglog("isFirstRow()") - if ct.Views.Table.Backing() == nil { - return false - } - - _, y := ct.Views.Table.Backing().Origin() - _, cy := ct.Views.Table.Backing().Cursor() + oy := ct.Views.Table.OriginY() + cy := ct.Views.Table.CursorY() - return (cy + y) == 0 + return (cy + oy) == 0 } // IsLastRow returns true if cursor is on last row func (ct *Cointop) IsLastRow() bool { ct.debuglog("isLastRow()") - if ct.Views.Table.Backing() == nil { - return false - } - - _, y := ct.Views.Table.Backing().Origin() - _, cy := ct.Views.Table.Backing().Cursor() + oy := ct.Views.Table.OriginY() + cy := ct.Views.Table.CursorY() numRows := len(ct.State.coins) - 1 - return (cy + y + 1) > numRows + return (cy + oy + 1) > numRows } // IsFirstPage returns true if cursor is on the first page @@ -397,23 +353,16 @@ func (ct *Cointop) IsLastPage() bool { // IsPageFirstLine returns true if the cursor is on the visible first row func (ct *Cointop) IsPageFirstLine() bool { ct.debuglog("isPageFirstLine()") - if ct.Views.Table.Backing() == nil { - return false - } - _, cy := ct.Views.Table.Backing().Cursor() + cy := ct.Views.Table.CursorY() return cy == 0 } // IsPageMiddleLine returns true if the cursor is on the visible middle row func (ct *Cointop) IsPageMiddleLine() bool { ct.debuglog("isPageMiddleLine()") - if ct.Views.Table.Backing() == nil { - return false - } - - _, cy := ct.Views.Table.Backing().Cursor() - _, sy := ct.Views.Table.Backing().Size() + cy := ct.Views.Table.CursorY() + sy := ct.Views.Table.Height() return (sy/2)-1 == cy } @@ -421,19 +370,16 @@ func (ct *Cointop) IsPageMiddleLine() bool { func (ct *Cointop) IsPageLastLine() bool { ct.debuglog("isPageLastLine()") - _, cy := ct.Views.Table.Backing().Cursor() - _, sy := ct.Views.Table.Backing().Size() + cy := ct.Views.Table.CursorY() + sy := ct.Views.Table.Height() return cy+1 == sy } // GoToPageRowIndex navigates to the selected row index of the page func (ct *Cointop) GoToPageRowIndex(idx int) error { ct.debuglog("goToPageRowIndex()") - if ct.Views.Table.Backing() == nil { - return nil - } - cx, _ := ct.Views.Table.Backing().Cursor() - if err := ct.Views.Table.Backing().SetCursor(cx, idx); err != nil { + cx := ct.Views.Table.CursorX() + if err := ct.Views.Table.SetCursor(cx, idx); err != nil { return err } ct.RowChanged() @@ -455,23 +401,19 @@ func (ct *Cointop) GoToGlobalIndex(idx int) error { // HighlightRow highlights the row at index func (ct *Cointop) HighlightRow(idx int) error { ct.debuglog("highlightRow()") - if ct.Views.Table.Backing() == nil { - return nil - } - - ct.Views.Table.Backing().SetOrigin(0, 0) - ct.Views.Table.Backing().SetCursor(0, 0) - ox, _ := ct.Views.Table.Backing().Origin() - cx, _ := ct.Views.Table.Backing().Cursor() - _, sy := ct.Views.Table.Backing().Size() + ct.Views.Table.SetOrigin(0, 0) + ct.Views.Table.SetCursor(0, 0) + ox := ct.Views.Table.OriginX() + cx := ct.Views.Table.CursorX() + sy := ct.Views.Table.Height() perpage := ct.TotalPerPage() p := idx % perpage oy := (p / sy) * sy cy := p % sy if oy > 0 { - ct.Views.Table.Backing().SetOrigin(ox, oy) + ct.Views.Table.SetOrigin(ox, oy) } - ct.Views.Table.Backing().SetCursor(cx, cy) + ct.Views.Table.SetCursor(cx, cy) return nil } diff --git a/cointop/portfolio.go b/cointop/portfolio.go index 410fd66..ddbcdc3 100644 --- a/cointop/portfolio.go +++ b/cointop/portfolio.go @@ -11,19 +11,19 @@ import ( "strconv" "strings" - "github.com/miguelmota/cointop/cointop/common/asciitable" - "github.com/miguelmota/cointop/cointop/common/humanize" - "github.com/miguelmota/cointop/cointop/common/pad" + "github.com/miguelmota/cointop/pkg/asciitable" + "github.com/miguelmota/cointop/pkg/humanize" + "github.com/miguelmota/cointop/pkg/pad" + "github.com/miguelmota/cointop/pkg/ui" ) // PortfolioUpdateMenuView is structure for portfolio update menu view -type PortfolioUpdateMenuView struct { - *View -} +type PortfolioUpdateMenuView = ui.View // NewPortfolioUpdateMenuView returns a new portfolio update menu view func NewPortfolioUpdateMenuView() *PortfolioUpdateMenuView { - return &PortfolioUpdateMenuView{NewView("portfolioupdatemenu")} + var view *PortfolioUpdateMenuView = ui.NewView("portfolioupdatemenu") + return view } // TogglePortfolio toggles the portfolio view @@ -71,7 +71,7 @@ func (ct *Cointop) CoinHoldings(coin *Coin) float64 { } // UpdatePortfolioUpdateMenu updates the portfolio update menu view -func (ct *Cointop) UpdatePortfolioUpdateMenu() { +func (ct *Cointop) UpdatePortfolioUpdateMenu() error { ct.debuglog("updatePortfolioUpdateMenu()") coin := ct.HighlightedRowCoin() exists := ct.PortfolioEntryExists(coin) @@ -92,14 +92,14 @@ func (ct *Cointop) UpdatePortfolioUpdateMenu() { label := fmt.Sprintf(" Enter holdings for %s %s", ct.colorscheme.MenuLabel(coin.Name), current) content := fmt.Sprintf("%s\n%s\n\n%s%s\n\n\n [Enter] %s [ESC] Cancel", header, label, strings.Repeat(" ", 29), coin.Symbol, submitText) - ct.Update(func() error { - ct.Views.PortfolioUpdateMenu.Backing().Clear() - ct.Views.PortfolioUpdateMenu.Backing().Frame = true - fmt.Fprintln(ct.Views.PortfolioUpdateMenu.Backing(), content) - fmt.Fprintln(ct.Views.Input.Backing(), value) - ct.Views.Input.Backing().SetCursor(len(value), 0) + ct.UpdateUI(func() error { + ct.Views.PortfolioUpdateMenu.SetFrame(true) + ct.Views.PortfolioUpdateMenu.Update(content) + ct.Views.Input.Write(value) + ct.Views.Input.SetCursor(len(value), 0) return nil }) + return nil } // ShowPortfolioUpdateMenu shows the portfolio update menu @@ -122,20 +122,13 @@ 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()) + ct.ui.SetViewOnBottom(ct.Views.PortfolioUpdateMenu) + ct.ui.SetViewOnBottom(ct.Views.Input) ct.SetActiveView(ct.Views.Table.Name()) - ct.Update(func() error { - if ct.Views.PortfolioUpdateMenu.Backing() == nil { - return nil - } - - ct.Views.PortfolioUpdateMenu.Backing().Clear() - ct.Views.PortfolioUpdateMenu.Backing().Frame = false - fmt.Fprintln(ct.Views.PortfolioUpdateMenu.Backing(), "") - - ct.Views.Input.Backing().Clear() - fmt.Fprintln(ct.Views.Input.Backing(), "") + ct.UpdateUI(func() error { + ct.Views.PortfolioUpdateMenu.SetFrame(false) + ct.Views.PortfolioUpdateMenu.Update("") + ct.Views.Input.Update("") return nil }) @@ -150,7 +143,7 @@ func (ct *Cointop) SetPortfolioHoldings() error { // read input field b := make([]byte, 100) - n, err := ct.Views.Input.Backing().Read(b) + n, err := ct.Views.Input.Read(b) if err != nil { return err } diff --git a/cointop/price.go b/cointop/price.go index b02595b..c600ef1 100644 --- a/cointop/price.go +++ b/cointop/price.go @@ -3,8 +3,8 @@ package cointop import ( "fmt" - "github.com/miguelmota/cointop/cointop/common/api" - "github.com/miguelmota/cointop/cointop/common/humanize" + "github.com/miguelmota/cointop/pkg/api" + "github.com/miguelmota/cointop/pkg/humanize" ) // PriceConfig is the config options for the price command diff --git a/cointop/quit.go b/cointop/quit.go index b014735..dab2fa9 100644 --- a/cointop/quit.go +++ b/cointop/quit.go @@ -22,7 +22,7 @@ func (ct *Cointop) QuitView() error { ct.State.filterByFavorites = false return ct.UpdateTable() } - if ct.ActiveViewName() == ct.Views.Table.Name() { + if ct.ui.ActiveViewName() == ct.Views.Table.Name() { return ct.Quit() } diff --git a/cointop/search.go b/cointop/search.go index b8463f2..89dccad 100644 --- a/cointop/search.go +++ b/cointop/search.go @@ -4,27 +4,26 @@ import ( "regexp" "strings" - "github.com/miguelmota/cointop/cointop/common/levenshtein" + "github.com/miguelmota/cointop/pkg/levenshtein" + "github.com/miguelmota/cointop/pkg/ui" ) // SearchFieldView is structure for search field view -type SearchFieldView struct { - *View -} +type SearchFieldView = ui.View // NewSearchFieldView returns a new search field view func NewSearchFieldView() *SearchFieldView { - return &SearchFieldView{NewView("searchfield")} + var view *SearchFieldView = ui.NewView("searchfield") + return view } // InputView is structure for help view -type InputView struct { - *View -} +type InputView = ui.View // NewInputView returns a new help view func NewInputView() *InputView { - return &InputView{NewView("input")} + var view *InputView = ui.NewView("input") + return view } // OpenSearch opens the search field @@ -46,9 +45,12 @@ func (ct *Cointop) CancelSearch() error { // DoSearch triggers the search and sets views func (ct *Cointop) DoSearch() error { ct.debuglog("doSearch()") - ct.Views.SearchField.Backing().Rewind() + ct.Views.SearchField.Rewind() b := make([]byte, 100) - n, err := ct.Views.SearchField.Backing().Read(b) + n, err := ct.Views.SearchField.Read(b) + if err != nil { + return err + } // TODO: do this a better way (SoC) ct.State.filterByFavorites = false diff --git a/cointop/statusbar.go b/cointop/statusbar.go index da6893f..7cb613a 100644 --- a/cointop/statusbar.go +++ b/cointop/statusbar.go @@ -3,30 +3,18 @@ package cointop import ( "fmt" - "github.com/miguelmota/cointop/cointop/common/open" - "github.com/miguelmota/cointop/cointop/common/pad" + "github.com/miguelmota/cointop/pkg/open" + "github.com/miguelmota/cointop/pkg/pad" + "github.com/miguelmota/cointop/pkg/ui" ) // StatusbarView is structure for statusbar view -type StatusbarView struct { - *View -} +type StatusbarView = ui.View // NewStatusbarView returns a new statusbar view func NewStatusbarView() *StatusbarView { - return &StatusbarView{NewView("statusbar")} -} - -// Update updates the content of the statusbar -func (statusbar *StatusbarView) Update(str string) error { - if statusbar.Backing() == nil { - return nil - } - - statusbar.Backing().Clear() - fmt.Fprintln(statusbar.Backing(), str) - - return nil + var view *StatusbarView = ui.NewView("statusbar") + return view } // UpdateStatusbar updates the statusbar view @@ -63,9 +51,8 @@ func (ct *Cointop) UpdateStatusbar(s string) error { str = str[:end] + v - ct.Update(func() error { - ct.Views.Statusbar.Update(str) - return nil + ct.UpdateUI(func() error { + return ct.Views.Statusbar.Update(str) }) return nil diff --git a/cointop/table.go b/cointop/table.go index f0451f7..6f6ef2d 100644 --- a/cointop/table.go +++ b/cointop/table.go @@ -8,19 +8,19 @@ import ( "strings" "time" - "github.com/miguelmota/cointop/cointop/common/humanize" - "github.com/miguelmota/cointop/cointop/common/pad" - "github.com/miguelmota/cointop/cointop/common/table" + "github.com/miguelmota/cointop/pkg/humanize" + "github.com/miguelmota/cointop/pkg/pad" + "github.com/miguelmota/cointop/pkg/table" + "github.com/miguelmota/cointop/pkg/ui" ) // TableView is structure for table view -type TableView struct { - *View -} +type TableView = ui.View // NewTableView returns a new table view func NewTableView() *TableView { - return &TableView{NewView("table")} + var view *TableView = ui.NewView("table") + return view } // TableColumnOrder returns the default order of the table columns @@ -193,12 +193,8 @@ func (ct *Cointop) RefreshTable() error { ct.HighlightRow(currentrow) } - ct.Update(func() error { - if ct.Views.Table.Backing() == nil { - return nil - } - - ct.Views.Table.Backing().Clear() + ct.UpdateUI(func() error { + ct.Views.Table.Clear() ct.table.Format().Fprint(ct.Views.Table.Backing()) go ct.RowChanged() go ct.UpdateTableHeader() @@ -288,13 +284,9 @@ func (ct *Cointop) GetTableCoinsSlice() []*Coin { // HighlightedRowIndex returns the index of the highlighted row func (ct *Cointop) HighlightedRowIndex() int { ct.debuglog("HighlightedRowIndex()") - if ct.Views.Table.Backing() == nil { - return 0 - } - - _, y := ct.Views.Table.Backing().Origin() - _, cy := ct.Views.Table.Backing().Cursor() - idx := y + cy + oy := ct.Views.Table.OriginY() + cy := ct.Views.Table.CursorY() + idx := oy + cy if idx < 0 { idx = 0 } @@ -317,7 +309,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() + cy := ct.Views.Table.CursorY() idx := cy if idx < 0 { idx = 0 diff --git a/cointop/table_header.go b/cointop/table_header.go index 3c3d3d7..3659a7a 100644 --- a/cointop/table_header.go +++ b/cointop/table_header.go @@ -3,20 +3,21 @@ package cointop import ( "fmt" "strings" + + "github.com/miguelmota/cointop/pkg/ui" ) // TableHeaderView is structure for table header view -type TableHeaderView struct { - *View -} +type TableHeaderView = ui.View // NewTableHeaderView returns a new table header view func NewTableHeaderView() *TableHeaderView { - return &TableHeaderView{NewView("header")} + var view *TableHeaderView = ui.NewView("table_header") + return view } // UpdateTableHeader renders the table header -func (ct *Cointop) UpdateTableHeader() { +func (ct *Cointop) UpdateTableHeader() error { ct.debuglog("UpdateTableHeader()") var cols []string @@ -89,13 +90,9 @@ func (ct *Cointop) UpdateTableHeader() { headers = append(headers, str) } - ct.Update(func() error { - if ct.Views.TableHeader.Backing() == nil { - return nil - } - - ct.Views.TableHeader.Backing().Clear() - fmt.Fprintln(ct.Views.TableHeader.Backing(), strings.Join(headers, "")) - return nil + ct.UpdateUI(func() error { + return ct.Views.TableHeader.Update(strings.Join(headers, "")) }) + + return nil } diff --git a/cointop/update.go b/cointop/update.go index db7f7c4..9b08b11 100644 --- a/cointop/update.go +++ b/cointop/update.go @@ -6,9 +6,9 @@ import ( "github.com/miguelmota/gocui" ) -// Update takes a callback which updates the view -func (ct *Cointop) Update(f func() error) { - ct.debuglog(fmt.Sprintf("Update()")) +// UpdateUI takes a callback which updates the view +func (ct *Cointop) UpdateUI(f func() error) { + ct.debuglog(fmt.Sprintf("UpdateUI()")) if ct.g == nil { return diff --git a/cointop/util.go b/cointop/util.go index 33506b3..3b06774 100644 --- a/cointop/util.go +++ b/cointop/util.go @@ -5,7 +5,7 @@ import ( "encoding/gob" "strings" - "github.com/miguelmota/cointop/cointop/common/open" + "github.com/miguelmota/cointop/pkg/open" ) // OpenLink opens the url in a browser diff --git a/cointop/view.go b/cointop/view.go deleted file mode 100644 index cbd33ae..0000000 --- a/cointop/view.go +++ /dev/null @@ -1,83 +0,0 @@ -package cointop - -import ( - "fmt" - - "github.com/miguelmota/gocui" -) - -// IView is a cointop view -type IView interface { - Backing() *gocui.View - SetBacking(gocuiView *gocui.View) - Name() string -} - -// View is a cointop view -type View struct { - backing *gocui.View - name string -} - -// NewView creates a new view -func NewView(name string) *View { - return &View{ - name: name, - } -} - -// Backing returns the backing gocui view -func (view *View) Backing() *gocui.View { - return view.backing -} - -// SetBacking sets the backing gocui view -func (view *View) SetBacking(gocuiView *gocui.View) { - view.backing = gocuiView -} - -// Height returns the view height -func (view *View) Height() int { - _, h := view.backing.Size() - return h -} - -// Width returns the view width -func (view *View) Width() int { - w, _ := view.backing.Size() - return w -} - -// Name returns the view's name -func (view *View) Name() string { - return view.name -} - -// SetActiveView sets the active view -func (ct *Cointop) SetActiveView(v string) error { - ct.g.SetViewOnTop(v) - ct.g.SetCurrentView(v) - if v == ct.Views.SearchField.Name() { - ct.Views.SearchField.Backing().Clear() - ct.Views.SearchField.Backing().SetCursor(1, 0) - fmt.Fprintf(ct.Views.SearchField.Backing(), "%s", "/") - } else if v == ct.Views.Table.Name() { - ct.g.SetViewOnTop(ct.Views.Statusbar.Name()) - } - if v == ct.Views.PortfolioUpdateMenu.Name() { - ct.g.SetViewOnTop(ct.Views.Input.Name()) - ct.g.SetCurrentView(ct.Views.Input.Name()) - } - return nil -} - -// ActiveViewName returns the name of the active view -func (ct *Cointop) ActiveViewName() string { - return ct.g.CurrentView().Name() -} - -// SetViewOnBottom sets the view to the bottom layer -func (ct *Cointop) SetViewOnBottom(v string) error { - _, err := ct.g.SetViewOnBottom(v) - return err -} diff --git a/main.go b/main.go index 4466657..bca7b13 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,7 @@ package main import ( - "github.com/miguelmota/cointop/cointop/cmd" + cmd "github.com/miguelmota/cointop/cmd/commands" ) func main() { diff --git a/cointop/common/api/api.go b/pkg/api/api.go similarity index 64% rename from cointop/common/api/api.go rename to pkg/api/api.go index 55bc781..b045efd 100644 --- a/cointop/common/api/api.go +++ b/pkg/api/api.go @@ -1,8 +1,8 @@ package api import ( - cg "github.com/miguelmota/cointop/cointop/common/api/impl/coingecko" - cmc "github.com/miguelmota/cointop/cointop/common/api/impl/coinmarketcap" + cg "github.com/miguelmota/cointop/pkg/api/impl/coingecko" + cmc "github.com/miguelmota/cointop/pkg/api/impl/coinmarketcap" ) // NewCMC new CoinMarketCap API diff --git a/cointop/common/api/impl/coingecko/coingecko.go b/pkg/api/impl/coingecko/coingecko.go similarity index 96% rename from cointop/common/api/impl/coingecko/coingecko.go rename to pkg/api/impl/coingecko/coingecko.go index c0d9223..4aafe43 100644 --- a/cointop/common/api/impl/coingecko/coingecko.go +++ b/pkg/api/impl/coingecko/coingecko.go @@ -7,10 +7,10 @@ import ( "strings" "time" - gecko "github.com/miguelmota/cointop/cointop/api/coingecko/v3" - geckoTypes "github.com/miguelmota/cointop/cointop/api/coingecko/v3/types" - apitypes "github.com/miguelmota/cointop/cointop/common/api/types" - util "github.com/miguelmota/cointop/cointop/common/api/util" + apitypes "github.com/miguelmota/cointop/pkg/api/types" + util "github.com/miguelmota/cointop/pkg/api/util" + gecko "github.com/miguelmota/cointop/pkg/api/vendors/coingecko/v3" + geckoTypes "github.com/miguelmota/cointop/pkg/api/vendors/coingecko/v3/types" ) // ErrPingFailed is the error for when pinging the API fails diff --git a/cointop/common/api/impl/coinmarketcap/coinmarketcap.go b/pkg/api/impl/coinmarketcap/coinmarketcap.go similarity index 97% rename from cointop/common/api/impl/coinmarketcap/coinmarketcap.go rename to pkg/api/impl/coinmarketcap/coinmarketcap.go index a6c91ac..cb855a8 100644 --- a/cointop/common/api/impl/coinmarketcap/coinmarketcap.go +++ b/pkg/api/impl/coinmarketcap/coinmarketcap.go @@ -7,8 +7,8 @@ import ( "strings" "time" - apitypes "github.com/miguelmota/cointop/cointop/common/api/types" - util "github.com/miguelmota/cointop/cointop/common/api/util" + apitypes "github.com/miguelmota/cointop/pkg/api/types" + util "github.com/miguelmota/cointop/pkg/api/util" cmc "github.com/miguelmota/go-coinmarketcap/pro/v1" cmcv2 "github.com/miguelmota/go-coinmarketcap/v2" ) diff --git a/cointop/common/api/impl/cryptocompare/cryptocompare.go b/pkg/api/impl/cryptocompare/cryptocompare.go similarity index 100% rename from cointop/common/api/impl/cryptocompare/cryptocompare.go rename to pkg/api/impl/cryptocompare/cryptocompare.go diff --git a/cointop/common/api/interface.go b/pkg/api/interface.go similarity index 93% rename from cointop/common/api/interface.go rename to pkg/api/interface.go index db908e3..36e0aab 100644 --- a/cointop/common/api/interface.go +++ b/pkg/api/interface.go @@ -1,7 +1,7 @@ package api import ( - types "github.com/miguelmota/cointop/cointop/common/api/types" + types "github.com/miguelmota/cointop/pkg/api/types" ) // Interface interface diff --git a/cointop/common/api/types/types.go b/pkg/api/types/types.go similarity index 100% rename from cointop/common/api/types/types.go rename to pkg/api/types/types.go diff --git a/cointop/common/api/util/util.go b/pkg/api/util/util.go similarity index 100% rename from cointop/common/api/util/util.go rename to pkg/api/util/util.go diff --git a/cointop/api/coingecko/LICENSE.txt b/pkg/api/vendors/coingecko/LICENSE.txt similarity index 100% rename from cointop/api/coingecko/LICENSE.txt rename to pkg/api/vendors/coingecko/LICENSE.txt diff --git a/cointop/api/coingecko/format/format.go b/pkg/api/vendors/coingecko/format/format.go similarity index 100% rename from cointop/api/coingecko/format/format.go rename to pkg/api/vendors/coingecko/format/format.go diff --git a/cointop/api/coingecko/v3/types/model.go b/pkg/api/vendors/coingecko/v3/types/model.go similarity index 100% rename from cointop/api/coingecko/v3/types/model.go rename to pkg/api/vendors/coingecko/v3/types/model.go diff --git a/cointop/api/coingecko/v3/types/types.go b/pkg/api/vendors/coingecko/v3/types/types.go similarity index 100% rename from cointop/api/coingecko/v3/types/types.go rename to pkg/api/vendors/coingecko/v3/types/types.go diff --git a/cointop/api/coingecko/v3/v3.go b/pkg/api/vendors/coingecko/v3/v3.go similarity index 98% rename from cointop/api/coingecko/v3/v3.go rename to pkg/api/vendors/coingecko/v3/v3.go index fb5822d..099ebc6 100644 --- a/cointop/api/coingecko/v3/v3.go +++ b/pkg/api/vendors/coingecko/v3/v3.go @@ -9,8 +9,8 @@ import ( "net/url" "strings" - "github.com/miguelmota/cointop/cointop/api/coingecko/format" - "github.com/miguelmota/cointop/cointop/api/coingecko/v3/types" + "github.com/miguelmota/cointop/pkg/api/vendors/coingecko/format" + "github.com/miguelmota/cointop/pkg/api/vendors/coingecko/v3/types" ) var baseURL = "https://api.coingecko.com/api/v3" diff --git a/cointop/common/asciitable/asciitable.go b/pkg/asciitable/asciitable.go similarity index 100% rename from cointop/common/asciitable/asciitable.go rename to pkg/asciitable/asciitable.go diff --git a/pkg/chartplot/chartplot.go b/pkg/chartplot/chartplot.go new file mode 100644 index 0000000..df52972 --- /dev/null +++ b/pkg/chartplot/chartplot.go @@ -0,0 +1,84 @@ +package chartplot + +import ( + "github.com/miguelmota/cointop/pkg/termui" +) + +// ChartPlot ... +type ChartPlot struct { + t *termui.LineChart +} + +// NewChartPlot ... +func NewChartPlot() *ChartPlot { + t := termui.NewLineChart() + + // NOTE: empty list means don't show x-axis labels + t.DataLabels = []string{""} + t.Border = false + + return &ChartPlot{ + t: t, + } +} + +// Height ... +func (c *ChartPlot) Height() int { + return c.t.Height +} + +// SetHeight ... +func (c *ChartPlot) SetHeight(height int) { + c.t.Height = height +} + +// Width ... +func (c *ChartPlot) Width() int { + return c.t.Width +} + +// SetWidth ... +func (c *ChartPlot) SetWidth(width int) { + c.t.Width = width +} + +// SetBorder ... +func (c *ChartPlot) SetBorder(enabled bool) { + c.t.Border = enabled +} + +// SetData ... +func (c *ChartPlot) SetData(data []float64) { + // NOTE: edit `termui.LineChart.shortenFloatVal(float64)` to not + // use exponential notation. + c.t.Data = data +} + +// GetChartPoints ... +func (c *ChartPlot) GetChartPoints(width int) [][]rune { + termui.Body = termui.NewGrid() + termui.Body.Width = width + termui.Body.AddRows( + termui.NewRow( + termui.NewCol(12, 0, c.t), + ), + ) + + var points [][]rune + // calculate layout + termui.Body.Align() + w := termui.Body.Width + h := c.Height() + row := termui.Body.Rows[0] + b := row.Buffer() + for i := 0; i < h; i = i + 1 { + var rowpoints []rune + for j := 0; j < w; j = j + 1 { + p := b.At(j, i) + rowpoints = append(rowpoints, p.Ch) + } + points = append(points, rowpoints) + } + + return points +} diff --git a/cointop/common/color/color.go b/pkg/color/color.go similarity index 100% rename from cointop/common/color/color.go rename to pkg/color/color.go diff --git a/cointop/common/filecache/filecache.go b/pkg/filecache/filecache.go similarity index 100% rename from cointop/common/filecache/filecache.go rename to pkg/filecache/filecache.go diff --git a/cointop/common/humanize/humanize.go b/pkg/humanize/humanize.go similarity index 100% rename from cointop/common/humanize/humanize.go rename to pkg/humanize/humanize.go diff --git a/cointop/common/levenshtein/levenshtein.go b/pkg/levenshtein/levenshtein.go similarity index 100% rename from cointop/common/levenshtein/levenshtein.go rename to pkg/levenshtein/levenshtein.go diff --git a/cointop/common/open/open.go b/pkg/open/open.go similarity index 100% rename from cointop/common/open/open.go rename to pkg/open/open.go diff --git a/cointop/common/open/open_windows.go b/pkg/open/open_windows.go similarity index 100% rename from cointop/common/open/open_windows.go rename to pkg/open/open_windows.go diff --git a/cointop/common/pad/pad.go b/pkg/pad/pad.go similarity index 100% rename from cointop/common/pad/pad.go rename to pkg/pad/pad.go diff --git a/cointop/common/pathutil/pathutil.go b/pkg/pathutil/pathutil.go similarity index 100% rename from cointop/common/pathutil/pathutil.go rename to pkg/pathutil/pathutil.go diff --git a/cointop/common/pathutil/pathutil_test.go b/pkg/pathutil/pathutil_test.go similarity index 100% rename from cointop/common/pathutil/pathutil_test.go rename to pkg/pathutil/pathutil_test.go diff --git a/cointop/ssh/server.go b/pkg/ssh/server.go similarity index 98% rename from cointop/ssh/server.go rename to pkg/ssh/server.go index c53b611..daa415c 100644 --- a/cointop/ssh/server.go +++ b/pkg/ssh/server.go @@ -16,7 +16,7 @@ import ( "github.com/creack/pty" "github.com/gliderlabs/ssh" - "github.com/miguelmota/cointop/cointop/common/pathutil" + "github.com/miguelmota/cointop/pkg/pathutil" gossh "golang.org/x/crypto/ssh" ) diff --git a/cointop/common/table/README.md b/pkg/table/README.md similarity index 100% rename from cointop/common/table/README.md rename to pkg/table/README.md diff --git a/cointop/common/table/align.go b/pkg/table/align.go similarity index 100% rename from cointop/common/table/align.go rename to pkg/table/align.go diff --git a/cointop/common/table/align/align.go b/pkg/table/align/align.go similarity index 100% rename from cointop/common/table/align/align.go rename to pkg/table/align/align.go diff --git a/cointop/common/table/column.go b/pkg/table/column.go similarity index 100% rename from cointop/common/table/column.go rename to pkg/table/column.go diff --git a/cointop/common/table/row.go b/pkg/table/row.go similarity index 100% rename from cointop/common/table/row.go rename to pkg/table/row.go diff --git a/cointop/common/table/sort.go b/pkg/table/sort.go similarity index 100% rename from cointop/common/table/sort.go rename to pkg/table/sort.go diff --git a/cointop/common/table/table.go b/pkg/table/table.go similarity index 98% rename from cointop/common/table/table.go rename to pkg/table/table.go index c52fcea..51c4db5 100644 --- a/cointop/common/table/table.go +++ b/pkg/table/table.go @@ -6,7 +6,7 @@ import ( "sort" "strings" - "github.com/miguelmota/cointop/cointop/common/table/align" + "github.com/miguelmota/cointop/pkg/table/align" ) // Table table diff --git a/cointop/common/gizak/termui/.gitignore b/pkg/termui/.gitignore similarity index 100% rename from cointop/common/gizak/termui/.gitignore rename to pkg/termui/.gitignore diff --git a/cointop/common/gizak/termui/.travis.yml b/pkg/termui/.travis.yml similarity index 100% rename from cointop/common/gizak/termui/.travis.yml rename to pkg/termui/.travis.yml diff --git a/cointop/common/gizak/termui/Gopkg.toml b/pkg/termui/Gopkg.toml similarity index 100% rename from cointop/common/gizak/termui/Gopkg.toml rename to pkg/termui/Gopkg.toml diff --git a/cointop/common/gizak/termui/LICENSE b/pkg/termui/LICENSE similarity index 100% rename from cointop/common/gizak/termui/LICENSE rename to pkg/termui/LICENSE diff --git a/cointop/common/gizak/termui/README.md b/pkg/termui/README.md similarity index 100% rename from cointop/common/gizak/termui/README.md rename to pkg/termui/README.md diff --git a/cointop/common/gizak/termui/barchart.go b/pkg/termui/barchart.go similarity index 100% rename from cointop/common/gizak/termui/barchart.go rename to pkg/termui/barchart.go diff --git a/cointop/common/gizak/termui/block.go b/pkg/termui/block.go similarity index 100% rename from cointop/common/gizak/termui/block.go rename to pkg/termui/block.go diff --git a/cointop/common/gizak/termui/block_common.go b/pkg/termui/block_common.go similarity index 100% rename from cointop/common/gizak/termui/block_common.go rename to pkg/termui/block_common.go diff --git a/cointop/common/gizak/termui/block_windows.go b/pkg/termui/block_windows.go similarity index 100% rename from cointop/common/gizak/termui/block_windows.go rename to pkg/termui/block_windows.go diff --git a/cointop/common/gizak/termui/buffer.go b/pkg/termui/buffer.go similarity index 100% rename from cointop/common/gizak/termui/buffer.go rename to pkg/termui/buffer.go diff --git a/cointop/common/gizak/termui/canvas.go b/pkg/termui/canvas.go similarity index 100% rename from cointop/common/gizak/termui/canvas.go rename to pkg/termui/canvas.go diff --git a/cointop/common/gizak/termui/config.py b/pkg/termui/config.py similarity index 100% rename from cointop/common/gizak/termui/config.py rename to pkg/termui/config.py diff --git a/cointop/common/gizak/termui/doc.go b/pkg/termui/doc.go similarity index 100% rename from cointop/common/gizak/termui/doc.go rename to pkg/termui/doc.go diff --git a/cointop/common/gizak/termui/events.go b/pkg/termui/events.go similarity index 100% rename from cointop/common/gizak/termui/events.go rename to pkg/termui/events.go diff --git a/cointop/common/gizak/termui/gauge.go b/pkg/termui/gauge.go similarity index 100% rename from cointop/common/gizak/termui/gauge.go rename to pkg/termui/gauge.go diff --git a/cointop/common/gizak/termui/grid.go b/pkg/termui/grid.go similarity index 100% rename from cointop/common/gizak/termui/grid.go rename to pkg/termui/grid.go diff --git a/cointop/common/gizak/termui/helper.go b/pkg/termui/helper.go similarity index 100% rename from cointop/common/gizak/termui/helper.go rename to pkg/termui/helper.go diff --git a/cointop/common/gizak/termui/linechart.go b/pkg/termui/linechart.go similarity index 100% rename from cointop/common/gizak/termui/linechart.go rename to pkg/termui/linechart.go diff --git a/cointop/common/gizak/termui/linechart_others.go b/pkg/termui/linechart_others.go similarity index 100% rename from cointop/common/gizak/termui/linechart_others.go rename to pkg/termui/linechart_others.go diff --git a/cointop/common/gizak/termui/linechart_windows.go b/pkg/termui/linechart_windows.go similarity index 100% rename from cointop/common/gizak/termui/linechart_windows.go rename to pkg/termui/linechart_windows.go diff --git a/cointop/common/gizak/termui/list.go b/pkg/termui/list.go similarity index 100% rename from cointop/common/gizak/termui/list.go rename to pkg/termui/list.go diff --git a/cointop/common/gizak/termui/mbarchart.go b/pkg/termui/mbarchart.go similarity index 100% rename from cointop/common/gizak/termui/mbarchart.go rename to pkg/termui/mbarchart.go diff --git a/cointop/common/gizak/termui/mkdocs.yml b/pkg/termui/mkdocs.yml similarity index 100% rename from cointop/common/gizak/termui/mkdocs.yml rename to pkg/termui/mkdocs.yml diff --git a/cointop/common/gizak/termui/par.go b/pkg/termui/par.go similarity index 100% rename from cointop/common/gizak/termui/par.go rename to pkg/termui/par.go diff --git a/cointop/common/gizak/termui/pos.go b/pkg/termui/pos.go similarity index 100% rename from cointop/common/gizak/termui/pos.go rename to pkg/termui/pos.go diff --git a/cointop/common/gizak/termui/render.go b/pkg/termui/render.go similarity index 100% rename from cointop/common/gizak/termui/render.go rename to pkg/termui/render.go diff --git a/cointop/common/gizak/termui/sparkline.go b/pkg/termui/sparkline.go similarity index 100% rename from cointop/common/gizak/termui/sparkline.go rename to pkg/termui/sparkline.go diff --git a/cointop/common/gizak/termui/table.go b/pkg/termui/table.go similarity index 100% rename from cointop/common/gizak/termui/table.go rename to pkg/termui/table.go diff --git a/cointop/common/gizak/termui/textbuilder.go b/pkg/termui/textbuilder.go similarity index 100% rename from cointop/common/gizak/termui/textbuilder.go rename to pkg/termui/textbuilder.go diff --git a/cointop/common/gizak/termui/theme.go b/pkg/termui/theme.go similarity index 100% rename from cointop/common/gizak/termui/theme.go rename to pkg/termui/theme.go diff --git a/cointop/common/gizak/termui/widget.go b/pkg/termui/widget.go similarity index 100% rename from cointop/common/gizak/termui/widget.go rename to pkg/termui/widget.go diff --git a/cointop/common/timeutil/timeutil.go b/pkg/timeutil/timeutil.go similarity index 100% rename from cointop/common/timeutil/timeutil.go rename to pkg/timeutil/timeutil.go diff --git a/pkg/ui/ui.go b/pkg/ui/ui.go new file mode 100644 index 0000000..443122c --- /dev/null +++ b/pkg/ui/ui.go @@ -0,0 +1,94 @@ +package ui + +import ( + "github.com/miguelmota/gocui" +) + +// UI ... +type UI struct { + g *gocui.Gui +} + +// NewUI ... +func NewUI() (*UI, error) { + g, err := gocui.NewGui(gocui.Output256) + if err != nil { + return nil, err + } + + return &UI{ + g: g, + }, nil +} + +// GetGocui ... +func (ui *UI) GetGocui() *gocui.Gui { + return ui.g +} + +// SetFgColor ... +func (ui *UI) SetFgColor(fgColor gocui.Attribute) { + ui.g.FgColor = fgColor +} + +// SetBgColor ... +func (ui *UI) SetBgColor(bgColor gocui.Attribute) { + ui.g.BgColor = bgColor +} + +// SetInputEsc ... +func (ui *UI) SetInputEsc(enabled bool) { + ui.g.InputEsc = true +} + +// SetMouse ... +func (ui *UI) SetMouse(enabled bool) { + ui.g.Mouse = true +} + +// SetHighlight ... +func (ui *UI) SetHighlight(enabled bool) { + ui.g.Highlight = true +} + +// SetManagerFunc ... +func (ui *UI) SetManagerFunc(fn func() error) { + ui.g.SetManagerFunc(func(*gocui.Gui) error { + return fn() + }) +} + +// MainLoop ... +func (ui *UI) MainLoop() error { + return ui.g.MainLoop() +} + +// Close ... +func (ui *UI) Close() { + ui.g.Close() +} + +// SetView ... +func (ui *UI) SetView(view interface{}, x, y, w, h int) error { + if v, ok := view.(*View); ok { + gv, err := ui.g.SetView(v.Name(), x, y, w, h) + v.SetBacking(gv) + return err + } + return nil +} + +// SetViewOnBottom sets the view to the bottom layer +func (ui *UI) SetViewOnBottom(view interface{}) error { + if v, ok := view.(*View); ok { + if _, err := ui.g.SetViewOnBottom(v.Name()); err != nil { + return err + } + } + return nil +} + +// ActiveViewName returns the name of the active view +func (ui *UI) ActiveViewName() string { + return ui.g.CurrentView().Name() +} diff --git a/pkg/ui/view.go b/pkg/ui/view.go new file mode 100644 index 0000000..6d276e8 --- /dev/null +++ b/pkg/ui/view.go @@ -0,0 +1,242 @@ +package ui + +import ( + "fmt" + + "github.com/miguelmota/gocui" +) + +// IView is a cointop view +type IView interface { + Backing() *gocui.View + SetBacking(gocuiView *gocui.View) + Name() string +} + +// View is a cointop view +type View struct { + backing *gocui.View + name string +} + +// NewView creates a new view +func NewView(name string) *View { + return &View{ + name: name, + } +} + +// Backing returns the backing gocui view +func (view *View) Backing() *gocui.View { + return view.backing +} + +// SetBacking sets the backing gocui view +func (view *View) SetBacking(gocuiView *gocui.View) { + view.backing = gocuiView +} + +// HasBacking returns the true if this view has a gocui backing +func (view *View) HasBacking() bool { + return view.backing != nil +} + +// Height returns the view height +func (view *View) Height() int { + if view.HasBacking() { + _, h := view.backing.Size() + return h + } + + return 0 +} + +// Width returns the view width +func (view *View) Width() int { + if view.HasBacking() { + w, _ := view.backing.Size() + return w + } + + return 0 +} + +// Cursor returns the view's cursor points +func (view *View) Cursor() (int, int) { + if view.HasBacking() { + return view.backing.Cursor() + } + + return 0, 0 +} + +// CursorX returns the view's cursor X point +func (view *View) CursorX() int { + if view.HasBacking() { + x, _ := view.backing.Cursor() + return x + } + + return 0 +} + +// CursorY returns the view's cursor Y point +func (view *View) CursorY() int { + if view.HasBacking() { + _, y := view.backing.Cursor() + return y + } + + return 0 +} + +// SetCursor sets the view's cursor +func (view *View) SetCursor(x, y int) error { + if view.HasBacking() { + return view.backing.SetCursor(x, y) + } + + return nil +} + +// Origin returns the view's origin points +func (view *View) Origin() (int, int) { + if view.HasBacking() { + return view.backing.Origin() + } + + return 0, 0 +} + +// OriginX returns the view's origin X point +func (view *View) OriginX() int { + if view.HasBacking() { + x, _ := view.backing.Origin() + return x + } + + return 0 +} + +// OriginY returns the view's origin Y point +func (view *View) OriginY() int { + if view.HasBacking() { + _, y := view.backing.Origin() + return y + } + + return 0 +} + +// SetOrigin sets the view's origin +func (view *View) SetOrigin(x, y int) error { + if view.HasBacking() { + return view.backing.SetOrigin(x, y) + } + + return nil +} + +// Name returns the view's name +func (view *View) Name() string { + return view.name +} + +// Clear clears the view content +func (view *View) Clear() error { + if view.HasBacking() { + view.backing.Clear() + } + return nil +} + +// Write will write the content to the view +func (view *View) Write(content string) error { + if view.HasBacking() { + fmt.Fprintln(view.backing, content) + } + return nil +} + +// Update will clear and write the content to the view +func (view *View) Update(content string) error { + view.Clear() + view.Write(content) + return nil +} + +// SetFrame enables the frame border for the view +func (view *View) SetFrame(enabled bool) error { + if view.HasBacking() { + view.backing.Frame = enabled + } + return nil +} + +// SetHighlight enables the highlight color for the view +func (view *View) SetHighlight(enabled bool) error { + if view.HasBacking() { + view.backing.Highlight = enabled + } + return nil +} + +// SetEditable makes the view editable +func (view *View) SetEditable(enabled bool) error { + if view.HasBacking() { + view.backing.Editable = enabled + } + return nil +} + +// SetWrap enables text wrapping for the view +func (view *View) SetWrap(enabled bool) error { + if view.HasBacking() { + view.backing.Wrap = enabled + } + return nil +} + +// SetFgColor sets the foreground color +func (view *View) SetFgColor(color gocui.Attribute) { + if view.HasBacking() { + view.backing.FgColor = color + } +} + +// SetBgColor sets the background color +func (view *View) SetBgColor(color gocui.Attribute) { + if view.HasBacking() { + view.backing.BgColor = color + } +} + +// SetSelFgColor sets the foreground color for selection +func (view *View) SetSelFgColor(color gocui.Attribute) { + if view.HasBacking() { + view.backing.SelFgColor = color + } +} + +// SetSelBgColor sets the background color for selection +func (view *View) SetSelBgColor(color gocui.Attribute) { + if view.HasBacking() { + view.backing.SelBgColor = color + } +} + +// Read reads data in bytes buffer +func (view *View) Read(b []byte) (int, error) { + if view.HasBacking() { + return view.backing.Read(b) + } + return 0, nil +} + +// Rewind undos view update +func (view *View) Rewind() error { + if view.HasBacking() { + view.backing.Rewind() + } + return nil +}