add support for custom shortcuts

Former-commit-id: 0cbbdec70aeb238f3d2335402125e85ad86a749d [formerly 0cbbdec70aeb238f3d2335402125e85ad86a749d [formerly 8684c983cc8fe39ae992e98c8b1468863edc5522 [formerly 9acdc2f1a2412e48fd78c77e8c33f37a8e65f11a]]]
Former-commit-id: da315cd287d39ed5929367e5ba291a1557256722
Former-commit-id: e832893298a69578434b8361effaf09401fc2bcb [formerly b3764ccad924fa795368fbca84852b19f018d873]
Former-commit-id: a4e140a3b193a387e61ea03267475b451260a2b0
pull/15/head
Miguel Mota 6 years ago
parent 01c81f3871
commit 555e1b8742

@ -8,6 +8,19 @@
[`cointop`](https://github.com/miguelmota/cointop) is a fast and lightweight interactive terminal based UI application for tracking and monitoring cryptocurrency coin stats in real-time. The interface is inspired by [`htop`](https://en.wikipedia.org/wiki/Htop).
## Table of Contents
- [Features](#features)
- [Install](#install)
- [Updating](#updating)
- [Usage](#usage)
- [Shortcuts](#shortcuts)
- [Config](#config)
- [Actions](#actions)
- [FAQ](#faq)
- [Authors](#authors)
- [License](#license)
## Features
- Quick sort shortcuts
@ -15,6 +28,7 @@
- Pagination
- 7 day charts
- Color coded
- Custom key bindings
#### Future releases
@ -23,7 +37,6 @@
- Currency conversion (i.e. Euro, Yen)
- Markets/Exchanges
- CryptoCompare API
- Custom shortcuts
## Install
@ -51,11 +64,13 @@ sudo curl -s "https://raw.githubusercontent.com/miguelmota/cointop/master/instal
## Usage
Just run the `cointop` command to get started:
```bash
$ cointop
```
### Shortcuts
## Shortcuts
List of default shortcut keys:
@ -100,6 +115,7 @@ Key|Action
<kbd>m</kbd>|Sort table by *[m]arket cap*
<kbd>M</kbd>|Go to middle of visible table window (vim style)
<kbd>n</kbd>|Sort table by *[n]ame*
<kbd>o</kbd>|[o]pen row link
<kbd>p</kbd>|Sort table by *[p]rice*
<kbd>r</kbd>|Sort table by *[r]ank*
<kbd>s</kbd>|Sort table by *[s]ymbol*
@ -110,6 +126,110 @@ Key|Action
<kbd>$</kbd>|Go to last page (vim style)
<kbd>?</kbd>|Show help|
## Config
The first time you run cointop, it'll create a config file in:
```
~/.cointop/config
```
You can then configure the actions you want for each key:
(default `~/.cointop/config`)
```toml
[shortcuts]
"$" = "last_page"
0 = "first_page"
1 = "sort_column_1h_change"
2 = "sort_column_24h_change"
7 = "sort_column_7d_change"
"?" = "help"
G = "move_to_page_last_row"
H = "move_to_page_visible_first_row"
L = "move_to_page_visible_last_row"
M = "move_to_page_visible_middle_row"
a = "sort_column_available_supply"
"alt+arrowdown" = "sort_column_desc"
"alt+arrowleft" = "sort_left_column"
"alt+arrowright" = "sort_right_column"
"alt+arrowup" = "sort_column_asc"
arrowdown = "move_down"
arrowleft = "previous_page"
arrowright = "next_page"
arrowup = "move_up"
c = "toggle_row_chart"
"ctrl+c" = "quit"
"ctrl+d" = "page_down"
"ctrl+n" = "next_page"
"ctrl+p" = "previous_page"
"ctrl+r" = "refresh"
"ctrl+u" = "page_up"
end = "move_to_page_last_row"
enter = "open_link"
esc = "quit"
f1 = "help"
g = "move_to_page_first_row"
h = "previous_page"
home = "move_to_page_first_row"
j = "move_down"
k = "move_up"
l = "next_page"
m = "sort_column_market_cap"
n = "sort_column_name"
o = "open_link"
p = "sort_column_price"
pagedown = "page_down"
pageup = "page_up"
q = "quit"
r = "sort_column_rank"
s = "sort_column_symbol"
space = "open_link"
t = "sort_column_total_supply"
u = "sort_column_last_updated"
v = "sort_column_24h_volume"
```
## List of actions
Action|Description
----|------|
`first_page`|Go to first page
`help`|Show help
`last_page`|Go to last page
`move_to_page_first_row`|Move to first row on page
`move_to_page_last_row`|Move to last row on page
`move_to_page_visible_first_row`|Move to first visible row on page
`move_to_page_visible_last_row`|Move to last visible row on page
`move_to_page_visible_middle_row`|Move to middle visible row on page
`move_up`|Move one row up
`move_down`|Move one row down
`next_page`|Go to next page
`open_link`|Open row link
`page_down`|Move one row down
`page_up`|Scroll one page up
`previous_page`|Go to previous page
`quit`|Quit application
`refresh`|Do a manual refresh on the data
`sort_column_1h_change`|Sort table by column *1 hour change*
`sort_column_24h_change`|Sort table by column *24 hour change*
`sort_column_24h_volume`|Sort table by column *24 hour volume*
`sort_column_7d_change`|Sort table by column *7 day change*
`sort_column_asc`|Sort highlighted column by ascending order
`sort_column_available_supply`|Sort table by column *available supply*
`sort_column_desc`|Sort highlighted column by descending order
`sort_column_last_updated`|Sort table by column *last updated*
`sort_column_market_cap`|Sort table by column *market cap*
`sort_column_name`|Sort table by column *name*
`sort_column_price`|Sort table by column *price*
`sort_column_rank`|Sort table by column *rank*
`sort_column_symbol`|Sort table by column *symbol*
`sort_column_total_supply`|Sort table by column *total supply*
`sort_left_column`|Sort the column to the left of the highlighted column
`sort_right_column`|Sort the column to the right of the highlighted column
`toggle_row_chart`|Toggle the chart for the highlighted row
## FAQ
- Q: Where is the data from?

@ -35,20 +35,13 @@ type Cointop struct {
forcerefresh chan bool
selectedcoin *apt.Coin
maxtablewidth int
shortcutkeys map[string]string
config config // toml config
}
// Run runs cointop
func Run() {
g, err := gocui.NewGui(gocui.Output256)
if err != nil {
log.Fatalf("new gocui: %v", err)
}
defer g.Close()
g.Cursor = true
g.Mouse = true
g.Highlight = true
ct := Cointop{
g: g,
api: api.NewCMC(),
refreshticker: time.NewTicker(1 * time.Minute),
sortby: "rank",
@ -57,7 +50,18 @@ func Run() {
perpage: 100,
forcerefresh: make(chan bool),
maxtablewidth: 175,
shortcutkeys: defaultShortcuts(),
}
_ = ct.setupConfig()
g, err := gocui.NewGui(gocui.Output256)
if err != nil {
log.Fatalf("new gocui: %v", err)
}
ct.g = g
defer g.Close()
g.Cursor = true
g.Mouse = true
g.Highlight = true
g.SetManagerFunc(ct.layout)
if err := ct.keybindings(g); err != nil {
log.Fatalf("keybindings: %v", err)

@ -0,0 +1,125 @@
package cointop
import (
"bytes"
"fmt"
"os"
"os/user"
"strings"
"github.com/BurntSushi/toml"
)
type config struct {
Shortcuts map[string]interface{} `toml:"shortcuts"`
}
func (ct *Cointop) setupConfig() error {
err := ct.makeConfigDir()
if err != nil {
return err
}
err = ct.makeConfigFile()
if err != nil {
return err
}
err = ct.parseConfig()
if err != nil {
return err
}
err = ct.loadShortcutsFromConfig()
if err != nil {
return err
}
return nil
}
func (ct *Cointop) configDirPath() string {
usr, err := user.Current()
if err != nil {
return ".cointop"
}
return fmt.Sprintf("%s%s", usr.HomeDir, "/.cointop")
}
func (ct *Cointop) configPath() string {
return fmt.Sprintf("%v%v", ct.configDirPath(), "/config")
}
func (ct *Cointop) makeConfigDir() error {
path := ct.configDirPath()
if _, err := os.Stat(path); os.IsNotExist(err) {
_ = os.Mkdir(path, os.ModePerm)
}
return nil
}
func (ct *Cointop) makeConfigFile() error {
path := ct.configPath()
if _, err := os.Stat(path); os.IsNotExist(err) {
fo, err := os.Create(path)
if err != nil {
return err
}
defer fo.Close()
b, err := ct.configToToml()
if err != nil {
return err
}
if _, err := fo.Write(b); err != nil {
return err
}
}
return nil
}
func (ct *Cointop) parseConfig() error {
var conf config
path := ct.configPath()
if _, err := toml.DecodeFile(path, &conf); err != nil {
return err
}
ct.config = conf
return nil
}
func (ct *Cointop) configToToml() ([]byte, error) {
s := defaultShortcuts()
ifcs := map[string]interface{}{}
for k, v := range s {
var i interface{} = v
ifcs[k] = i
}
var inputs = &config{
Shortcuts: ifcs,
}
var b bytes.Buffer
encoder := toml.NewEncoder(&b)
err := encoder.Encode(inputs)
if err != nil {
return nil, err
}
return b.Bytes(), nil
}
func (ct *Cointop) loadShortcutsFromConfig() error {
actionsmap := actionsMap()
for k, ifc := range ct.config.Shortcuts {
k = strings.ToLower(k)
v, ok := ifc.(string)
if ok {
if !actionsmap[k] {
continue
}
if ct.shortcutkeys[k] == "" {
continue
}
ct.shortcutkeys[k] = v
}
}
return nil
}

@ -6,58 +6,6 @@ import (
"github.com/jroimartin/gocui"
)
// defaults
var shortcutkeys = map[string]string{
"arrowup": "moveup",
"arrowdown": "movedown",
"arrowleft": "prevpage",
"arrowright": "nextpage",
"pagedown": "pagedown",
"pageup": "pageup",
"home": "movepagefirstrow",
"end": "movepagelastrow",
"enter": "openlink",
"esc": "quit",
"space": "openlink",
"ctrl+c": "quit",
"ctrl+d": "pagedown",
"ctrl+n": "nextpage",
"ctrl+p": "prevpage",
"ctrl+r": "refresh",
"ctrl+u": "pageup",
"alt+arrowup": "sortcolasc",
"alt+arrowdown": "sortcoldesc",
"alt+arrowleft": "sortleftcol",
"alt+arrowright": "sortrightcol",
"f1": "help",
"0": "movefirstpage",
"1": "sortcol1hchange",
"2": "sortcol24hchange",
"7": "sortcol7dchange",
"a": "sortcolavailablesupply",
"c": "togglerowchart",
"g": "movepagefirstrow",
"G": "movepagelastrow",
"h": "prevpage",
"H": "movepagevisiblefirstrow",
"j": "movedown",
"k": "moveup",
"l": "nextpage",
"L": "movepagevisiblelastrow",
"m": "sortcolmarketcap",
"M": "movepagevisiblemiddlerow",
"n": "sortcolname",
"p": "sortcolprice",
"r": "sortcolrank",
"s": "sortcolsymbol",
"t": "sortcoltotalsupply",
"u": "sortcollastupdated",
"v": "sortcol24hvolume",
"q": "quit",
"$": "movelastpage",
"?": "help",
}
func (ct *Cointop) parseKeys(s string) (interface{}, gocui.Modifier) {
var key interface{}
mod := gocui.ModNone
@ -238,75 +186,76 @@ func (ct *Cointop) parseKeys(s string) (interface{}, gocui.Modifier) {
}
func (ct *Cointop) keybindings(g *gocui.Gui) error {
for k, v := range shortcutkeys {
for k, v := range ct.shortcutkeys {
v = strings.TrimSpace(strings.ToLower(v))
var fn func(g *gocui.Gui, v *gocui.View) error
key, mod := ct.parseKeys(k)
switch v {
case "moveup":
case "move_up":
fn = ct.cursorUp
case "movedown":
case "move_down":
fn = ct.cursorDown
case "prevpage":
case "previous_page":
fn = ct.prevPage
case "nextpage":
case "next_page":
fn = ct.nextPage
case "pagedown":
case "page_down":
fn = ct.pageDown
case "pageup":
case "page_up":
fn = ct.pageUp
case "sortcolsymbol":
case "sort_column_symbol":
fn = ct.sortfn("symbol", false)
case "movepagefirstrow":
case "move_to_page_first_row":
fn = ct.navigateFirstLine
case "movepagelastrow":
case "move_to_page_last_row":
fn = ct.navigateLastLine
case "openlink":
case "open_link":
fn = ct.openLink
case "refresh":
fn = ct.refresh
case "sortcolasc":
case "sort_column_asc":
fn = ct.sortAsc
case "sortcoldesc":
case "sort_column_desc":
fn = ct.sortDesc
case "sortleftcol":
case "sort_left_column":
fn = ct.sortPrevCol
case "sortrightcol":
case "sort_right_column":
fn = ct.sortNextCol
case "help":
fn = ct.openHelp
case "movefirstpage":
case "first_page":
fn = ct.firstPage
case "sortcol1hchange":
case "sort_column_1h_change":
fn = ct.sortfn("1hchange", true)
case "sortcol24hchange":
case "sort_column_24h_change":
fn = ct.sortfn("24hchange", true)
case "sortcol7dchange":
case "sort_column_7d_change":
fn = ct.sortfn("7dchange", true)
case "sortcolavailablesupply":
case "sort_column_available_supply":
fn = ct.sortfn("availablesupply", true)
case "togglerowchart":
case "toggle_row_chart":
fn = ct.toggleCoinChart
case "movepagevisiblefirstrow":
case "move_to_page_visible_first_row":
fn = ct.navigatePageFirstLine
case "movepagevisiblelastrow":
case "move_to_page_visible_last_row":
fn = ct.navigatePageLastLine
case "sortcolmarketcap":
case "sort_column_market_cap":
fn = ct.sortfn("marketcap", true)
case "movepagevisiblemiddlerow":
case "move_to_page_visible_middle_row":
fn = ct.navigatePageMiddleLine
case "sortcolname":
case "sort_column_name":
fn = ct.sortfn("name", true)
case "sortcolprice":
case "sort_column_price":
fn = ct.sortfn("price", true)
case "sortcolrank":
case "sort_column_rank":
fn = ct.sortfn("rank", false)
case "sortcoltotalsupply":
case "sort_column_total_supply":
fn = ct.sortfn("totalsupply", true)
case "sortcollastupdated":
case "sort_column_last_updated":
fn = ct.sortfn("lastupdated", true)
case "sortcol24hvolume":
case "sort_column_24h_volume":
fn = ct.sortfn("24hvolume", true)
case "movelastpage":
case "last_page":
fn = ct.lastPage
case "quit":
fn = ct.quit

@ -0,0 +1,94 @@
package cointop
func actionsMap() map[string]bool {
return map[string]bool{
"first_page": true,
"help": true,
"last_page": true,
"move_to_page_first_row": true,
"move_to_page_last_row": true,
"move_to_page_visible_first_row": true,
"move_to_page_visible_last_row": true,
"move_to_page_visible_middle_row": true,
"move_up": true,
"move_down": true,
"next_page": true,
"open_link": true,
"page_down": true,
"page_up": true,
"previous_page": true,
"quit": true,
"refresh": true,
"sort_column_1h_change": true,
"sort_column_24h_change": true,
"sort_column_24h_volume": true,
"sort_column_7d_change": true,
"sort_column_asc": true,
"sort_column_available_supply": true,
"sort_column_desc": true,
"sort_column_last_updated": true,
"sort_column_market_cap": true,
"sort_column_name": true,
"sort_column_price": true,
"sort_column_rank": true,
"sort_column_symbol": true,
"sort_column_total_supply": true,
"sort_left_column": true,
"sort_right_column": true,
"toggle_row_chart": true,
}
}
func defaultShortcuts() map[string]string {
return map[string]string{
"arrowup": "move_up",
"arrowdown": "move_down",
"arrowleft": "previous_page",
"arrowright": "next_page",
"pagedown": "page_down",
"pageup": "page_up",
"home": "move_to_page_first_row",
"end": "move_to_page_last_row",
"enter": "open_link",
"esc": "quit",
"space": "open_link",
"ctrl+c": "quit",
"ctrl+d": "page_down",
"ctrl+n": "next_page",
"ctrl+p": "previous_page",
"ctrl+r": "refresh",
"ctrl+u": "page_up",
"alt+arrowup": "sort_column_asc",
"alt+arrowdown": "sort_column_desc",
"alt+arrowleft": "sort_left_column",
"alt+arrowright": "sort_right_column",
"f1": "help",
"0": "first_page",
"1": "sort_column_1h_change",
"2": "sort_column_24h_change",
"7": "sort_column_7d_change",
"a": "sort_column_available_supply",
"c": "toggle_row_chart",
"g": "move_to_page_first_row",
"G": "move_to_page_last_row",
"h": "previous_page",
"H": "move_to_page_visible_first_row",
"j": "move_down",
"k": "move_up",
"l": "next_page",
"L": "move_to_page_visible_last_row",
"m": "sort_column_market_cap",
"M": "move_to_page_visible_middle_row",
"n": "sort_column_name",
"o": "open_link",
"p": "sort_column_price",
"r": "sort_column_rank",
"s": "sort_column_symbol",
"t": "sort_column_total_supply",
"u": "sort_column_last_updated",
"v": "sort_column_24h_volume",
"q": "quit",
"$": "last_page",
"?": "help",
}
}
Loading…
Cancel
Save