Add clipboard command, better help message styles, refactor
This commit is contained in:
parent
09e780385e
commit
8d197dd6d9
13
client.go
13
client.go
|
@ -37,6 +37,8 @@ type Client struct {
|
|||
|
||||
tourLinks []string // List of links to tour
|
||||
tourNext int // The index for link that will be visit next time user uses tour
|
||||
|
||||
lastPage *Page // Last viewed page information
|
||||
}
|
||||
|
||||
// NewClient loads the config file and returns a new client object
|
||||
|
@ -378,6 +380,7 @@ func (c *Client) HandleSpartanParsedURL(parsed *url.URL) bool {
|
|||
page.mediaType = mediaType
|
||||
page.params = params
|
||||
c.DisplayPage(page)
|
||||
c.lastPage = page
|
||||
case 3:
|
||||
c.HandleURL("spartan://" + parsed.Host + res.meta)
|
||||
case 4:
|
||||
|
@ -428,6 +431,7 @@ func (c *Client) HandleGeminiParsedURL(parsed *url.URL) bool {
|
|||
page.bodyBytes = bodyBytes
|
||||
page.mediaType = mediaType
|
||||
page.params = params
|
||||
c.lastPage = page
|
||||
c.DisplayPage(page)
|
||||
case 3:
|
||||
return c.HandleURL(res.meta) // TODO: max redirect times
|
||||
|
@ -469,8 +473,7 @@ func (c *Client) Search(query string) {
|
|||
|
||||
// LookupCommand attempts to get the corresponding command from cmdStr,
|
||||
// returning the command and whether the command was found
|
||||
func (c *Client) LookupCommand(cmdStr string) (cmd Command, ok bool) {
|
||||
cmdName := ""
|
||||
func (c *Client) LookupCommand(cmdStr string) (cmdName string, cmd Command, ok bool) {
|
||||
ok = false
|
||||
// skipping metaCommands
|
||||
for name, v := range commands {
|
||||
|
@ -513,10 +516,14 @@ func (c *Client) Command(cmdStr string, args ...string) bool {
|
|||
metaCommands[cmdName].do(c, args...)
|
||||
return true
|
||||
}
|
||||
cmd, ok := c.LookupCommand(cmdStr)
|
||||
_, cmd, ok := c.LookupCommand(cmdStr)
|
||||
if !ok {
|
||||
return ok
|
||||
}
|
||||
// "<cmd> help"
|
||||
if len(args) > 0 && (args[0] == "help" || args[0] == "?" || args[0] == "--help") {
|
||||
return c.Command("help", cmdStr)
|
||||
}
|
||||
cmd.do(c, args...)
|
||||
return true
|
||||
}
|
||||
|
|
198
cmd.go
198
cmd.go
|
@ -2,6 +2,9 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
@ -12,9 +15,10 @@ type Command struct {
|
|||
do func(client *Client, args ...string)
|
||||
help string
|
||||
quotedArgs bool
|
||||
hidden bool
|
||||
}
|
||||
|
||||
func printHelp() {
|
||||
func printHelp(style *Style) {
|
||||
maxWidth := 0
|
||||
var placeholder string
|
||||
curWidth := 0
|
||||
|
@ -41,42 +45,52 @@ func printHelp() {
|
|||
fmt.Println("Otherwise, there are plenty of useful commands you can use.")
|
||||
fmt.Println("Arguments are separated by spaces, and quoting with ' and \" is supported\nlike the shell, but escaping quotes is not support yet.")
|
||||
fmt.Println()
|
||||
fmt.Println("You can supply a command name to `help` to see the help for a specific command")
|
||||
fmt.Println("You can supply a command name to `help` to see the help for a specific command, like `help tour`.")
|
||||
fmt.Println()
|
||||
fmt.Println("Commands:")
|
||||
var spacesBetween int
|
||||
for name, cmd := range commands {
|
||||
// TODO: wrap description with... aniswrap?
|
||||
// also maybe add some colors in the help!
|
||||
parts := formatCommandHelp(&cmd, name, false)
|
||||
spacesBetween = maxWidth + minSepSpaceLen - len(parts[0])
|
||||
fmt.Printf(" %s%s %s\n", parts[0], strings.Repeat(" ", spacesBetween), parts[1])
|
||||
if cmd.hidden {
|
||||
continue
|
||||
}
|
||||
parts := formatCommandHelp(&cmd, name, false, style)
|
||||
// FIXME: formatcmd help behaviour changed, alter other places!
|
||||
spacesBetween = maxWidth + minSepSpaceLen - len(parts[0]) - len(name) - 1
|
||||
fmt.Printf(" %s %s%s %s\n", name, style.cmdPlaceholder.Sprint(parts[0]), strings.Repeat(" ", spacesBetween), parts[1])
|
||||
}
|
||||
fmt.Println("\nMeta commands:")
|
||||
fmt.Println(" help | ? | h [<cmd>...]")
|
||||
fmt.Println(" aliases | alias | synonym [<cmd>...]")
|
||||
}
|
||||
|
||||
// Handles placeholders in cmd.help if any, if format is true it will return the placeholder
|
||||
// string and the help string concatenated, if format is false, it returns them separately.
|
||||
func formatCommandHelp(cmd *Command, name string, format bool) (formatted []string) {
|
||||
func formatCommandHelp(cmd *Command, name string, format bool, style *Style) (formatted []string) {
|
||||
firstLine := strings.SplitN(cmd.help, "\n", 2)[0]
|
||||
parts := strings.SplitN(firstLine, ":", 2)
|
||||
|
||||
var placeholder, desc string
|
||||
|
||||
desc = firstLine
|
||||
if len(parts) == 2 {
|
||||
placeholder = strings.TrimSpace(parts[0])
|
||||
desc = strings.TrimSpace(parts[1])
|
||||
}
|
||||
left := ""
|
||||
if placeholder != "" {
|
||||
left = fmt.Sprintf("%s %s", name, placeholder)
|
||||
} else {
|
||||
left = name
|
||||
desc = firstLine
|
||||
}
|
||||
formatted = make([]string, 2)
|
||||
if format {
|
||||
formatted[0] = fmt.Sprintf("%s %s", left, desc)
|
||||
if placeholder != "" {
|
||||
left = fmt.Sprintf("%s %s", name, style.cmdPlaceholder.Sprint(placeholder))
|
||||
} else {
|
||||
left = name
|
||||
desc = firstLine
|
||||
}
|
||||
formatted[0] = style.cmdLabels.Sprint("Usage") + fmt.Sprintf(": %s\n\n", left) + style.cmdSynopsis.Sprint(desc)
|
||||
return
|
||||
}
|
||||
formatted[0] = left
|
||||
formatted[0] = placeholder
|
||||
formatted[1] = desc
|
||||
return
|
||||
}
|
||||
|
@ -108,36 +122,45 @@ var metaCommands = map[string]Command{
|
|||
aliases: []string{"h", "?", "hi"},
|
||||
do: func(c *Client, args ...string) {
|
||||
if len(args) > 0 {
|
||||
for _, v := range args {
|
||||
for i, v := range args {
|
||||
// Separator
|
||||
if len(args) > 1 && i > 0 {
|
||||
fmt.Println("---")
|
||||
}
|
||||
// Yes, have to do metaCommands manually
|
||||
switch v {
|
||||
case "help", "?", "h", "hi":
|
||||
fmt.Println("You literally just get help :P")
|
||||
return
|
||||
fmt.Println("help: You literally just get help :P")
|
||||
continue
|
||||
case "alias", "aliases", "synonymn":
|
||||
fmt.Println("See aliases for a command or all commands")
|
||||
return
|
||||
fmt.Println("alias: See aliases for a command or all commands")
|
||||
continue
|
||||
}
|
||||
|
||||
cmd, ok := c.LookupCommand(v)
|
||||
name, cmd, ok := c.LookupCommand(v)
|
||||
if !ok {
|
||||
fmt.Println(v, "command not found")
|
||||
return
|
||||
continue
|
||||
}
|
||||
formatted := formatCommandHelp(&cmd, v, true)
|
||||
formatted := formatCommandHelp(&cmd, name, true, c.style)
|
||||
fmt.Println(formatted[0])
|
||||
if len(cmd.aliases) > 0 {
|
||||
fmt.Println("\n"+c.style.cmdLabels.Sprint("Aliases")+": [", strings.Join(cmd.aliases, ", "), "]")
|
||||
}
|
||||
// Extra help for command if the command supports it
|
||||
extra := strings.SplitN(cmd.help, "\n", 2)[1]
|
||||
if extra != "" {
|
||||
fmt.Println()
|
||||
fmt.Println(extra)
|
||||
if strings.Contains(cmd.help, "\n") {
|
||||
extra := strings.SplitN(cmd.help, "\n", 2)[1]
|
||||
if extra != "" {
|
||||
fmt.Println()
|
||||
fmt.Println(extra)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
printHelp()
|
||||
printHelp(c.style)
|
||||
},
|
||||
help: "<cmd...> : print the usage or the help for a command",
|
||||
help: "[<cmd...>] : print the usage or the help for a command",
|
||||
},
|
||||
"aliases": {
|
||||
aliases: []string{"alias", "synonym"},
|
||||
|
@ -148,19 +171,19 @@ var metaCommands = map[string]Command{
|
|||
// but I can't find a better solution UGH
|
||||
switch v {
|
||||
case "help", "?", "h", "hi":
|
||||
fmt.Println("help, ?, h, hi")
|
||||
return
|
||||
fmt.Println("help ? h hi")
|
||||
continue
|
||||
case "alias", "aliases", "synonym":
|
||||
fmt.Println("alias, aliases, synonym")
|
||||
return
|
||||
fmt.Println("alias aliases synonym")
|
||||
continue
|
||||
}
|
||||
cmd, ok := c.LookupCommand(v)
|
||||
name, cmd, ok := c.LookupCommand(v)
|
||||
if !ok {
|
||||
fmt.Println(v, "command not found")
|
||||
}
|
||||
fmt.Println(strings.Join(cmd.aliases, ", "))
|
||||
return
|
||||
fmt.Println(name, strings.Join(cmd.aliases, " "))
|
||||
}
|
||||
return
|
||||
}
|
||||
fmt.Println("todo")
|
||||
},
|
||||
|
@ -174,7 +197,7 @@ var commands = map[string]Command{
|
|||
do: func(c *Client, args ...string) {
|
||||
c.Search(strings.Join(args, " "))
|
||||
},
|
||||
help: "<query...> : search with search engine",
|
||||
help: "[<query...>] : search with search engine",
|
||||
},
|
||||
"quit": {
|
||||
aliases: []string{"exit", "x", "q"},
|
||||
|
@ -195,7 +218,7 @@ var commands = map[string]Command{
|
|||
help: "reload current page",
|
||||
},
|
||||
"history": {
|
||||
aliases: []string{"hist"},
|
||||
aliases: []string{"hist", "his"},
|
||||
do: func(c *Client, args ...string) {
|
||||
if len(args) == 0 {
|
||||
for i, v := range c.history {
|
||||
|
@ -220,14 +243,14 @@ var commands = map[string]Command{
|
|||
// TODO: handle spartan input
|
||||
c.HandleParsedURL(c.history[index-1])
|
||||
},
|
||||
help: `<index> : print list of previously visited URLs, or visit an item in history
|
||||
help: `[<index>] : print list of previously visited URLs, or visit an item in history
|
||||
Examples:
|
||||
- history
|
||||
- history 1
|
||||
- history -3`,
|
||||
- his 1
|
||||
- hist -3`,
|
||||
},
|
||||
"link": {
|
||||
aliases: []string{"l", "peek", "p", "links"},
|
||||
aliases: []string{"l", "peek", "links"},
|
||||
do: func(c *Client, args ...string) {
|
||||
if len(args) < 1 {
|
||||
for i, v := range c.links {
|
||||
|
@ -255,7 +278,7 @@ Examples:
|
|||
fmt.Println(index, link) // TODO: also save the label in c.links
|
||||
}
|
||||
},
|
||||
help: `<index>... : peek what a link index would link to, or see the list of all links
|
||||
help: `[<index>...] : peek what a link index would link to, or see the list of all links
|
||||
You can use non-positive indexes too, see ` + "`links 0`" + ` for more information
|
||||
Examples:
|
||||
- links
|
||||
|
@ -280,7 +303,8 @@ Examples:
|
|||
do: func(c *Client, args ...string) {
|
||||
fmt.Println("not implemented yet!")
|
||||
},
|
||||
help: "go forward in history",
|
||||
help: "go forward in history",
|
||||
hidden: true,
|
||||
},
|
||||
"current": {
|
||||
aliases: []string{"u", "url", "cur"},
|
||||
|
@ -293,8 +317,49 @@ Examples:
|
|||
},
|
||||
help: "print current url",
|
||||
},
|
||||
"copyurl": {
|
||||
aliases: []string{"cu", "yy"},
|
||||
do: func(c *Client, args ...string) {
|
||||
var urlStr string
|
||||
if len(args) < 1 {
|
||||
if len(c.history) == 0 {
|
||||
fmt.Println("No history yet!")
|
||||
return
|
||||
}
|
||||
urlStr = c.history[len(c.history)-1].String()
|
||||
fmt.Println("url:", urlStr)
|
||||
c.ClipboardCopy(urlStr)
|
||||
return
|
||||
}
|
||||
var index int
|
||||
var err error
|
||||
for i, arg := range args {
|
||||
index, err = strconv.Atoi(arg)
|
||||
if err != nil {
|
||||
c.style.ErrorMsg(arg + ": Invalid link index")
|
||||
continue
|
||||
}
|
||||
index = c.ResolveNonPositiveIndex(index, len(c.links))
|
||||
if index == 0 {
|
||||
continue
|
||||
}
|
||||
if index < 1 || index > len(c.links) {
|
||||
c.style.ErrorMsg(arg + ": Invalid link index")
|
||||
continue
|
||||
}
|
||||
link, _ := c.GetLinkFromIndex(index)
|
||||
if len(args) > 1 && i != 0 {
|
||||
urlStr += "\n"
|
||||
}
|
||||
urlStr += link
|
||||
fmt.Println("url:", link)
|
||||
}
|
||||
c.ClipboardCopy(urlStr)
|
||||
},
|
||||
help: "[<index>...] : copy current url or links on page to clipboard",
|
||||
},
|
||||
"editurl": {
|
||||
aliases: []string{"e", "edit"},
|
||||
aliases: []string{"e", "eu", "edit"},
|
||||
do: func(c *Client, args ...string) {
|
||||
// TODO: Use a link from current page or from history instead of current url
|
||||
var link string
|
||||
|
@ -441,7 +506,12 @@ Examples:
|
|||
fmt.Println("Added", added, "items to tour list")
|
||||
}
|
||||
},
|
||||
help: `<range or number>... : loop over selection of links in current page
|
||||
help: `[<range or number>...] : loop over selection of links in current page
|
||||
Subcommands:
|
||||
- l[s] list items in tour
|
||||
- c[lear] clear tour list
|
||||
- g[o] jump to item in tour
|
||||
|
||||
Use tour * to add all links. you can use ranges like 1,10 or 10,1 with single links as multiple arguments.
|
||||
Use tour ls/clear to view items or clear all.
|
||||
tour go <index> takes you to an item in the tour list
|
||||
|
@ -466,6 +536,13 @@ Examples:
|
|||
// help: "<key> <value>: set a configuration value for the current gelim session",
|
||||
// quotedArgs: true,
|
||||
// },
|
||||
"page": {
|
||||
aliases: []string{"p", "print", "view", "display"},
|
||||
do: func(c *Client, args ...string) {
|
||||
c.DisplayPage(c.lastPage)
|
||||
},
|
||||
help: "view current page again without reloading",
|
||||
},
|
||||
}
|
||||
|
||||
// CommandCompleter returns a suitable command to complete an input line
|
||||
|
@ -477,3 +554,34 @@ func CommandCompleter(line string) (c []string) {
|
|||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) ClipboardCopy(content string) (ok bool) {
|
||||
ok = true
|
||||
|
||||
clip := c.conf.ClipboardCmd
|
||||
if clip == "" {
|
||||
ok = false
|
||||
c.style.ErrorMsg("please set a clipboard command in config file option 'clipboardCmd'\nThe content to copy will be piped into that command as stdin")
|
||||
return
|
||||
}
|
||||
cmd := exec.Command(clip)
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
ok = false
|
||||
c.style.ErrorMsg("Error running command '" + clip + "': " + err.Error())
|
||||
return
|
||||
}
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err = cmd.Start(); err != nil {
|
||||
ok = false
|
||||
c.style.ErrorMsg("Error running command '" + clip + "': " + err.Error())
|
||||
return
|
||||
}
|
||||
io.WriteString(stdin, content)
|
||||
stdin.Close()
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Wait()
|
||||
fmt.Println("Copied successfully")
|
||||
return
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ type Config struct {
|
|||
Index0Shortcut int
|
||||
LeftMargin float32
|
||||
MaxWidth int
|
||||
ClipboardCmd string
|
||||
}
|
||||
|
||||
// LoadConfig opes the configuration file at $XDG_CONFIG_HOME/gelim/config.toml
|
||||
|
@ -38,6 +39,7 @@ func LoadConfig() (*Config, error) {
|
|||
conf.SearchURL = "gemini://geminispace.info/search"
|
||||
conf.LeftMargin = 0.15
|
||||
conf.MaxWidth = 102 // 100 + allowance of 2 ;P
|
||||
conf.ClipboardCmd = ""
|
||||
|
||||
path := filepath.Join(xdg.ConfigHome(), "gelim", "config.toml")
|
||||
_, err = os.Stat(path)
|
||||
|
|
|
@ -19,6 +19,11 @@ type Style struct {
|
|||
gmiLink *color.Color
|
||||
gmiQuote *color.Color
|
||||
gmiPre *color.Color
|
||||
|
||||
// Line mode interface
|
||||
cmdSynopsis *color.Color
|
||||
cmdPlaceholder *color.Color
|
||||
cmdLabels *color.Color // Eg: Usage:
|
||||
}
|
||||
|
||||
var DefaultStyle = Style{
|
||||
|
@ -33,6 +38,10 @@ var DefaultStyle = Style{
|
|||
gmiPre: color.New(color.FgYellow),
|
||||
gmiLink: color.New(color.FgBlue),
|
||||
gmiQuote: color.New(color.Italic, color.FgGreen),
|
||||
|
||||
cmdSynopsis: color.New(color.Italic),
|
||||
cmdPlaceholder: color.New(color.FgBlue, color.Italic),
|
||||
cmdLabels: color.New(color.Bold),
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
2
go.mod
2
go.mod
|
@ -9,6 +9,6 @@ require (
|
|||
github.com/manifoldco/ansiwrap v1.1.0
|
||||
github.com/peterh/liner v1.2.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa // indirect
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect
|
||||
golang.org/x/term v0.0.0-20210429154555-c04ba851c2a4
|
||||
)
|
||||
|
|
4
go.sum
4
go.sum
|
@ -19,7 +19,7 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
|
|||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa h1:ZYxPR6aca/uhfRJyaOAtflSHjJYiktO7QnJC5ut7iY4=
|
||||
golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20210429154555-c04ba851c2a4 h1:UPou2i3GzKgi6igR+/0C5XyHKBngHxBp/CL5CQ0p3Zk=
|
||||
golang.org/x/term v0.0.0-20210429154555-c04ba851c2a4/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
|
|
Loading…
Reference in New Issue