mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-10-24 02:26:39 +08:00
Merge pull request #13480 from ioito/hotfix/qx-climc-help
fix(climc): support --help args
This commit is contained in:
+13
-23
@@ -36,7 +36,6 @@ import (
|
||||
)
|
||||
|
||||
type BaseOptions struct {
|
||||
Help bool `help:"Show help" short-token:"h"`
|
||||
Debug bool `help:"Show debug information"`
|
||||
Version bool `help:"Show version"`
|
||||
Timeout int `default:"600" help:"Number of seconds to wait for a response"`
|
||||
@@ -75,7 +74,7 @@ func getSubcommandsParser() (*structarg.ArgumentParser, error) {
|
||||
prog = "climc"
|
||||
desc = `Command-line interface to the API server.`
|
||||
)
|
||||
parse, e := structarg.NewArgumentParser(&BaseOptions{},
|
||||
parse, e := structarg.NewArgumentParserWithHelp(&BaseOptions{},
|
||||
prog,
|
||||
desc,
|
||||
`See "climc help COMMAND" for help on a specific command.`)
|
||||
@@ -86,22 +85,10 @@ func getSubcommandsParser() (*structarg.ArgumentParser, error) {
|
||||
if subcmd == nil {
|
||||
return nil, fmt.Errorf("No subcommand argument")
|
||||
}
|
||||
type HelpOptions struct {
|
||||
SUBCOMMAND string `help:"Sub-command name"`
|
||||
}
|
||||
shell.R(&HelpOptions{}, "help", "Show help information of any subcommand", func(suboptions *HelpOptions) error {
|
||||
helpstr, e := subcmd.SubHelpString(suboptions.SUBCOMMAND)
|
||||
if e != nil {
|
||||
return e
|
||||
} else {
|
||||
fmt.Print(helpstr)
|
||||
return nil
|
||||
}
|
||||
})
|
||||
|
||||
promptRootCmd := promputils.InitRootCmd(prog, desc, parse.GetOptArgs(), parse.GetPosArgs())
|
||||
for _, v := range shell.CommandTable {
|
||||
_par, e := subcmd.AddSubParser(v.Options, v.Command, v.Desc, v.Callback)
|
||||
_par, e := subcmd.AddSubParserWithHelp(v.Options, v.Command, v.Desc, v.Callback)
|
||||
|
||||
if e != nil {
|
||||
return nil, e
|
||||
@@ -254,15 +241,18 @@ func executeSubcommand(
|
||||
options *BaseOptions,
|
||||
sessionFactory func() *mcclient.ClientSession,
|
||||
) {
|
||||
var e error
|
||||
suboptions := subparser.Options()
|
||||
if options.SUBCOMMAND == "help" {
|
||||
e = subcmd.Invoke(suboptions)
|
||||
} else {
|
||||
e = subcmd.Invoke(sessionFactory(), suboptions)
|
||||
if subparser.IsHelpSet() {
|
||||
helpStr, err := subcmd.SubHelpString(options.SUBCOMMAND)
|
||||
if err != nil {
|
||||
showErrorAndExit(err)
|
||||
}
|
||||
fmt.Println(helpStr)
|
||||
return
|
||||
}
|
||||
if e != nil {
|
||||
showErrorAndExit(e)
|
||||
err := subcmd.Invoke(sessionFactory(), suboptions)
|
||||
if err != nil {
|
||||
showErrorAndExit(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,7 +272,7 @@ func ClimcMain() {
|
||||
return
|
||||
}
|
||||
|
||||
if options.Help {
|
||||
if parser.IsHelpSet() {
|
||||
fmt.Print(parser.HelpString())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -75,6 +75,10 @@ func Executor(s string) {
|
||||
} else {
|
||||
session.GetClient().SetDebug(false)
|
||||
}
|
||||
if subparser.IsHelpSet() {
|
||||
fmt.Print(subparser.HelpString())
|
||||
return
|
||||
}
|
||||
if e != nil {
|
||||
if subparser != nil {
|
||||
fmt.Print(subparser.Usage())
|
||||
@@ -84,11 +88,7 @@ func Executor(s string) {
|
||||
fmt.Println(e)
|
||||
} else {
|
||||
suboptions := subparser.Options()
|
||||
if args[0] == "help" {
|
||||
e = subcmd.Invoke(suboptions)
|
||||
} else {
|
||||
e = subcmd.Invoke(session, suboptions)
|
||||
}
|
||||
e = subcmd.Invoke(session, suboptions)
|
||||
if e != nil {
|
||||
fmt.Println(e)
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ require (
|
||||
github.com/benbjohnson/clock v1.0.0
|
||||
github.com/bitly/go-simplejson v0.5.0
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
|
||||
github.com/c-bata/go-prompt v0.2.1
|
||||
github.com/c-bata/go-prompt v0.2.4
|
||||
github.com/cheggaaa/pb/v3 v3.0.8
|
||||
github.com/coredns/coredns v1.3.0
|
||||
github.com/coreos/go-systemd v0.0.0-20190620071333-e64a0ec8b42a // indirect
|
||||
@@ -103,7 +103,6 @@ require (
|
||||
github.com/openzipkin/zipkin-go-opentracing v0.3.4 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.12
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pkg/term v0.0.0-20181116001808-27bbf2edb814 // indirect
|
||||
github.com/pquerna/otp v1.2.0
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a // indirect
|
||||
github.com/satori/go.uuid v1.2.0 // indirect
|
||||
@@ -160,5 +159,5 @@ require (
|
||||
yunion.io/x/pkg v0.0.0-20211116020154-6a76ba2f7e97
|
||||
yunion.io/x/s3cli v0.0.0-20190917004522-13ac36d8687e
|
||||
yunion.io/x/sqlchemy v0.0.0-20220119131341-fadec84c0638
|
||||
yunion.io/x/structarg v0.0.0-20200720093445-9f850fa222ce
|
||||
yunion.io/x/structarg v0.0.0-20220224030024-02b7582b2546
|
||||
)
|
||||
|
||||
@@ -159,8 +159,8 @@ github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2 h1:1B/+1BcRhOMG1KH/YhNIU8OppSWk5d/NGyfRla88CuY=
|
||||
github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
|
||||
github.com/c-bata/go-prompt v0.2.1 h1:HTPxmnfY4y5x3Myo7Gq4ce989jfnvZIBgdBp6y8bjYM=
|
||||
github.com/c-bata/go-prompt v0.2.1/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34=
|
||||
github.com/c-bata/go-prompt v0.2.4 h1:7pKUJ3CUgzdu1HJeWhNRkpVyY/NnlJhM/7d6YgHNOao=
|
||||
github.com/c-bata/go-prompt v0.2.4/go.mod h1:PqlttLXp0E7bZcoDW+dmzyKqFbmQTFoNzGSuW/AQRmo=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cheggaaa/pb/v3 v3.0.8 h1:bC8oemdChbke2FHIIGy9mn4DPJ2caZYQnfbRqwmdCoA=
|
||||
github.com/cheggaaa/pb/v3 v3.0.8/go.mod h1:UICbiLec/XO6Hw6k+BHEtHeQFzzBH4i2/qk/ow1EJTA=
|
||||
@@ -453,6 +453,7 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
@@ -460,6 +461,7 @@ github.com/mattn/go-sqlite3 v1.7.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOq
|
||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
|
||||
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
|
||||
github.com/mattn/go-tty v0.0.0-20181127064339-e4f871175a2f h1:4P7Ul+TAnk92vTeVkXs6VLjmf1EhrYtDRa03PCYY6VM=
|
||||
github.com/mattn/go-tty v0.0.0-20181127064339-e4f871175a2f/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
@@ -541,8 +543,8 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/term v0.0.0-20181116001808-27bbf2edb814 h1:qWlKm6sU/AyVAgBC1Pg1uuY/X/p4lNYG7t4v8/f4FJE=
|
||||
github.com/pkg/term v0.0.0-20181116001808-27bbf2edb814/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ=
|
||||
github.com/pkg/term v1.0.0 h1:raTSNJjic7X4n89hhPyLOsgRYczmr3MyezRierWCTa8=
|
||||
github.com/pkg/term v1.0.0/go.mod h1:6rk0zrj6TXf8MR5fdVFsZMeGM2lxe3tUFLNBRlwX+dk=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pquerna/otp v1.2.0 h1:/A3+Jn+cagqayeR3iHs/L62m5ue7710D35zl1zJ1kok=
|
||||
@@ -784,6 +786,7 @@ golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
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-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -970,5 +973,5 @@ yunion.io/x/s3cli v0.0.0-20190917004522-13ac36d8687e h1:v+EzIadodSwkdZ/7bremd7J8
|
||||
yunion.io/x/s3cli v0.0.0-20190917004522-13ac36d8687e/go.mod h1:0iFKpOs1y4lbCxeOmq3Xx/0AcQoewVPwj62eRluioEo=
|
||||
yunion.io/x/sqlchemy v0.0.0-20220119131341-fadec84c0638 h1:Tc7bMp/mahI1Zua5dxBNrXSKl4hCUSA7CPBcqBnK8Uw=
|
||||
yunion.io/x/sqlchemy v0.0.0-20220119131341-fadec84c0638/go.mod h1:FTdwPdGhMgh4E+UFXc9klI1Ok34fMuybTT+jLhOaIjI=
|
||||
yunion.io/x/structarg v0.0.0-20200720093445-9f850fa222ce h1:kU8xE7O5uZ1GSJVMZHoJ+jrNL7csUQHYGyAPW9QfNpE=
|
||||
yunion.io/x/structarg v0.0.0-20200720093445-9f850fa222ce/go.mod h1:EP6NSv2C0zzqBDTKumv8hPWLb3XvgMZDHQRfyuOrQng=
|
||||
yunion.io/x/structarg v0.0.0-20220224030024-02b7582b2546 h1:OWu5/aUPmF1V4m+LSDRM0ESY1WX/oJ2x7gwO00j80ik=
|
||||
yunion.io/x/structarg v0.0.0-20220224030024-02b7582b2546/go.mod h1:EP6NSv2C0zzqBDTKumv8hPWLb3XvgMZDHQRfyuOrQng=
|
||||
|
||||
+1
@@ -2,6 +2,7 @@
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
bin/
|
||||
|
||||
# Folders
|
||||
pkg/
|
||||
|
||||
+39
-1
@@ -1,10 +1,48 @@
|
||||
# Change Log
|
||||
|
||||
## v0.3.0 (2018/??/??)
|
||||
|
||||
next release.
|
||||
|
||||
## v0.2.3 (2018/10/25)
|
||||
|
||||
### What's new?
|
||||
|
||||
* Add `prompt.FuzzyFilter` for fuzzy matching at [#92](https://github.com/c-bata/go-prompt/pull/92).
|
||||
* Add `OptionShowCompletionAtStart` to show completion at start at [#100](https://github.com/c-bata/go-prompt/pull/100).
|
||||
* Add `prompt.NewStderrWriter` at [#102](https://github.com/c-bata/go-prompt/pull/102).
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix resetting display attributes (please see [pull #104](https://github.com/c-bata/go-prompt/pull/104) for more details).
|
||||
* Fix error handling of Flush function in ConsoleWriter (please see [pull #97](https://github.com/c-bata/go-prompt/pull/97) for more details).
|
||||
* Fix panic problem when reading from stdin before starting the prompt (please see [issue #88](https://github.com/c-bata/go-prompt/issues/88) for more details).
|
||||
|
||||
### Removed or Deprecated
|
||||
|
||||
* `prompt.NewStandardOutputWriter` is deprecated. Please use `prompt.NewStdoutWriter`.
|
||||
|
||||
## v0.2.2 (2018/06/28)
|
||||
|
||||
### What's new?
|
||||
|
||||
* Support CJK(Chinese, Japanese and Korean) and Cyrillic characters.
|
||||
* Add OptionCompletionWordSeparator(x string) to customize insertion points for completions.
|
||||
* To support this, text query functions by arbitrary word separator are added in Document (please see [here](https://github.com/c-bata/go-prompt/pull/79) for more details).
|
||||
* Add FilePathCompleter to complete file path on your system.
|
||||
* Add option to customize ascii code key bindings.
|
||||
* Add GetWordAfterCursor method in Document.
|
||||
|
||||
### Removed or Deprecated
|
||||
|
||||
* prompt.Choose shortcut function is deprecated.
|
||||
|
||||
## v0.2.1 (2018/02/14)
|
||||
|
||||
### What's New?
|
||||
|
||||
* It seems that windows support is almost perfect.
|
||||
* ~~It seems that windows support is almost perfect.~~
|
||||
* A critical bug is found :( When you change a terminal window size, the layout will be broken because current implementation cannot catch signal for updating window size on Windows.
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
-155
@@ -1,155 +0,0 @@
|
||||
# Developer Guide
|
||||
|
||||
## Getting Started
|
||||
|
||||
The most simple example is below.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/c-bata/go-prompt"
|
||||
)
|
||||
|
||||
// executor executes command and print the output.
|
||||
func executor(in string) {
|
||||
fmt.Println("Your input: " + in)
|
||||
}
|
||||
|
||||
// completer returns the completion items from user input.
|
||||
func completer(d prompt.Document) []prompt.Suggest {
|
||||
s := []prompt.Suggest{
|
||||
{Text: "users", Description: "user table"},
|
||||
{Text: "sites", Description: "sites table"},
|
||||
{Text: "articles", Description: "articles table"},
|
||||
{Text: "comments", Description: "comments table"},
|
||||
}
|
||||
return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
|
||||
}
|
||||
|
||||
func main() {
|
||||
p := prompt.New(
|
||||
executor,
|
||||
completer,
|
||||
prompt.OptionPrefix(">>> "),
|
||||
prompt.OptionTitle("sql-prompt"),
|
||||
)
|
||||
p.Run()
|
||||
}
|
||||
```
|
||||
|
||||
If you want to create CLI using go-prompt, I recommend you to look at the [source code of kube-prompt](https://github.com/c-bata/kube-prompt).
|
||||
It is the most practical example.
|
||||
|
||||
|
||||
## Options
|
||||
|
||||
go-prompt has many color options.
|
||||
It is difficult to describe by text. So please see below figure:
|
||||
|
||||

|
||||
|
||||
* **OptionPrefixTextColor(prompt.Color)** : default `prompt.Blue`
|
||||
* **OptionPrefixBackgroundColor(prompt.Color)** : default `prompt.DefaultColor`
|
||||
* **OptionInputTextColor(prompt.Color)** : default `prompt.DefaultColor`
|
||||
* **OptionInputBGColor(prompt.Color)** : default `prompt.DefaultColor`
|
||||
* **OptionPreviewSuggestionTextColor(prompt.Color)** : default `prompt.Green`
|
||||
* **OptionPreviewSuggestionBGColor(prompt.Color)** : default `prompt.DefaultColor`
|
||||
* **OptionSuggestionTextColor(prompt.Color)** : default `prompt.White`
|
||||
* **OptionSuggestionBGColor(prompt.Color)** : default `prompt.Cyan`
|
||||
* **OptionSelectedSuggestionTextColor(prompt.Color)** : `default prompt.Black`
|
||||
* **OptionSelectedSuggestionBGColor(prompt.Color)** : `default prompt.DefaultColor`
|
||||
* **OptionDescriptionTextColor(prompt.Color)** : default `prompt.Black`
|
||||
* **OptionDescriptionBGColor(prompt.Color)** : default `prompt.Turquoise`
|
||||
* **OptionSelectedDescriptionTextColor(prompt.Color)** : default `prompt.White`
|
||||
* **OptionSelectedDescriptionBGColor(prompt.Color)** : default `prompt.Cyan`
|
||||
* **OptionScrollbarThumbColor** : `prompt.DarkGray`
|
||||
* **OptionScrollbarBGColor** : `prompt.Cyan`
|
||||
|
||||
**Other Options**
|
||||
|
||||
#### `OptionTitle(string)` : default `""`
|
||||
Option to set a title that wll be displayed on the header bar of terminal.
|
||||
|
||||
#### `OptionHistory([]string)` : default `[]string{}`
|
||||
Option to set history.
|
||||
|
||||
#### `OptionPrefix(string)` : default `"> "`
|
||||
Option to set prefix string.
|
||||
|
||||
#### `OptionLivePrefix(func() (prefix string, useLivePrefix bool))` : default `nil`
|
||||
Option to set a callback function for updating prefix string dynamically.
|
||||
|
||||
#### `OptionMaxSuggestions(x uint16)` : default `6`
|
||||
The max number of displayed suggestions.
|
||||
|
||||
#### `OptionParser(prompt.ConsoleParser)` : default `VT100Parser`
|
||||
To set a custom ConsoleParser object.
|
||||
The argument should implement ConsoleParser interface.
|
||||
|
||||
#### `OptionWriter(prompt.ConsoleWriter)` : default `VT100Writer`
|
||||
To set a custom ConsoleWriter object.
|
||||
The argument should implement ConsoleWriter interface.
|
||||
|
||||
#### `SwitchKeyBindMode(prompt.KeyBindMode)` : default `prompt.EmacsKeyBindMode`
|
||||
To set a key bind mode.
|
||||
|
||||
#### `OptionAddKeyBind(...KeyBind)` : default `[]KeyBind{}`
|
||||
To set a custom key bind.
|
||||
|
||||
## Architecture of go-prompt
|
||||
|
||||
*Caution: This section is WIP.*
|
||||
|
||||
This is a short description of go-prompt implementation.
|
||||
go-prompt consists of three parts.
|
||||
|
||||
1. Input parser
|
||||
2. Emulate user input with Buffer object.
|
||||
3. Render buffer object.
|
||||
|
||||
### Input Parser
|
||||
|
||||

|
||||
|
||||
Input Parser only supports vt100-compatible console now.
|
||||
|
||||
* Set as the raw mode.
|
||||
* Read a standard input.
|
||||
* Parse to byte array
|
||||
|
||||
### Emulate user input
|
||||
|
||||
go-prompt contains Buffer class.
|
||||
It represents input state by handling a key input by user.
|
||||
|
||||
`Buffer` object has text and cursor position.
|
||||
|
||||
**TODO prepare the sample of buffer**
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "github.com/c-bata/go-prompt"
|
||||
|
||||
func main() {
|
||||
b := prompt.NewBuffer()
|
||||
... wip
|
||||
}
|
||||
```
|
||||
|
||||
### Renderer
|
||||
|
||||
`Renderer` object renders a buffer object.
|
||||
|
||||
**TODO prepare the sample of brender**
|
||||
|
||||
```go
|
||||
package main
|
||||
```
|
||||
|
||||
the output is below:
|
||||
|
||||
**TODO prepare a screen shot**
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/mattn/go-colorable"
|
||||
packages = ["."]
|
||||
revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
|
||||
version = "v0.0.9"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/mattn/go-isatty"
|
||||
packages = ["."]
|
||||
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
|
||||
version = "v0.0.3"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/mattn/go-tty"
|
||||
packages = ["."]
|
||||
revision = "c1750293025292316a611ae8f632d9791515e51c"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/pkg/term"
|
||||
packages = ["termios"]
|
||||
revision = "b1f72af2d63057363398bec5873d16a98b453312"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/sys"
|
||||
packages = ["unix"]
|
||||
revision = "37707fdb30a5b38865cfb95e5aab41707daec7fd"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "6c442f55617e93df75aa1ee2fd2b36cfab23003fded4723be56c6b6fd0545c56"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/mattn/go-colorable"
|
||||
version = "0.0.9"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/mattn/go-tty"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/pkg/term"
|
||||
+30
-15
@@ -1,29 +1,44 @@
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
SOURCES := $(shell find . -prune -o -name "*.go" -not -name '*_test.go' -print)
|
||||
|
||||
GOIMPORTS ?= goimports
|
||||
GOCILINT ?= golangci-lint
|
||||
|
||||
.PHONY: setup
|
||||
setup: ## Setup for required tools.
|
||||
go get github.com/golang/lint/golint
|
||||
go get golang.org/x/tools/cmd/goimports
|
||||
go get golang.org/x/tools/cmd/stringer
|
||||
go get -u github.com/golang/dep/cmd/dep
|
||||
dep ensure
|
||||
go get -u golang.org/x/tools/cmd/goimports
|
||||
go get -u github.com/golangci/golangci-lint/cmd/golangci-lint
|
||||
go get -u golang.org/x/tools/cmd/stringer
|
||||
|
||||
.PHONY: fmt
|
||||
fmt: ## Formatting source codes.
|
||||
@goimports -w .
|
||||
fmt: $(SOURCES) ## Formatting source codes.
|
||||
@$(GOIMPORTS) -w $^
|
||||
|
||||
.PHONY: lint
|
||||
lint: ## Run golint and go vet.
|
||||
@golint .
|
||||
@go vet .
|
||||
lint: ## Run golangci-lint.
|
||||
@$(GOCILINT) run --no-config --disable-all --enable=goimports --enable=misspell ./...
|
||||
|
||||
.PHONY: test
|
||||
test: ## Run the tests.
|
||||
@go test .
|
||||
test: ## Run tests with race condition checking.
|
||||
@go test -race ./...
|
||||
|
||||
.PHONY: race-test
|
||||
race-test: ## Checking the race condition.
|
||||
@go test -race .
|
||||
.PHONY: bench
|
||||
bench: ## Run benchmarks.
|
||||
@go test -bench=. -run=- -benchmem ./...
|
||||
|
||||
.PHONY: coverage
|
||||
cover: ## Run the tests.
|
||||
@go test -coverprofile=coverage.o
|
||||
@go tool cover -func=coverage.o
|
||||
|
||||
.PHONY: generate
|
||||
generate: ## Run go generate
|
||||
@go generate ./...
|
||||
|
||||
.PHONY: build
|
||||
build: ## Build example command lines.
|
||||
./_example/build.sh
|
||||
|
||||
.PHONY: help
|
||||
help: ## Show help text
|
||||
|
||||
+44
-31
@@ -1,7 +1,12 @@
|
||||
# go-prompt
|
||||
|
||||
Library for building a powerful interactive prompt, inspired by [python-prompt-toolkit](https://github.com/jonathanslenders/python-prompt-toolkit).
|
||||
Easy building a multi-platform binary of the command line tools because written in Golang.
|
||||
[](https://goreportcard.com/report/github.com/c-bata/go-prompt)
|
||||

|
||||
[](https://godoc.org/github.com/c-bata/go-prompt)
|
||||
[](https://travis-ci.org/c-bata/go-prompt)
|
||||
|
||||
A library for building powerful interactive prompts inspired by [python-prompt-toolkit](https://github.com/jonathanslenders/python-prompt-toolkit),
|
||||
making it easier to build cross-platform command line tools using Go.
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -27,15 +32,26 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### Projects using go-prompt
|
||||
|
||||
* [c-bata/kube-prompt : An interactive kubernetes client featuring auto-complete written in Go.](https://github.com/c-bata/kube-prompt)
|
||||
* [rancher/cli : The Rancher Command Line Interface (CLI)is a unified tool to manage your Rancher server](https://github.com/rancher/cli)
|
||||
* [kris-nova/kubicorn : Simple. Cloud Native. Kubernetes. Infrastructure.](https://github.com/kris-nova/kubicorn)
|
||||
* [kubicorn/kubicorn : Simple, cloud native infrastructure for Kubernetes.](https://github.com/kubicorn/kubicorn)
|
||||
* [cch123/asm-cli : Interactive shell of assembly language(X86/X64) based on unicorn and rasm2](https://github.com/cch123/asm-cli)
|
||||
* [ktr0731/evans : more expressive universal gRPC client](https://github.com/ktr0731/evans)
|
||||
* (If you create a CLI using go-prompt and want your own project to be listed here, Please submit a Github Issue.)
|
||||
* [CrushedPixel/moshpit: A Command-line tool for datamoshing.](https://github.com/CrushedPixel/moshpit)
|
||||
* [last-ent/testy-go: Testy Go: A tool for easy testing!](https://github.com/last-ent/testy-go)
|
||||
* [tiagorlampert/CHAOS: a PoC that allow generate payloads and control remote operating systems.](https://github.com/tiagorlampert/CHAOS)
|
||||
* [abs-lang/abs: ABS is a scripting language that works best on terminal. It tries to combine the elegance of languages such as Python, or Ruby, to the convenience of Bash.](https://github.com/abs-lang/abs)
|
||||
* [takashabe/btcli: btcli is a CLI client for the Bigtable. Has many read options and auto-completion.](https://github.com/takashabe/btcli)
|
||||
* [ysn2233/kafka-prompt: An interactive kafka-prompt(kafka-shell) built on existing kafka command client](https://github.com/ysn2233/kafka-prompt)
|
||||
* [fishi0x01/vsh: HashiCorp Vault interactive shell](https://github.com/fishi0x01/vsh)
|
||||
* [mstrYoda/docker-shell: A simple interactive prompt for docker](https://github.com/mstrYoda/docker-shell)
|
||||
* [c-bata/gh-prompt: An interactive GitHub CLI featuring auto-complete.](https://github.com/c-bata/gh-prompt)
|
||||
* [docker-slim/docker-slim: Don't change anything in your Docker container image and minify it by up to 30x (and for compiled languages even more) making it secure too! (free and open source)](https://github.com/docker-slim/docker-slim)
|
||||
* [rueyaa332266/ezcron: Ezcron is a CLI tool, helping you deal with cron expression easier.](https://github.com/rueyaa332266/ezcron)
|
||||
* [qingstor/qsctl: Advanced command line tool for QingStor Object Storage.](https://github.com/qingstor/qsctl)
|
||||
* (If you create a CLI utility using go-prompt and want your own project to be listed here, please submit a GitHub issue.)
|
||||
|
||||
## Features
|
||||
|
||||
@@ -47,54 +63,52 @@ func main() {
|
||||
|
||||
### Flexible options
|
||||
|
||||
go-prompt provides many options. All options are listed in [Developer Guide](./DEVELOPER_GUIDE.md).
|
||||
go-prompt provides many options. Please check [option section of GoDoc](https://godoc.org/github.com/c-bata/go-prompt#Option) for more details.
|
||||
|
||||
[](#flexible-options)
|
||||
|
||||
### Keyboard Shortcuts
|
||||
|
||||
Emacs-like keyboard shortcut is available by default (it's also default shortcuts in Bash shell).
|
||||
Emacs-like keyboard shortcuts are available by default (these also are the default shortcuts in Bash shell).
|
||||
You can customize and expand these shortcuts.
|
||||
|
||||
[](#keyboard-shortcuts)
|
||||
|
||||
KeyBinding | Description
|
||||
--------------------|---------------------------------------------------------
|
||||
<kbd>Ctrl + A</kbd> | Go to the beginning of the line (Home)
|
||||
<kbd>Ctrl + E</kbd> | Go to the End of the line (End)
|
||||
<kbd>Ctrl + P</kbd> | Previous command (Up arrow)
|
||||
<kbd>Ctrl + N</kbd> | Next command (Down arrow)
|
||||
<kbd>Ctrl + F</kbd> | Forward one character
|
||||
<kbd>Ctrl + B</kbd> | Backward one character
|
||||
<kbd>Ctrl + D</kbd> | Delete character under the cursor
|
||||
<kbd>Ctrl + H</kbd> | Delete character before the cursor (Backspace)
|
||||
<kbd>Ctrl + W</kbd> | Cut the Word before the cursor to the clipboard.
|
||||
<kbd>Ctrl + K</kbd> | Cut the Line after the cursor to the clipboard.
|
||||
<kbd>Ctrl + U</kbd> | Cut/delete the Line before the cursor to the clipboard.
|
||||
<kbd>Ctrl + L</kbd> | Clear the screen
|
||||
Key Binding | Description
|
||||
---------------------|---------------------------------------------------------
|
||||
<kbd>Ctrl + A</kbd> | Go to the beginning of the line (Home)
|
||||
<kbd>Ctrl + E</kbd> | Go to the end of the line (End)
|
||||
<kbd>Ctrl + P</kbd> | Previous command (Up arrow)
|
||||
<kbd>Ctrl + N</kbd> | Next command (Down arrow)
|
||||
<kbd>Ctrl + F</kbd> | Forward one character
|
||||
<kbd>Ctrl + B</kbd> | Backward one character
|
||||
<kbd>Ctrl + D</kbd> | Delete character under the cursor
|
||||
<kbd>Ctrl + H</kbd> | Delete character before the cursor (Backspace)
|
||||
<kbd>Ctrl + W</kbd> | Cut the word before the cursor to the clipboard
|
||||
<kbd>Ctrl + K</kbd> | Cut the line after the cursor to the clipboard
|
||||
<kbd>Ctrl + U</kbd> | Cut the line before the cursor to the clipboard
|
||||
<kbd>Ctrl + L</kbd> | Clear the screen
|
||||
|
||||
### History
|
||||
|
||||
You can use up-arrow and down-arrow to walk through the history of commands executed.
|
||||
You can use <kbd>Up arrow</kbd> and <kbd>Down arrow</kbd> to walk through the history of commands executed.
|
||||
|
||||
[](#history)
|
||||
|
||||
|
||||
### Multiple platform support
|
||||
|
||||
We confirmed following terminals
|
||||
We have confirmed go-prompt works fine in the following terminals:
|
||||
|
||||
* iTerm2 (macOS)
|
||||
* Terminal.app (macOS)
|
||||
* Command Prompt (Windows)
|
||||
* GNU Terminal (Ubuntu)
|
||||
|
||||
* gnome-terminal (Ubuntu)
|
||||
|
||||
## Links
|
||||
|
||||
* [Developer Guide](./DEVELOPER_GUIDE.md).
|
||||
* [Change Log](./CHANGELOG.md)
|
||||
* [GoDoc](http://godoc.org/github.com/c-bata/go-prompt).
|
||||
* [GoDoc](http://godoc.org/github.com/c-bata/go-prompt)
|
||||
* [gocover.io](https://gocover.io/github.com/c-bata/go-prompt)
|
||||
|
||||
## Author
|
||||
|
||||
@@ -102,9 +116,8 @@ Masashi Shibata
|
||||
|
||||
* Twitter: [@c\_bata\_](https://twitter.com/c_bata_/)
|
||||
* Github: [@c-bata](https://github.com/c-bata/)
|
||||
* Facebook: [Masashi Shibata](https://www.facebook.com/masashi.cbata)
|
||||
|
||||
## LICENSE
|
||||
## License
|
||||
|
||||
This software is licensed under the MIT License (See [LICENSE](./LICENSE) ).
|
||||
This software is licensed under the MIT license, see [LICENSE](./LICENSE) for more information.
|
||||
|
||||
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
package prompt
|
||||
|
||||
import "sort"
|
||||
|
||||
// BisectLeft to Locate the insertion point for v in a to maintain sorted order.
|
||||
func BisectLeft(a []int, v int) int {
|
||||
return bisectLeftRange(a, v, 0, len(a))
|
||||
}
|
||||
|
||||
func bisectLeftRange(a []int, v int, lo, hi int) int {
|
||||
s := a[lo:hi]
|
||||
return sort.Search(len(s), func(i int) bool {
|
||||
return s[i] >= v
|
||||
})
|
||||
}
|
||||
|
||||
// BisectRight to Locate the insertion point for v in a to maintain sorted order.
|
||||
func BisectRight(a []int, v int) int {
|
||||
return bisectRightRange(a, v, 0, len(a))
|
||||
}
|
||||
|
||||
func bisectRightRange(a []int, v int, lo, hi int) int {
|
||||
s := a[lo:hi]
|
||||
return sort.Search(len(s), func(i int) bool {
|
||||
return s[i] > v
|
||||
})
|
||||
}
|
||||
+37
-46
@@ -1,17 +1,19 @@
|
||||
package prompt
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/c-bata/go-prompt/internal/debug"
|
||||
)
|
||||
|
||||
// Buffer emulates the console buffer.
|
||||
type Buffer struct {
|
||||
workingLines []string // The working lines. Similar to history
|
||||
workingIndex int
|
||||
CursorPosition int
|
||||
cursorPosition int
|
||||
cacheDocument *Document
|
||||
preferredColumn int // Remember the original column for the next up/down movement.
|
||||
lastKeyStroke Key
|
||||
}
|
||||
|
||||
// Text returns string of the current line.
|
||||
@@ -23,19 +25,26 @@ func (b *Buffer) Text() string {
|
||||
func (b *Buffer) Document() (d *Document) {
|
||||
if b.cacheDocument == nil ||
|
||||
b.cacheDocument.Text != b.Text() ||
|
||||
b.cacheDocument.CursorPosition != b.CursorPosition {
|
||||
b.cacheDocument.cursorPosition != b.cursorPosition {
|
||||
b.cacheDocument = &Document{
|
||||
Text: b.Text(),
|
||||
CursorPosition: b.CursorPosition,
|
||||
cursorPosition: b.cursorPosition,
|
||||
}
|
||||
}
|
||||
b.cacheDocument.lastKey = b.lastKeyStroke
|
||||
return b.cacheDocument
|
||||
}
|
||||
|
||||
// DisplayCursorPosition returns the cursor position on rendered text on terminal emulators.
|
||||
// So if Document is "日本(cursor)語", DisplayedCursorPosition returns 4 because '日' and '本' are double width characters.
|
||||
func (b *Buffer) DisplayCursorPosition() int {
|
||||
return b.Document().DisplayCursorPosition()
|
||||
}
|
||||
|
||||
// InsertText insert string from current line.
|
||||
func (b *Buffer) InsertText(v string, overwrite bool, moveCursor bool) {
|
||||
or := []rune(b.Text())
|
||||
oc := b.CursorPosition
|
||||
oc := b.cursorPosition
|
||||
|
||||
if overwrite {
|
||||
overwritten := string(or[oc : oc+len(v)])
|
||||
@@ -49,59 +58,43 @@ func (b *Buffer) InsertText(v string, overwrite bool, moveCursor bool) {
|
||||
}
|
||||
|
||||
if moveCursor {
|
||||
b.CursorPosition += len([]rune(v))
|
||||
b.cursorPosition += len([]rune(v))
|
||||
}
|
||||
}
|
||||
|
||||
// SetText method to set text and update CursorPosition.
|
||||
// SetText method to set text and update cursorPosition.
|
||||
// (When doing this, make sure that the cursor_position is valid for this text.
|
||||
// text/cursor_position should be consistent at any time, otherwise set a Document instead.)
|
||||
func (b *Buffer) setText(v string) {
|
||||
if b.CursorPosition > len([]rune(v)) {
|
||||
log.Print("[ERROR] The length of input value should be shorter than the position of cursor.")
|
||||
}
|
||||
o := b.workingLines[b.workingIndex]
|
||||
debug.Assert(b.cursorPosition <= len([]rune(v)), "length of input should be shorter than cursor position")
|
||||
b.workingLines[b.workingIndex] = v
|
||||
|
||||
if o != v {
|
||||
// Text is changed.
|
||||
// TODO: Call callback function triggered by text changed. And also history search text should reset.
|
||||
// https://github.com/jonathanslenders/python-prompt-toolkit/blob/master/prompt_toolkit/buffer.py#L380-L384
|
||||
}
|
||||
}
|
||||
|
||||
// Set cursor position. Return whether it changed.
|
||||
func (b *Buffer) setCursorPosition(p int) {
|
||||
o := b.CursorPosition
|
||||
if p > 0 {
|
||||
b.CursorPosition = p
|
||||
b.cursorPosition = p
|
||||
} else {
|
||||
b.CursorPosition = 0
|
||||
}
|
||||
if p != o {
|
||||
// Cursor position is changed.
|
||||
// TODO: Call a onCursorPositionChanged function.
|
||||
b.cursorPosition = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Buffer) setDocument(d *Document) {
|
||||
b.cacheDocument = d
|
||||
b.setCursorPosition(d.CursorPosition) // Call before setText because setText check the relation between cursorPosition and line length.
|
||||
b.setCursorPosition(d.cursorPosition) // Call before setText because setText check the relation between cursorPosition and line length.
|
||||
b.setText(d.Text)
|
||||
}
|
||||
|
||||
// CursorLeft move to left on the current line.
|
||||
func (b *Buffer) CursorLeft(count int) {
|
||||
l := b.Document().GetCursorLeftPosition(count)
|
||||
b.CursorPosition += l
|
||||
return
|
||||
b.cursorPosition += l
|
||||
}
|
||||
|
||||
// CursorRight move to right on the current line.
|
||||
func (b *Buffer) CursorRight(count int) {
|
||||
l := b.Document().GetCursorRightPosition(count)
|
||||
b.CursorPosition += l
|
||||
return
|
||||
b.cursorPosition += l
|
||||
}
|
||||
|
||||
// CursorUp move cursor to the previous line.
|
||||
@@ -111,7 +104,7 @@ func (b *Buffer) CursorUp(count int) {
|
||||
if b.preferredColumn == -1 { // -1 means nil
|
||||
orig = b.Document().CursorPositionCol()
|
||||
}
|
||||
b.CursorPosition += b.Document().GetCursorUpPosition(count, orig)
|
||||
b.cursorPosition += b.Document().GetCursorUpPosition(count, orig)
|
||||
|
||||
// Remember the original column for the next up/down movement.
|
||||
b.preferredColumn = orig
|
||||
@@ -124,7 +117,7 @@ func (b *Buffer) CursorDown(count int) {
|
||||
if b.preferredColumn == -1 { // -1 means nil
|
||||
orig = b.Document().CursorPositionCol()
|
||||
}
|
||||
b.CursorPosition += b.Document().GetCursorDownPosition(count, orig)
|
||||
b.cursorPosition += b.Document().GetCursorDownPosition(count, orig)
|
||||
|
||||
// Remember the original column for the next up/down movement.
|
||||
b.preferredColumn = orig
|
||||
@@ -132,20 +125,18 @@ func (b *Buffer) CursorDown(count int) {
|
||||
|
||||
// DeleteBeforeCursor delete specified number of characters before cursor and return the deleted text.
|
||||
func (b *Buffer) DeleteBeforeCursor(count int) (deleted string) {
|
||||
if count <= 0 {
|
||||
log.Print("[ERROR] The count argument on DeleteBeforeCursor should grater than 0.")
|
||||
}
|
||||
debug.Assert(count >= 0, "count should be positive")
|
||||
r := []rune(b.Text())
|
||||
|
||||
if b.CursorPosition > 0 {
|
||||
start := b.CursorPosition - count
|
||||
if b.cursorPosition > 0 {
|
||||
start := b.cursorPosition - count
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
deleted = string(r[start:b.CursorPosition])
|
||||
deleted = string(r[start:b.cursorPosition])
|
||||
b.setDocument(&Document{
|
||||
Text: string(r[:start]) + string(r[b.CursorPosition:]),
|
||||
CursorPosition: b.CursorPosition - len([]rune(deleted)),
|
||||
Text: string(r[:start]) + string(r[b.cursorPosition:]),
|
||||
cursorPosition: b.cursorPosition - len([]rune(deleted)),
|
||||
})
|
||||
}
|
||||
return
|
||||
@@ -163,9 +154,9 @@ func (b *Buffer) NewLine(copyMargin bool) {
|
||||
// Delete specified number of characters and Return the deleted text.
|
||||
func (b *Buffer) Delete(count int) (deleted string) {
|
||||
r := []rune(b.Text())
|
||||
if b.CursorPosition < len(r) {
|
||||
if b.cursorPosition < len(r) {
|
||||
deleted = b.Document().TextAfterCursor()[:count]
|
||||
b.setText(string(r[:b.CursorPosition]) + string(r[b.CursorPosition+len(deleted):]))
|
||||
b.setText(string(r[:b.cursorPosition]) + string(r[b.cursorPosition+len(deleted):]))
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -173,7 +164,7 @@ func (b *Buffer) Delete(count int) (deleted string) {
|
||||
// JoinNextLine joins the next line to the current one by deleting the line ending after the current line.
|
||||
func (b *Buffer) JoinNextLine(separator string) {
|
||||
if !b.Document().OnLastLine() {
|
||||
b.CursorPosition += b.Document().GetEndOfLinePosition()
|
||||
b.cursorPosition += b.Document().GetEndOfLinePosition()
|
||||
b.Delete(1)
|
||||
// Remove spaces
|
||||
b.setText(b.Document().TextBeforeCursor() + separator + strings.TrimLeft(b.Document().TextAfterCursor(), " "))
|
||||
@@ -182,10 +173,10 @@ func (b *Buffer) JoinNextLine(separator string) {
|
||||
|
||||
// SwapCharactersBeforeCursor swaps the last two characters before the cursor.
|
||||
func (b *Buffer) SwapCharactersBeforeCursor() {
|
||||
if b.CursorPosition >= 2 {
|
||||
x := b.Text()[b.CursorPosition-2 : b.CursorPosition-1]
|
||||
y := b.Text()[b.CursorPosition-1 : b.CursorPosition]
|
||||
b.setText(b.Text()[:b.CursorPosition-2] + y + x + b.Text()[b.CursorPosition:])
|
||||
if b.cursorPosition >= 2 {
|
||||
x := b.Text()[b.cursorPosition-2 : b.cursorPosition-1]
|
||||
y := b.Text()[b.cursorPosition-1 : b.cursorPosition]
|
||||
b.setText(b.Text()[:b.cursorPosition-2] + y + x + b.Text()[b.cursorPosition:])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+35
-22
@@ -1,18 +1,23 @@
|
||||
package prompt
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/c-bata/go-prompt/internal/debug"
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
const (
|
||||
shortenSuffix = "..."
|
||||
leftPrefix = " "
|
||||
leftSuffix = " "
|
||||
rightPrefix = " "
|
||||
rightSuffix = " "
|
||||
leftMargin = len(leftPrefix + leftSuffix)
|
||||
rightMargin = len(rightPrefix + rightSuffix)
|
||||
shortenSuffix = "..."
|
||||
leftPrefix = " "
|
||||
leftSuffix = " "
|
||||
rightPrefix = " "
|
||||
rightSuffix = " "
|
||||
)
|
||||
|
||||
var (
|
||||
leftMargin = runewidth.StringWidth(leftPrefix + leftSuffix)
|
||||
rightMargin = runewidth.StringWidth(rightPrefix + rightSuffix)
|
||||
completionMargin = leftMargin + rightMargin
|
||||
)
|
||||
|
||||
@@ -30,6 +35,8 @@ type CompletionManager struct {
|
||||
completer Completer
|
||||
|
||||
verticalScroll int
|
||||
wordSeparator string
|
||||
showAtStart bool
|
||||
}
|
||||
|
||||
// GetSelectedSuggestion returns the selected item.
|
||||
@@ -37,7 +44,7 @@ func (c *CompletionManager) GetSelectedSuggestion() (s Suggest, ok bool) {
|
||||
if c.selected == -1 {
|
||||
return Suggest{}, false
|
||||
} else if c.selected < -1 {
|
||||
log.Printf("[ERROR] shoud be reached here, selected=%d", c.selected)
|
||||
debug.Assert(false, "must not reach here")
|
||||
c.selected = -1
|
||||
return Suggest{}, false
|
||||
}
|
||||
@@ -54,13 +61,11 @@ func (c *CompletionManager) Reset() {
|
||||
c.selected = -1
|
||||
c.verticalScroll = 0
|
||||
c.Update(*NewDocument())
|
||||
return
|
||||
}
|
||||
|
||||
// Update to update the suggestions.
|
||||
func (c *CompletionManager) Update(in Document) {
|
||||
c.tmp = c.completer(in)
|
||||
return
|
||||
}
|
||||
|
||||
// Previous to select the previous suggestion item.
|
||||
@@ -70,7 +75,6 @@ func (c *CompletionManager) Previous() {
|
||||
}
|
||||
c.selected--
|
||||
c.update()
|
||||
return
|
||||
}
|
||||
|
||||
// Next to select the next suggestion item.
|
||||
@@ -80,7 +84,6 @@ func (c *CompletionManager) Next() {
|
||||
}
|
||||
c.selected++
|
||||
c.update()
|
||||
return
|
||||
}
|
||||
|
||||
// Completing returns whether the CompletionManager selects something one.
|
||||
@@ -102,17 +105,26 @@ func (c *CompletionManager) update() {
|
||||
}
|
||||
}
|
||||
|
||||
func deleteBreakLineCharacters(s string) string {
|
||||
s = strings.Replace(s, "\n", "", -1)
|
||||
s = strings.Replace(s, "\r", "", -1)
|
||||
return s
|
||||
}
|
||||
|
||||
func formatTexts(o []string, max int, prefix, suffix string) (new []string, width int) {
|
||||
l := len(o)
|
||||
n := make([]string, l)
|
||||
|
||||
lenPrefix := len([]rune(prefix))
|
||||
lenSuffix := len([]rune(suffix))
|
||||
lenShorten := len(shortenSuffix)
|
||||
lenPrefix := runewidth.StringWidth(prefix)
|
||||
lenSuffix := runewidth.StringWidth(suffix)
|
||||
lenShorten := runewidth.StringWidth(shortenSuffix)
|
||||
min := lenPrefix + lenSuffix + lenShorten
|
||||
for i := 0; i < l; i++ {
|
||||
if width < len([]rune(o[i])) {
|
||||
width = len([]rune(o[i]))
|
||||
o[i] = deleteBreakLineCharacters(o[i])
|
||||
|
||||
w := runewidth.StringWidth(o[i])
|
||||
if width < w {
|
||||
width = w
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +132,6 @@ func formatTexts(o []string, max int, prefix, suffix string) (new []string, widt
|
||||
return n, 0
|
||||
}
|
||||
if min >= max {
|
||||
log.Println("[WARN] formatTexts: max is lower than length of prefix and suffix.")
|
||||
return n, 0
|
||||
}
|
||||
if lenPrefix+width+lenSuffix > max {
|
||||
@@ -128,13 +139,15 @@ func formatTexts(o []string, max int, prefix, suffix string) (new []string, widt
|
||||
}
|
||||
|
||||
for i := 0; i < l; i++ {
|
||||
r := []rune(o[i])
|
||||
x := len(r)
|
||||
x := runewidth.StringWidth(o[i])
|
||||
if x <= width {
|
||||
spaces := strings.Repeat(" ", width-x)
|
||||
n[i] = prefix + o[i] + spaces + suffix
|
||||
} else if x > width {
|
||||
n[i] = prefix + string(r[:width-lenShorten]) + shortenSuffix + suffix
|
||||
x := runewidth.Truncate(o[i], width, shortenSuffix)
|
||||
// When calling runewidth.Truncate("您好xxx您好xxx", 11, "...") returns "您好xxx..."
|
||||
// But the length of this result is 10. So we need fill right using runewidth.FillRight.
|
||||
n[i] = prefix + runewidth.FillRight(x, width) + suffix
|
||||
}
|
||||
}
|
||||
return n, lenPrefix + width + lenSuffix
|
||||
|
||||
+190
-31
@@ -3,22 +3,46 @@ package prompt
|
||||
import (
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/c-bata/go-prompt/internal/bisect"
|
||||
istrings "github.com/c-bata/go-prompt/internal/strings"
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
// Document has text displayed in terminal and cursor position.
|
||||
type Document struct {
|
||||
Text string
|
||||
CursorPosition int
|
||||
Text string
|
||||
// This represents a index in a rune array of Document.Text.
|
||||
// So if Document is "日本(cursor)語", cursorPosition is 2.
|
||||
// But DisplayedCursorPosition returns 4 because '日' and '本' are double width characters.
|
||||
cursorPosition int
|
||||
lastKey Key
|
||||
}
|
||||
|
||||
// NewDocument return the new empty document.
|
||||
func NewDocument() *Document {
|
||||
return &Document{
|
||||
Text: "",
|
||||
CursorPosition: 0,
|
||||
cursorPosition: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// LastKeyStroke return the last key pressed in this document.
|
||||
func (d *Document) LastKeyStroke() Key {
|
||||
return d.lastKey
|
||||
}
|
||||
|
||||
// DisplayCursorPosition returns the cursor position on rendered text on terminal emulators.
|
||||
// So if Document is "日本(cursor)語", DisplayedCursorPosition returns 4 because '日' and '本' are double width characters.
|
||||
func (d *Document) DisplayCursorPosition() int {
|
||||
var position int
|
||||
runes := []rune(d.Text)[:d.cursorPosition]
|
||||
for i := range runes {
|
||||
position += runewidth.RuneWidth(runes[i])
|
||||
}
|
||||
return position
|
||||
}
|
||||
|
||||
// GetCharRelativeToCursor return character relative to cursor position, or empty string
|
||||
func (d *Document) GetCharRelativeToCursor(offset int) (r rune) {
|
||||
s := d.Text
|
||||
@@ -27,7 +51,7 @@ func (d *Document) GetCharRelativeToCursor(offset int) (r rune) {
|
||||
for len(s) > 0 {
|
||||
cnt++
|
||||
r, size := utf8.DecodeRuneInString(s)
|
||||
if cnt == d.CursorPosition+offset {
|
||||
if cnt == d.cursorPosition+offset {
|
||||
return r
|
||||
}
|
||||
s = s[size:]
|
||||
@@ -38,13 +62,13 @@ func (d *Document) GetCharRelativeToCursor(offset int) (r rune) {
|
||||
// TextBeforeCursor returns the text before the cursor.
|
||||
func (d *Document) TextBeforeCursor() string {
|
||||
r := []rune(d.Text)
|
||||
return string(r[:d.CursorPosition])
|
||||
return string(r[:d.cursorPosition])
|
||||
}
|
||||
|
||||
// TextAfterCursor returns the text after the cursor.
|
||||
func (d *Document) TextAfterCursor() string {
|
||||
r := []rune(d.Text)
|
||||
return string(r[d.CursorPosition:])
|
||||
return string(r[d.cursorPosition:])
|
||||
}
|
||||
|
||||
// GetWordBeforeCursor returns the word before the cursor.
|
||||
@@ -54,6 +78,13 @@ func (d *Document) GetWordBeforeCursor() string {
|
||||
return x[d.FindStartOfPreviousWord():]
|
||||
}
|
||||
|
||||
// GetWordAfterCursor returns the word after the cursor.
|
||||
// If we have whitespace after the cursor this returns an empty string.
|
||||
func (d *Document) GetWordAfterCursor() string {
|
||||
x := d.TextAfterCursor()
|
||||
return x[:d.FindEndOfCurrentWord()]
|
||||
}
|
||||
|
||||
// GetWordBeforeCursorWithSpace returns the word before the cursor.
|
||||
// Unlike GetWordBeforeCursor, it returns string containing space
|
||||
func (d *Document) GetWordBeforeCursorWithSpace() string {
|
||||
@@ -61,16 +92,46 @@ func (d *Document) GetWordBeforeCursorWithSpace() string {
|
||||
return x[d.FindStartOfPreviousWordWithSpace():]
|
||||
}
|
||||
|
||||
// FindStartOfPreviousWord returns an index relative to the cursor position
|
||||
// pointing to the start of the previous word. Return `None` if nothing was found.
|
||||
func (d *Document) FindStartOfPreviousWord() int {
|
||||
// Reverse the text before the cursor, in order to do an efficient backwards search.
|
||||
// GetWordAfterCursorWithSpace returns the word after the cursor.
|
||||
// Unlike GetWordAfterCursor, it returns string containing space
|
||||
func (d *Document) GetWordAfterCursorWithSpace() string {
|
||||
x := d.TextAfterCursor()
|
||||
return x[:d.FindEndOfCurrentWordWithSpace()]
|
||||
}
|
||||
|
||||
// GetWordBeforeCursorUntilSeparator returns the text before the cursor until next separator.
|
||||
func (d *Document) GetWordBeforeCursorUntilSeparator(sep string) string {
|
||||
x := d.TextBeforeCursor()
|
||||
l := len(x)
|
||||
for i := l; i > 0; i-- {
|
||||
if x[i-1:i] == " " {
|
||||
return i
|
||||
}
|
||||
return x[d.FindStartOfPreviousWordUntilSeparator(sep):]
|
||||
}
|
||||
|
||||
// GetWordAfterCursorUntilSeparator returns the text after the cursor until next separator.
|
||||
func (d *Document) GetWordAfterCursorUntilSeparator(sep string) string {
|
||||
x := d.TextAfterCursor()
|
||||
return x[:d.FindEndOfCurrentWordUntilSeparator(sep)]
|
||||
}
|
||||
|
||||
// GetWordBeforeCursorUntilSeparatorIgnoreNextToCursor returns the word before the cursor.
|
||||
// Unlike GetWordBeforeCursor, it returns string containing space
|
||||
func (d *Document) GetWordBeforeCursorUntilSeparatorIgnoreNextToCursor(sep string) string {
|
||||
x := d.TextBeforeCursor()
|
||||
return x[d.FindStartOfPreviousWordUntilSeparatorIgnoreNextToCursor(sep):]
|
||||
}
|
||||
|
||||
// GetWordAfterCursorUntilSeparatorIgnoreNextToCursor returns the word after the cursor.
|
||||
// Unlike GetWordAfterCursor, it returns string containing space
|
||||
func (d *Document) GetWordAfterCursorUntilSeparatorIgnoreNextToCursor(sep string) string {
|
||||
x := d.TextAfterCursor()
|
||||
return x[:d.FindEndOfCurrentWordUntilSeparatorIgnoreNextToCursor(sep)]
|
||||
}
|
||||
|
||||
// FindStartOfPreviousWord returns an index relative to the cursor position
|
||||
// pointing to the start of the previous word. Return 0 if nothing was found.
|
||||
func (d *Document) FindStartOfPreviousWord() int {
|
||||
x := d.TextBeforeCursor()
|
||||
i := strings.LastIndexByte(x, ' ')
|
||||
if i != -1 {
|
||||
return i + 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -78,21 +139,119 @@ func (d *Document) FindStartOfPreviousWord() int {
|
||||
// FindStartOfPreviousWordWithSpace is almost the same as FindStartOfPreviousWord.
|
||||
// The only difference is to ignore contiguous spaces.
|
||||
func (d *Document) FindStartOfPreviousWordWithSpace() int {
|
||||
// Reverse the text before the cursor, in order to do an efficient backwards search.
|
||||
x := d.TextBeforeCursor()
|
||||
l := len(x)
|
||||
appear := false
|
||||
for i := l; i > 0; i-- {
|
||||
if x[i-1:i] != " " {
|
||||
appear = true
|
||||
}
|
||||
if x[i-1:i] == " " && appear {
|
||||
return i
|
||||
}
|
||||
end := istrings.LastIndexNotByte(x, ' ')
|
||||
if end == -1 {
|
||||
return 0
|
||||
}
|
||||
|
||||
start := strings.LastIndexByte(x[:end], ' ')
|
||||
if start == -1 {
|
||||
return 0
|
||||
}
|
||||
return start + 1
|
||||
}
|
||||
|
||||
// FindStartOfPreviousWordUntilSeparator is almost the same as FindStartOfPreviousWord.
|
||||
// But this can specify Separator. Return 0 if nothing was found.
|
||||
func (d *Document) FindStartOfPreviousWordUntilSeparator(sep string) int {
|
||||
if sep == "" {
|
||||
return d.FindStartOfPreviousWord()
|
||||
}
|
||||
|
||||
x := d.TextBeforeCursor()
|
||||
i := strings.LastIndexAny(x, sep)
|
||||
if i != -1 {
|
||||
return i + 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// FindStartOfPreviousWordUntilSeparatorIgnoreNextToCursor is almost the same as FindStartOfPreviousWordWithSpace.
|
||||
// But this can specify Separator. Return 0 if nothing was found.
|
||||
func (d *Document) FindStartOfPreviousWordUntilSeparatorIgnoreNextToCursor(sep string) int {
|
||||
if sep == "" {
|
||||
return d.FindStartOfPreviousWordWithSpace()
|
||||
}
|
||||
|
||||
x := d.TextBeforeCursor()
|
||||
end := istrings.LastIndexNotAny(x, sep)
|
||||
if end == -1 {
|
||||
return 0
|
||||
}
|
||||
start := strings.LastIndexAny(x[:end], sep)
|
||||
if start == -1 {
|
||||
return 0
|
||||
}
|
||||
return start + 1
|
||||
}
|
||||
|
||||
// FindEndOfCurrentWord returns an index relative to the cursor position.
|
||||
// pointing to the end of the current word. Return 0 if nothing was found.
|
||||
func (d *Document) FindEndOfCurrentWord() int {
|
||||
x := d.TextAfterCursor()
|
||||
i := strings.IndexByte(x, ' ')
|
||||
if i != -1 {
|
||||
return i
|
||||
}
|
||||
return len(x)
|
||||
}
|
||||
|
||||
// FindEndOfCurrentWordWithSpace is almost the same as FindEndOfCurrentWord.
|
||||
// The only difference is to ignore contiguous spaces.
|
||||
func (d *Document) FindEndOfCurrentWordWithSpace() int {
|
||||
x := d.TextAfterCursor()
|
||||
|
||||
start := istrings.IndexNotByte(x, ' ')
|
||||
if start == -1 {
|
||||
return len(x)
|
||||
}
|
||||
|
||||
end := strings.IndexByte(x[start:], ' ')
|
||||
if end == -1 {
|
||||
return len(x)
|
||||
}
|
||||
|
||||
return start + end
|
||||
}
|
||||
|
||||
// FindEndOfCurrentWordUntilSeparator is almost the same as FindEndOfCurrentWord.
|
||||
// But this can specify Separator. Return 0 if nothing was found.
|
||||
func (d *Document) FindEndOfCurrentWordUntilSeparator(sep string) int {
|
||||
if sep == "" {
|
||||
return d.FindEndOfCurrentWord()
|
||||
}
|
||||
|
||||
x := d.TextAfterCursor()
|
||||
i := strings.IndexAny(x, sep)
|
||||
if i != -1 {
|
||||
return i
|
||||
}
|
||||
return len(x)
|
||||
}
|
||||
|
||||
// FindEndOfCurrentWordUntilSeparatorIgnoreNextToCursor is almost the same as FindEndOfCurrentWordWithSpace.
|
||||
// But this can specify Separator. Return 0 if nothing was found.
|
||||
func (d *Document) FindEndOfCurrentWordUntilSeparatorIgnoreNextToCursor(sep string) int {
|
||||
if sep == "" {
|
||||
return d.FindEndOfCurrentWordWithSpace()
|
||||
}
|
||||
|
||||
x := d.TextAfterCursor()
|
||||
|
||||
start := istrings.IndexNotAny(x, sep)
|
||||
if start == -1 {
|
||||
return len(x)
|
||||
}
|
||||
|
||||
end := strings.IndexAny(x[start:], sep)
|
||||
if end == -1 {
|
||||
return len(x)
|
||||
}
|
||||
|
||||
return start + end
|
||||
}
|
||||
|
||||
// CurrentLineBeforeCursor returns the text from the start of the line until the cursor.
|
||||
func (d *Document) CurrentLineBeforeCursor() string {
|
||||
s := strings.Split(d.TextBeforeCursor(), "\n")
|
||||
@@ -140,14 +299,14 @@ func (d *Document) lineStartIndexes() []int {
|
||||
// the first character on that line.
|
||||
func (d *Document) findLineStartIndex(index int) (pos int, lineStartIndex int) {
|
||||
indexes := d.lineStartIndexes()
|
||||
pos = BisectRight(indexes, index) - 1
|
||||
pos = bisect.Right(indexes, index) - 1
|
||||
lineStartIndex = indexes[pos]
|
||||
return
|
||||
}
|
||||
|
||||
// CursorPositionRow returns the current row. (0-based.)
|
||||
func (d *Document) CursorPositionRow() (row int) {
|
||||
row, _ = d.findLineStartIndex(d.CursorPosition)
|
||||
row, _ = d.findLineStartIndex(d.cursorPosition)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -155,8 +314,8 @@ func (d *Document) CursorPositionRow() (row int) {
|
||||
func (d *Document) CursorPositionCol() (col int) {
|
||||
// Don't use self.text_before_cursor to calculate this. Creating substrings
|
||||
// and splitting is too expensive for getting the cursor position.
|
||||
_, index := d.findLineStartIndex(d.CursorPosition)
|
||||
col = d.CursorPosition - index
|
||||
_, index := d.findLineStartIndex(d.cursorPosition)
|
||||
col = d.cursorPosition - index
|
||||
return
|
||||
}
|
||||
|
||||
@@ -196,7 +355,7 @@ func (d *Document) GetCursorUpPosition(count int, preferredColumn int) int {
|
||||
if row < 0 {
|
||||
row = 0
|
||||
}
|
||||
return d.TranslateRowColToIndex(row, col) - d.CursorPosition
|
||||
return d.TranslateRowColToIndex(row, col) - d.cursorPosition
|
||||
}
|
||||
|
||||
// GetCursorDownPosition return the relative cursor position (character index) where we would be if the
|
||||
@@ -209,7 +368,7 @@ func (d *Document) GetCursorDownPosition(count int, preferredColumn int) int {
|
||||
col = preferredColumn
|
||||
}
|
||||
row := d.CursorPositionRow() + count
|
||||
return d.TranslateRowColToIndex(row, col) - d.CursorPosition
|
||||
return d.TranslateRowColToIndex(row, col) - d.cursorPosition
|
||||
}
|
||||
|
||||
// Lines returns the array of all the lines.
|
||||
|
||||
+5
-3
@@ -1,5 +1,7 @@
|
||||
package prompt
|
||||
|
||||
import "github.com/c-bata/go-prompt/internal/debug"
|
||||
|
||||
/*
|
||||
|
||||
========
|
||||
@@ -110,9 +112,9 @@ var emacsKeyBindings = []KeyBind{
|
||||
{
|
||||
Key: ControlL,
|
||||
Fn: func(buf *Buffer) {
|
||||
out := NewStandardOutputWriter()
|
||||
out.EraseScreen()
|
||||
out.CursorGoTo(0, 0)
|
||||
consoleWriter.EraseScreen()
|
||||
consoleWriter.CursorGoTo(0, 0)
|
||||
debug.AssertNoError(consoleWriter.Flush())
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
+41
-42
@@ -7,65 +7,64 @@ type Filter func([]Suggest, string, bool) []Suggest
|
||||
|
||||
// FilterHasPrefix checks whether the string completions.Text begins with sub.
|
||||
func FilterHasPrefix(completions []Suggest, sub string, ignoreCase bool) []Suggest {
|
||||
if sub == "" {
|
||||
return completions
|
||||
}
|
||||
if ignoreCase {
|
||||
sub = strings.ToUpper(sub)
|
||||
}
|
||||
|
||||
ret := make([]Suggest, 0, len(completions))
|
||||
for i := range completions {
|
||||
c := completions[i].Text
|
||||
if ignoreCase {
|
||||
c = strings.ToUpper(c)
|
||||
}
|
||||
if strings.HasPrefix(c, sub) {
|
||||
ret = append(ret, completions[i])
|
||||
}
|
||||
}
|
||||
return ret
|
||||
return filterSuggestions(completions, sub, ignoreCase, strings.HasPrefix)
|
||||
}
|
||||
|
||||
// FilterHasSuffix checks whether the completion.Text ends with sub.
|
||||
func FilterHasSuffix(completions []Suggest, sub string, ignoreCase bool) []Suggest {
|
||||
if sub == "" {
|
||||
return completions
|
||||
}
|
||||
if ignoreCase {
|
||||
sub = strings.ToUpper(sub)
|
||||
}
|
||||
|
||||
ret := make([]Suggest, 0, len(completions))
|
||||
for i := range completions {
|
||||
c := completions[i].Text
|
||||
if ignoreCase {
|
||||
c = strings.ToUpper(c)
|
||||
}
|
||||
if strings.HasSuffix(c, sub) {
|
||||
ret = append(ret, completions[i])
|
||||
}
|
||||
}
|
||||
return ret
|
||||
return filterSuggestions(completions, sub, ignoreCase, strings.HasSuffix)
|
||||
}
|
||||
|
||||
// FilterContains checks whether the completion.Text contains sub.
|
||||
func FilterContains(completions []Suggest, sub string, ignoreCase bool) []Suggest {
|
||||
return filterSuggestions(completions, sub, ignoreCase, strings.Contains)
|
||||
}
|
||||
|
||||
// FilterFuzzy checks whether the completion.Text fuzzy matches sub.
|
||||
// Fuzzy searching for "dog" is equivalent to "*d*o*g*". This search term
|
||||
// would match, for example, "Good food is gone"
|
||||
// ^ ^ ^
|
||||
func FilterFuzzy(completions []Suggest, sub string, ignoreCase bool) []Suggest {
|
||||
return filterSuggestions(completions, sub, ignoreCase, fuzzyMatch)
|
||||
}
|
||||
|
||||
func fuzzyMatch(s, sub string) bool {
|
||||
sChars := []rune(s)
|
||||
sIdx := 0
|
||||
|
||||
// https://staticcheck.io/docs/checks#S1029
|
||||
for _, c := range sub {
|
||||
found := false
|
||||
for ; sIdx < len(sChars); sIdx++ {
|
||||
if sChars[sIdx] == c {
|
||||
found = true
|
||||
sIdx++
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func filterSuggestions(suggestions []Suggest, sub string, ignoreCase bool, function func(string, string) bool) []Suggest {
|
||||
if sub == "" {
|
||||
return completions
|
||||
return suggestions
|
||||
}
|
||||
if ignoreCase {
|
||||
sub = strings.ToUpper(sub)
|
||||
}
|
||||
|
||||
ret := make([]Suggest, 0, len(completions))
|
||||
for i := range completions {
|
||||
c := completions[i].Text
|
||||
ret := make([]Suggest, 0, len(suggestions))
|
||||
for i := range suggestions {
|
||||
c := suggestions[i].Text
|
||||
if ignoreCase {
|
||||
c = strings.ToUpper(c)
|
||||
}
|
||||
if strings.Contains(c, sub) {
|
||||
ret = append(ret, completions[i])
|
||||
if function(c, sub) {
|
||||
ret = append(ret, suggestions[i])
|
||||
}
|
||||
}
|
||||
return ret
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
module github.com/c-bata/go-prompt
|
||||
|
||||
require (
|
||||
github.com/mattn/go-colorable v0.0.9
|
||||
github.com/mattn/go-isatty v0.0.3 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.3
|
||||
github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a
|
||||
github.com/pkg/term v1.0.0
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299
|
||||
)
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
|
||||
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a h1:8TGB3DFRNl06DB1Q6zBX+I7FDoCUZY2fmMS9WGUIIpw=
|
||||
github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
|
||||
github.com/pkg/term v0.0.0-20180423043932-cda20d4ac917 h1:BinR73QvQveJdQ8uYZK/8MOjLADpZbI2qs/2+5rnhzQ=
|
||||
github.com/pkg/term v0.0.0-20180423043932-cda20d4ac917/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ=
|
||||
github.com/pkg/term v1.0.0 h1:raTSNJjic7X4n89hhPyLOsgRYczmr3MyezRierWCTa8=
|
||||
github.com/pkg/term v1.0.0/go.mod h1:6rk0zrj6TXf8MR5fdVFsZMeGM2lxe3tUFLNBRlwX+dk=
|
||||
golang.org/x/sys v0.0.0-20180620133508-ad87a3a340fa h1:MUO6aP6ViFfqImh/3zU3O6QX3W2hFRzkkuCIQuUCOsM=
|
||||
golang.org/x/sys v0.0.0-20180620133508-ad87a3a340fa/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
+158
-31
@@ -1,42 +1,169 @@
|
||||
package prompt
|
||||
|
||||
func dummyExecutor(in string) { return }
|
||||
import "bytes"
|
||||
|
||||
// Input get the input data from the user and return it.
|
||||
func Input(prefix string, completer Completer, opts ...Option) string {
|
||||
pt := New(dummyExecutor, completer)
|
||||
pt.renderer.prefixTextColor = DefaultColor
|
||||
pt.renderer.prefix = prefix
|
||||
// WinSize represents the width and height of terminal.
|
||||
type WinSize struct {
|
||||
Row uint16
|
||||
Col uint16
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
if err := opt(pt); err != nil {
|
||||
panic(err)
|
||||
// ConsoleParser is an interface to abstract input layer.
|
||||
type ConsoleParser interface {
|
||||
// Setup should be called before starting input
|
||||
Setup() error
|
||||
// TearDown should be called after stopping input
|
||||
TearDown() error
|
||||
// GetWinSize returns WinSize object to represent width and height of terminal.
|
||||
GetWinSize() *WinSize
|
||||
// Read returns byte array.
|
||||
Read() ([]byte, error)
|
||||
}
|
||||
|
||||
// GetKey returns Key correspond to input byte codes.
|
||||
func GetKey(b []byte) Key {
|
||||
for _, k := range ASCIISequences {
|
||||
if bytes.Equal(k.ASCIICode, b) {
|
||||
return k.Key
|
||||
}
|
||||
}
|
||||
return pt.Input()
|
||||
return NotDefined
|
||||
}
|
||||
|
||||
// Choose to the shortcut of input function to select from string array.
|
||||
func Choose(prefix string, choices []string, opts ...Option) string {
|
||||
completer := newChoiceCompleter(choices, FilterHasPrefix)
|
||||
pt := New(dummyExecutor, completer)
|
||||
pt.renderer.prefixTextColor = DefaultColor
|
||||
pt.renderer.prefix = prefix
|
||||
// ASCIISequences holds mappings of the key and byte array.
|
||||
var ASCIISequences = []*ASCIICode{
|
||||
{Key: Escape, ASCIICode: []byte{0x1b}},
|
||||
|
||||
for _, opt := range opts {
|
||||
if err := opt(pt); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return pt.Input()
|
||||
}
|
||||
{Key: ControlSpace, ASCIICode: []byte{0x00}},
|
||||
{Key: ControlA, ASCIICode: []byte{0x1}},
|
||||
{Key: ControlB, ASCIICode: []byte{0x2}},
|
||||
{Key: ControlC, ASCIICode: []byte{0x3}},
|
||||
{Key: ControlD, ASCIICode: []byte{0x4}},
|
||||
{Key: ControlE, ASCIICode: []byte{0x5}},
|
||||
{Key: ControlF, ASCIICode: []byte{0x6}},
|
||||
{Key: ControlG, ASCIICode: []byte{0x7}},
|
||||
{Key: ControlH, ASCIICode: []byte{0x8}},
|
||||
//{Key: ControlI, ASCIICode: []byte{0x9}},
|
||||
//{Key: ControlJ, ASCIICode: []byte{0xa}},
|
||||
{Key: ControlK, ASCIICode: []byte{0xb}},
|
||||
{Key: ControlL, ASCIICode: []byte{0xc}},
|
||||
{Key: ControlM, ASCIICode: []byte{0xd}},
|
||||
{Key: ControlN, ASCIICode: []byte{0xe}},
|
||||
{Key: ControlO, ASCIICode: []byte{0xf}},
|
||||
{Key: ControlP, ASCIICode: []byte{0x10}},
|
||||
{Key: ControlQ, ASCIICode: []byte{0x11}},
|
||||
{Key: ControlR, ASCIICode: []byte{0x12}},
|
||||
{Key: ControlS, ASCIICode: []byte{0x13}},
|
||||
{Key: ControlT, ASCIICode: []byte{0x14}},
|
||||
{Key: ControlU, ASCIICode: []byte{0x15}},
|
||||
{Key: ControlV, ASCIICode: []byte{0x16}},
|
||||
{Key: ControlW, ASCIICode: []byte{0x17}},
|
||||
{Key: ControlX, ASCIICode: []byte{0x18}},
|
||||
{Key: ControlY, ASCIICode: []byte{0x19}},
|
||||
{Key: ControlZ, ASCIICode: []byte{0x1a}},
|
||||
|
||||
func newChoiceCompleter(choices []string, filter Filter) Completer {
|
||||
s := make([]Suggest, len(choices))
|
||||
for i := range choices {
|
||||
s[i] = Suggest{Text: choices[i]}
|
||||
}
|
||||
return func(x Document) []Suggest {
|
||||
return filter(s, x.GetWordBeforeCursor(), true)
|
||||
}
|
||||
{Key: ControlBackslash, ASCIICode: []byte{0x1c}},
|
||||
{Key: ControlSquareClose, ASCIICode: []byte{0x1d}},
|
||||
{Key: ControlCircumflex, ASCIICode: []byte{0x1e}},
|
||||
{Key: ControlUnderscore, ASCIICode: []byte{0x1f}},
|
||||
{Key: Backspace, ASCIICode: []byte{0x7f}},
|
||||
|
||||
{Key: Up, ASCIICode: []byte{0x1b, 0x5b, 0x41}},
|
||||
{Key: Down, ASCIICode: []byte{0x1b, 0x5b, 0x42}},
|
||||
{Key: Right, ASCIICode: []byte{0x1b, 0x5b, 0x43}},
|
||||
{Key: Left, ASCIICode: []byte{0x1b, 0x5b, 0x44}},
|
||||
{Key: Home, ASCIICode: []byte{0x1b, 0x5b, 0x48}},
|
||||
{Key: Home, ASCIICode: []byte{0x1b, 0x30, 0x48}},
|
||||
{Key: End, ASCIICode: []byte{0x1b, 0x5b, 0x46}},
|
||||
{Key: End, ASCIICode: []byte{0x1b, 0x30, 0x46}},
|
||||
|
||||
{Key: Enter, ASCIICode: []byte{0xa}},
|
||||
{Key: Delete, ASCIICode: []byte{0x1b, 0x5b, 0x33, 0x7e}},
|
||||
{Key: ShiftDelete, ASCIICode: []byte{0x1b, 0x5b, 0x33, 0x3b, 0x32, 0x7e}},
|
||||
{Key: ControlDelete, ASCIICode: []byte{0x1b, 0x5b, 0x33, 0x3b, 0x35, 0x7e}},
|
||||
{Key: Home, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x7e}},
|
||||
{Key: End, ASCIICode: []byte{0x1b, 0x5b, 0x34, 0x7e}},
|
||||
{Key: PageUp, ASCIICode: []byte{0x1b, 0x5b, 0x35, 0x7e}},
|
||||
{Key: PageDown, ASCIICode: []byte{0x1b, 0x5b, 0x36, 0x7e}},
|
||||
{Key: Home, ASCIICode: []byte{0x1b, 0x5b, 0x37, 0x7e}},
|
||||
{Key: End, ASCIICode: []byte{0x1b, 0x5b, 0x38, 0x7e}},
|
||||
{Key: Tab, ASCIICode: []byte{0x9}},
|
||||
{Key: BackTab, ASCIICode: []byte{0x1b, 0x5b, 0x5a}},
|
||||
{Key: Insert, ASCIICode: []byte{0x1b, 0x5b, 0x32, 0x7e}},
|
||||
|
||||
{Key: F1, ASCIICode: []byte{0x1b, 0x4f, 0x50}},
|
||||
{Key: F2, ASCIICode: []byte{0x1b, 0x4f, 0x51}},
|
||||
{Key: F3, ASCIICode: []byte{0x1b, 0x4f, 0x52}},
|
||||
{Key: F4, ASCIICode: []byte{0x1b, 0x4f, 0x53}},
|
||||
|
||||
{Key: F1, ASCIICode: []byte{0x1b, 0x4f, 0x50, 0x41}}, // Linux console
|
||||
{Key: F2, ASCIICode: []byte{0x1b, 0x5b, 0x5b, 0x42}}, // Linux console
|
||||
{Key: F3, ASCIICode: []byte{0x1b, 0x5b, 0x5b, 0x43}}, // Linux console
|
||||
{Key: F4, ASCIICode: []byte{0x1b, 0x5b, 0x5b, 0x44}}, // Linux console
|
||||
{Key: F5, ASCIICode: []byte{0x1b, 0x5b, 0x5b, 0x45}}, // Linux console
|
||||
|
||||
{Key: F1, ASCIICode: []byte{0x1b, 0x5b, 0x11, 0x7e}}, // rxvt-unicode
|
||||
{Key: F2, ASCIICode: []byte{0x1b, 0x5b, 0x12, 0x7e}}, // rxvt-unicode
|
||||
{Key: F3, ASCIICode: []byte{0x1b, 0x5b, 0x13, 0x7e}}, // rxvt-unicode
|
||||
{Key: F4, ASCIICode: []byte{0x1b, 0x5b, 0x14, 0x7e}}, // rxvt-unicode
|
||||
|
||||
{Key: F5, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x35, 0x7e}},
|
||||
{Key: F6, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x37, 0x7e}},
|
||||
{Key: F7, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x38, 0x7e}},
|
||||
{Key: F8, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x39, 0x7e}},
|
||||
{Key: F9, ASCIICode: []byte{0x1b, 0x5b, 0x32, 0x30, 0x7e}},
|
||||
{Key: F10, ASCIICode: []byte{0x1b, 0x5b, 0x32, 0x31, 0x7e}},
|
||||
{Key: F11, ASCIICode: []byte{0x1b, 0x5b, 0x32, 0x32, 0x7e}},
|
||||
{Key: F12, ASCIICode: []byte{0x1b, 0x5b, 0x32, 0x34, 0x7e, 0x8}},
|
||||
{Key: F13, ASCIICode: []byte{0x1b, 0x5b, 0x25, 0x7e}},
|
||||
{Key: F14, ASCIICode: []byte{0x1b, 0x5b, 0x26, 0x7e}},
|
||||
{Key: F15, ASCIICode: []byte{0x1b, 0x5b, 0x28, 0x7e}},
|
||||
{Key: F16, ASCIICode: []byte{0x1b, 0x5b, 0x29, 0x7e}},
|
||||
{Key: F17, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x7e}},
|
||||
{Key: F18, ASCIICode: []byte{0x1b, 0x5b, 0x32, 0x7e}},
|
||||
{Key: F19, ASCIICode: []byte{0x1b, 0x5b, 0x33, 0x7e}},
|
||||
{Key: F20, ASCIICode: []byte{0x1b, 0x5b, 0x34, 0x7e}},
|
||||
|
||||
// Xterm
|
||||
{Key: F13, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x32, 0x50}},
|
||||
{Key: F14, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x32, 0x51}},
|
||||
// &ASCIICode{Key: F15, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x32, 0x52}}, // Conflicts with CPR response
|
||||
{Key: F16, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x32, 0x52}},
|
||||
{Key: F17, ASCIICode: []byte{0x1b, 0x5b, 0x15, 0x3b, 0x32, 0x7e}},
|
||||
{Key: F18, ASCIICode: []byte{0x1b, 0x5b, 0x17, 0x3b, 0x32, 0x7e}},
|
||||
{Key: F19, ASCIICode: []byte{0x1b, 0x5b, 0x18, 0x3b, 0x32, 0x7e}},
|
||||
{Key: F20, ASCIICode: []byte{0x1b, 0x5b, 0x19, 0x3b, 0x32, 0x7e}},
|
||||
{Key: F21, ASCIICode: []byte{0x1b, 0x5b, 0x20, 0x3b, 0x32, 0x7e}},
|
||||
{Key: F22, ASCIICode: []byte{0x1b, 0x5b, 0x21, 0x3b, 0x32, 0x7e}},
|
||||
{Key: F23, ASCIICode: []byte{0x1b, 0x5b, 0x23, 0x3b, 0x32, 0x7e}},
|
||||
{Key: F24, ASCIICode: []byte{0x1b, 0x5b, 0x24, 0x3b, 0x32, 0x7e}},
|
||||
|
||||
{Key: ControlUp, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x35, 0x41}},
|
||||
{Key: ControlDown, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x35, 0x42}},
|
||||
{Key: ControlRight, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x35, 0x43}},
|
||||
{Key: ControlLeft, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x35, 0x44}},
|
||||
|
||||
{Key: ShiftUp, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x32, 0x41}},
|
||||
{Key: ShiftDown, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x32, 0x42}},
|
||||
{Key: ShiftRight, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x32, 0x43}},
|
||||
{Key: ShiftLeft, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x32, 0x44}},
|
||||
|
||||
// Tmux sends following keystrokes when control+arrow is pressed, but for
|
||||
// Emacs ansi-term sends the same sequences for normal arrow keys. Consider
|
||||
// it a normal arrow press, because that's more important.
|
||||
{Key: Up, ASCIICode: []byte{0x1b, 0x4f, 0x41}},
|
||||
{Key: Down, ASCIICode: []byte{0x1b, 0x4f, 0x42}},
|
||||
{Key: Right, ASCIICode: []byte{0x1b, 0x4f, 0x43}},
|
||||
{Key: Left, ASCIICode: []byte{0x1b, 0x4f, 0x44}},
|
||||
|
||||
{Key: ControlUp, ASCIICode: []byte{0x1b, 0x5b, 0x35, 0x41}},
|
||||
{Key: ControlDown, ASCIICode: []byte{0x1b, 0x5b, 0x35, 0x42}},
|
||||
{Key: ControlRight, ASCIICode: []byte{0x1b, 0x5b, 0x35, 0x43}},
|
||||
{Key: ControlLeft, ASCIICode: []byte{0x1b, 0x5b, 0x35, 0x44}},
|
||||
|
||||
{Key: ControlRight, ASCIICode: []byte{0x1b, 0x5b, 0x4f, 0x63}}, // rxvt
|
||||
{Key: ControlLeft, ASCIICode: []byte{0x1b, 0x5b, 0x4f, 0x64}}, // rxvt
|
||||
|
||||
{Key: Ignore, ASCIICode: []byte{0x1b, 0x5b, 0x45}}, // Xterm
|
||||
{Key: Ignore, ASCIICode: []byte{0x1b, 0x5b, 0x46}}, // Linux console
|
||||
}
|
||||
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
// +build !windows
|
||||
|
||||
package prompt
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/c-bata/go-prompt/internal/term"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const maxReadBytes = 1024
|
||||
|
||||
// PosixParser is a ConsoleParser implementation for POSIX environment.
|
||||
type PosixParser struct {
|
||||
fd int
|
||||
origTermios syscall.Termios
|
||||
}
|
||||
|
||||
// Setup should be called before starting input
|
||||
func (t *PosixParser) Setup() error {
|
||||
// Set NonBlocking mode because if syscall.Read block this goroutine, it cannot receive data from stopCh.
|
||||
if err := syscall.SetNonblock(t.fd, true); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := term.SetRaw(t.fd); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TearDown should be called after stopping input
|
||||
func (t *PosixParser) TearDown() error {
|
||||
if err := syscall.SetNonblock(t.fd, false); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := term.Restore(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read returns byte array.
|
||||
func (t *PosixParser) Read() ([]byte, error) {
|
||||
buf := make([]byte, maxReadBytes)
|
||||
n, err := syscall.Read(t.fd, buf)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
return buf[:n], nil
|
||||
}
|
||||
|
||||
// GetWinSize returns WinSize object to represent width and height of terminal.
|
||||
func (t *PosixParser) GetWinSize() *WinSize {
|
||||
ws, err := unix.IoctlGetWinsize(t.fd, unix.TIOCGWINSZ)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &WinSize{
|
||||
Row: ws.Row,
|
||||
Col: ws.Col,
|
||||
}
|
||||
}
|
||||
|
||||
var _ ConsoleParser = &PosixParser{}
|
||||
|
||||
// NewStandardInputParser returns ConsoleParser object to read from stdin.
|
||||
func NewStandardInputParser() *PosixParser {
|
||||
in, err := syscall.Open("/dev/tty", syscall.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &PosixParser{
|
||||
fd: in,
|
||||
}
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
// +build windows
|
||||
|
||||
package prompt
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
"unicode/utf8"
|
||||
"unsafe"
|
||||
|
||||
tty "github.com/mattn/go-tty"
|
||||
)
|
||||
|
||||
const maxReadBytes = 1024
|
||||
|
||||
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
|
||||
var procGetNumberOfConsoleInputEvents = kernel32.NewProc("GetNumberOfConsoleInputEvents")
|
||||
|
||||
// WindowsParser is a ConsoleParser implementation for Win32 console.
|
||||
type WindowsParser struct {
|
||||
tty *tty.TTY
|
||||
}
|
||||
|
||||
// Setup should be called before starting input
|
||||
func (p *WindowsParser) Setup() error {
|
||||
t, err := tty.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.tty = t
|
||||
return nil
|
||||
}
|
||||
|
||||
// TearDown should be called after stopping input
|
||||
func (p *WindowsParser) TearDown() error {
|
||||
return p.tty.Close()
|
||||
}
|
||||
|
||||
// Read returns byte array.
|
||||
func (p *WindowsParser) Read() ([]byte, error) {
|
||||
var ev uint32
|
||||
r0, _, err := procGetNumberOfConsoleInputEvents.Call(p.tty.Input().Fd(), uintptr(unsafe.Pointer(&ev)))
|
||||
if r0 == 0 {
|
||||
return nil, err
|
||||
}
|
||||
if ev == 0 {
|
||||
return nil, errors.New("EAGAIN")
|
||||
}
|
||||
|
||||
r, err := p.tty.ReadRune()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := make([]byte, maxReadBytes)
|
||||
n := utf8.EncodeRune(buf[:], r)
|
||||
for p.tty.Buffered() && n < maxReadBytes {
|
||||
r, err := p.tty.ReadRune()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
n += utf8.EncodeRune(buf[n:], r)
|
||||
}
|
||||
return buf[:n], nil
|
||||
}
|
||||
|
||||
// GetWinSize returns WinSize object to represent width and height of terminal.
|
||||
func (p *WindowsParser) GetWinSize() *WinSize {
|
||||
w, h, err := p.tty.Size()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &WinSize{
|
||||
Row: uint16(h),
|
||||
Col: uint16(w),
|
||||
}
|
||||
}
|
||||
|
||||
// NewStandardInputParser returns ConsoleParser object to read from stdin.
|
||||
func NewStandardInputParser() *WindowsParser {
|
||||
return &WindowsParser{}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package bisect
|
||||
|
||||
import "sort"
|
||||
|
||||
// Right to locate the insertion point for v in a to maintain sorted order.
|
||||
func Right(a []int, v int) int {
|
||||
return bisectRightRange(a, v, 0, len(a))
|
||||
}
|
||||
|
||||
func bisectRightRange(a []int, v int, lo, hi int) int {
|
||||
s := a[lo:hi]
|
||||
return sort.Search(len(s), func(i int) bool {
|
||||
return s[i] > v
|
||||
})
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package debug
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
envAssertPanic = "GO_PROMPT_ENABLE_ASSERT"
|
||||
)
|
||||
|
||||
var (
|
||||
enableAssert bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
if e := os.Getenv(envAssertPanic); e == "true" || e == "1" {
|
||||
enableAssert = true
|
||||
}
|
||||
}
|
||||
|
||||
// Assert ensures expected condition.
|
||||
func Assert(cond bool, msg interface{}) {
|
||||
if cond {
|
||||
return
|
||||
}
|
||||
if enableAssert {
|
||||
panic(msg)
|
||||
}
|
||||
writeWithSync(2, "[ASSERT] "+toString(msg))
|
||||
}
|
||||
|
||||
func toString(v interface{}) string {
|
||||
switch a := v.(type) {
|
||||
case func() string:
|
||||
return a()
|
||||
case string:
|
||||
return a
|
||||
case fmt.Stringer:
|
||||
return a.String()
|
||||
default:
|
||||
return fmt.Sprintf("unexpected type, %t", v)
|
||||
}
|
||||
}
|
||||
|
||||
// AssertNoError ensures err is nil.
|
||||
func AssertNoError(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if enableAssert {
|
||||
panic(err)
|
||||
}
|
||||
writeWithSync(2, "[ASSERT] "+err.Error())
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package debug
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
envEnableLog = "GO_PROMPT_ENABLE_LOG"
|
||||
logFileName = "go-prompt.log"
|
||||
)
|
||||
|
||||
var (
|
||||
logfile *os.File
|
||||
logger *log.Logger
|
||||
)
|
||||
|
||||
func init() {
|
||||
if e := os.Getenv(envEnableLog); e == "true" || e == "1" {
|
||||
var err error
|
||||
logfile, err = os.OpenFile(logFileName, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
|
||||
if err == nil {
|
||||
logger = log.New(logfile, "", log.Llongfile)
|
||||
return
|
||||
}
|
||||
}
|
||||
logger = log.New(ioutil.Discard, "", log.Llongfile)
|
||||
}
|
||||
|
||||
// Teardown to close logfile
|
||||
func Teardown() {
|
||||
if logfile == nil {
|
||||
return
|
||||
}
|
||||
_ = logfile.Close()
|
||||
}
|
||||
|
||||
func writeWithSync(calldepth int, msg string) {
|
||||
calldepth++
|
||||
if logfile == nil {
|
||||
return
|
||||
}
|
||||
_ = logger.Output(calldepth, msg)
|
||||
_ = logfile.Sync() // immediately write msg
|
||||
}
|
||||
|
||||
// Log to output message
|
||||
func Log(msg string) {
|
||||
calldepth := 2
|
||||
writeWithSync(calldepth, msg)
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package strings
|
||||
|
||||
import "unicode/utf8"
|
||||
|
||||
// IndexNotByte is similar with strings.IndexByte but showing the opposite behavior.
|
||||
func IndexNotByte(s string, c byte) int {
|
||||
n := len(s)
|
||||
for i := 0; i < n; i++ {
|
||||
if s[i] != c {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// LastIndexNotByte is similar with strings.LastIndexByte but showing the opposite behavior.
|
||||
func LastIndexNotByte(s string, c byte) int {
|
||||
for i := len(s) - 1; i >= 0; i-- {
|
||||
if s[i] != c {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
type asciiSet [8]uint32
|
||||
|
||||
func (as *asciiSet) notContains(c byte) bool {
|
||||
return (as[c>>5] & (1 << uint(c&31))) == 0
|
||||
}
|
||||
|
||||
func makeASCIISet(chars string) (as asciiSet, ok bool) {
|
||||
for i := 0; i < len(chars); i++ {
|
||||
c := chars[i]
|
||||
if c >= utf8.RuneSelf {
|
||||
return as, false
|
||||
}
|
||||
as[c>>5] |= 1 << uint(c&31)
|
||||
}
|
||||
return as, true
|
||||
}
|
||||
|
||||
// IndexNotAny is similar with strings.IndexAny but showing the opposite behavior.
|
||||
func IndexNotAny(s, chars string) int {
|
||||
if len(chars) > 0 {
|
||||
if len(s) > 8 {
|
||||
if as, isASCII := makeASCIISet(chars); isASCII {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if as.notContains(s[i]) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
LabelFirstLoop:
|
||||
for i, c := range s {
|
||||
for j, m := range chars {
|
||||
if c != m && j == len(chars)-1 {
|
||||
return i
|
||||
} else if c != m {
|
||||
continue
|
||||
} else {
|
||||
continue LabelFirstLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// LastIndexNotAny is similar with strings.LastIndexAny but showing the opposite behavior.
|
||||
func LastIndexNotAny(s, chars string) int {
|
||||
if len(chars) > 0 {
|
||||
if len(s) > 8 {
|
||||
if as, isASCII := makeASCIISet(chars); isASCII {
|
||||
for i := len(s) - 1; i >= 0; i-- {
|
||||
if as.notContains(s[i]) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
}
|
||||
LabelFirstLoop:
|
||||
for i := len(s); i > 0; {
|
||||
r, size := utf8.DecodeLastRuneInString(s[:i])
|
||||
i -= size
|
||||
for j, m := range chars {
|
||||
if r != m && j == len(chars)-1 {
|
||||
return i
|
||||
} else if r != m {
|
||||
continue
|
||||
} else {
|
||||
continue LabelFirstLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// +build !windows
|
||||
|
||||
package term
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/pkg/term/termios"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// SetRaw put terminal into a raw mode
|
||||
func SetRaw(fd int) error {
|
||||
n, err := getOriginalTermios(fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n.Iflag &^= syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK |
|
||||
syscall.ISTRIP | syscall.INLCR | syscall.IGNCR |
|
||||
syscall.ICRNL | syscall.IXON
|
||||
n.Lflag &^= syscall.ECHO | syscall.ICANON | syscall.IEXTEN | syscall.ISIG | syscall.ECHONL
|
||||
n.Cflag &^= syscall.CSIZE | syscall.PARENB
|
||||
n.Cflag |= syscall.CS8 // Set to 8-bit wide. Typical value for displaying characters.
|
||||
n.Cc[syscall.VMIN] = 1
|
||||
n.Cc[syscall.VTIME] = 0
|
||||
|
||||
return termios.Tcsetattr(uintptr(fd), termios.TCSANOW, (*unix.Termios)(&n))
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
// +build !windows
|
||||
|
||||
package term
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/term/termios"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var (
|
||||
saveTermios unix.Termios
|
||||
saveTermiosFD int
|
||||
saveTermiosOnce sync.Once
|
||||
)
|
||||
|
||||
func getOriginalTermios(fd int) (unix.Termios, error) {
|
||||
var err error
|
||||
saveTermiosOnce.Do(func() {
|
||||
saveTermiosFD = fd
|
||||
err = termios.Tcgetattr(uintptr(fd), &saveTermios)
|
||||
})
|
||||
return saveTermios, err
|
||||
}
|
||||
|
||||
// Restore terminal's mode.
|
||||
func Restore() error {
|
||||
o, err := getOriginalTermios(saveTermiosFD)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return termios.Tcsetattr(uintptr(saveTermiosFD), termios.TCSANOW, &o)
|
||||
}
|
||||
+4
@@ -1,6 +1,10 @@
|
||||
// Code generated by hand; DO NOT EDIT.
|
||||
// This is a little bit stupid, but there are many public constants which is no value for writing godoc comment.
|
||||
|
||||
package prompt
|
||||
|
||||
// Key is the type express the key inserted from user.
|
||||
//go:generate stringer -type=Key
|
||||
type Key int
|
||||
|
||||
// ASCIICode is the type contains Key and it's ascii byte array.
|
||||
|
||||
+18
-21
@@ -1,62 +1,59 @@
|
||||
package prompt
|
||||
|
||||
// KeyBindFunc receives buffer and processed it.
|
||||
type KeyBindFunc func(*Buffer)
|
||||
|
||||
// KeyBind represents which key should do what operation.
|
||||
type KeyBind struct {
|
||||
Key Key
|
||||
Fn KeyBindFunc
|
||||
}
|
||||
|
||||
// ASCIICodeBind represents which []byte should do what operation
|
||||
type ASCIICodeBind struct {
|
||||
ASCIICode []byte
|
||||
Fn KeyBindFunc
|
||||
}
|
||||
|
||||
// KeyBindMode to switch a key binding flexibly.
|
||||
type KeyBindMode string
|
||||
|
||||
const (
|
||||
// CommonKeyBind is a mode without any keyboard shortcut
|
||||
CommonKeyBind KeyBindMode = "common"
|
||||
EmacsKeyBind KeyBindMode = "emacs"
|
||||
// EmacsKeyBind is a mode to use emacs-like keyboard shortcut
|
||||
EmacsKeyBind KeyBindMode = "emacs"
|
||||
)
|
||||
|
||||
var commonKeyBindings = []KeyBind{
|
||||
// Go to the End of the line
|
||||
{
|
||||
Key: End,
|
||||
Fn: func(buf *Buffer) {
|
||||
x := []rune(buf.Document().TextAfterCursor())
|
||||
buf.CursorRight(len(x))
|
||||
},
|
||||
Fn: GoLineEnd,
|
||||
},
|
||||
// Go to the beginning of the line
|
||||
{
|
||||
Key: Home,
|
||||
Fn: func(buf *Buffer) {
|
||||
x := []rune(buf.Document().TextBeforeCursor())
|
||||
buf.CursorLeft(len(x))
|
||||
},
|
||||
Fn: GoLineBeginning,
|
||||
},
|
||||
// Delete character under the cursor
|
||||
{
|
||||
Key: Delete,
|
||||
Fn: func(buf *Buffer) {
|
||||
buf.Delete(1)
|
||||
},
|
||||
Fn: DeleteChar,
|
||||
},
|
||||
// Backspace
|
||||
{
|
||||
Key: Backspace,
|
||||
Fn: func(buf *Buffer) {
|
||||
buf.DeleteBeforeCursor(1)
|
||||
},
|
||||
Fn: DeleteBeforeChar,
|
||||
},
|
||||
// Right allow: Forward one character
|
||||
{
|
||||
Key: Right,
|
||||
Fn: func(buf *Buffer) {
|
||||
buf.CursorRight(1)
|
||||
},
|
||||
Fn: GoRightChar,
|
||||
},
|
||||
// Left allow: Backward one character
|
||||
{
|
||||
Key: Left,
|
||||
Fn: func(buf *Buffer) {
|
||||
buf.CursorLeft(1)
|
||||
},
|
||||
Fn: GoLeftChar,
|
||||
},
|
||||
}
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package prompt
|
||||
|
||||
// GoLineEnd Go to the End of the line
|
||||
func GoLineEnd(buf *Buffer) {
|
||||
x := []rune(buf.Document().TextAfterCursor())
|
||||
buf.CursorRight(len(x))
|
||||
}
|
||||
|
||||
// GoLineBeginning Go to the beginning of the line
|
||||
func GoLineBeginning(buf *Buffer) {
|
||||
x := []rune(buf.Document().TextBeforeCursor())
|
||||
buf.CursorLeft(len(x))
|
||||
}
|
||||
|
||||
// DeleteChar Delete character under the cursor
|
||||
func DeleteChar(buf *Buffer) {
|
||||
buf.Delete(1)
|
||||
}
|
||||
|
||||
// DeleteWord Delete word before the cursor
|
||||
func DeleteWord(buf *Buffer) {
|
||||
buf.DeleteBeforeCursor(len([]rune(buf.Document().TextBeforeCursor())) - buf.Document().FindStartOfPreviousWordWithSpace())
|
||||
}
|
||||
|
||||
// DeleteBeforeChar Go to Backspace
|
||||
func DeleteBeforeChar(buf *Buffer) {
|
||||
buf.DeleteBeforeCursor(1)
|
||||
}
|
||||
|
||||
// GoRightChar Forward one character
|
||||
func GoRightChar(buf *Buffer) {
|
||||
buf.CursorRight(1)
|
||||
}
|
||||
|
||||
// GoLeftChar Backward one character
|
||||
func GoLeftChar(buf *Buffer) {
|
||||
buf.CursorLeft(1)
|
||||
}
|
||||
|
||||
// GoRightWord Forward one word
|
||||
func GoRightWord(buf *Buffer) {
|
||||
buf.CursorRight(buf.Document().FindEndOfCurrentWordWithSpace())
|
||||
}
|
||||
|
||||
// GoLeftWord Backward one word
|
||||
func GoLeftWord(buf *Buffer) {
|
||||
buf.CursorLeft(len([]rune(buf.Document().TextBeforeCursor())) - buf.Document().FindStartOfPreviousWordWithSpace())
|
||||
}
|
||||
+78
-2
@@ -12,9 +12,10 @@ func OptionParser(x ConsoleParser) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// OptionWriter to set a custom ConsoleWriter object. An argument should implement ConsoleWriter interace.
|
||||
// OptionWriter to set a custom ConsoleWriter object. An argument should implement ConsoleWriter interface.
|
||||
func OptionWriter(x ConsoleWriter) Option {
|
||||
return func(p *Prompt) error {
|
||||
registerConsoleWriter(x)
|
||||
p.renderer.out = x
|
||||
return nil
|
||||
}
|
||||
@@ -36,6 +37,22 @@ func OptionPrefix(x string) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// OptionInitialBufferText to set the initial buffer text
|
||||
func OptionInitialBufferText(x string) Option {
|
||||
return func(p *Prompt) error {
|
||||
p.buf.InsertText(x, false, true)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// OptionCompletionWordSeparator to set word separators. Enable only ' ' if empty.
|
||||
func OptionCompletionWordSeparator(x string) Option {
|
||||
return func(p *Prompt) error {
|
||||
p.completion.wordSeparator = x
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// OptionLivePrefix to change the prefix dynamically by callback function
|
||||
func OptionLivePrefix(f func() (prefix string, useLivePrefix bool)) Option {
|
||||
return func(p *Prompt) error {
|
||||
@@ -44,6 +61,7 @@ func OptionLivePrefix(f func() (prefix string, useLivePrefix bool)) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// OptionPrefixTextColor change a text color of prefix string
|
||||
func OptionPrefixTextColor(x Color) Option {
|
||||
return func(p *Prompt) error {
|
||||
p.renderer.prefixTextColor = x
|
||||
@@ -51,6 +69,7 @@ func OptionPrefixTextColor(x Color) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// OptionPrefixBackgroundColor to change a background color of prefix string
|
||||
func OptionPrefixBackgroundColor(x Color) Option {
|
||||
return func(p *Prompt) error {
|
||||
p.renderer.prefixBGColor = x
|
||||
@@ -58,6 +77,7 @@ func OptionPrefixBackgroundColor(x Color) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// OptionInputTextColor to change a color of text which is input by user
|
||||
func OptionInputTextColor(x Color) Option {
|
||||
return func(p *Prompt) error {
|
||||
p.renderer.inputTextColor = x
|
||||
@@ -65,6 +85,7 @@ func OptionInputTextColor(x Color) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// OptionInputBGColor to change a color of background which is input by user
|
||||
func OptionInputBGColor(x Color) Option {
|
||||
return func(p *Prompt) error {
|
||||
p.renderer.inputBGColor = x
|
||||
@@ -72,6 +93,7 @@ func OptionInputBGColor(x Color) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// OptionPreviewSuggestionTextColor to change a text color which is completed
|
||||
func OptionPreviewSuggestionTextColor(x Color) Option {
|
||||
return func(p *Prompt) error {
|
||||
p.renderer.previewSuggestionTextColor = x
|
||||
@@ -79,6 +101,7 @@ func OptionPreviewSuggestionTextColor(x Color) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// OptionPreviewSuggestionBGColor to change a background color which is completed
|
||||
func OptionPreviewSuggestionBGColor(x Color) Option {
|
||||
return func(p *Prompt) error {
|
||||
p.renderer.previewSuggestionBGColor = x
|
||||
@@ -86,6 +109,7 @@ func OptionPreviewSuggestionBGColor(x Color) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// OptionSuggestionTextColor to change a text color in drop down suggestions.
|
||||
func OptionSuggestionTextColor(x Color) Option {
|
||||
return func(p *Prompt) error {
|
||||
p.renderer.suggestionTextColor = x
|
||||
@@ -93,6 +117,7 @@ func OptionSuggestionTextColor(x Color) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// OptionSuggestionBGColor change a background color in drop down suggestions.
|
||||
func OptionSuggestionBGColor(x Color) Option {
|
||||
return func(p *Prompt) error {
|
||||
p.renderer.suggestionBGColor = x
|
||||
@@ -100,6 +125,7 @@ func OptionSuggestionBGColor(x Color) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// OptionSelectedSuggestionTextColor to change a text color for completed text which is selected inside suggestions drop down box.
|
||||
func OptionSelectedSuggestionTextColor(x Color) Option {
|
||||
return func(p *Prompt) error {
|
||||
p.renderer.selectedSuggestionTextColor = x
|
||||
@@ -107,6 +133,7 @@ func OptionSelectedSuggestionTextColor(x Color) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// OptionSelectedSuggestionBGColor to change a background color for completed text which is selected inside suggestions drop down box.
|
||||
func OptionSelectedSuggestionBGColor(x Color) Option {
|
||||
return func(p *Prompt) error {
|
||||
p.renderer.selectedSuggestionBGColor = x
|
||||
@@ -114,6 +141,7 @@ func OptionSelectedSuggestionBGColor(x Color) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// OptionDescriptionTextColor to change a background color of description text in drop down suggestions.
|
||||
func OptionDescriptionTextColor(x Color) Option {
|
||||
return func(p *Prompt) error {
|
||||
p.renderer.descriptionTextColor = x
|
||||
@@ -121,6 +149,7 @@ func OptionDescriptionTextColor(x Color) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// OptionDescriptionBGColor to change a background color of description text in drop down suggestions.
|
||||
func OptionDescriptionBGColor(x Color) Option {
|
||||
return func(p *Prompt) error {
|
||||
p.renderer.descriptionBGColor = x
|
||||
@@ -128,6 +157,7 @@ func OptionDescriptionBGColor(x Color) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// OptionSelectedDescriptionTextColor to change a text color of description which is selected inside suggestions drop down box.
|
||||
func OptionSelectedDescriptionTextColor(x Color) Option {
|
||||
return func(p *Prompt) error {
|
||||
p.renderer.selectedDescriptionTextColor = x
|
||||
@@ -135,6 +165,7 @@ func OptionSelectedDescriptionTextColor(x Color) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// OptionSelectedDescriptionBGColor to change a background color of description which is selected inside suggestions drop down box.
|
||||
func OptionSelectedDescriptionBGColor(x Color) Option {
|
||||
return func(p *Prompt) error {
|
||||
p.renderer.selectedDescriptionBGColor = x
|
||||
@@ -142,6 +173,7 @@ func OptionSelectedDescriptionBGColor(x Color) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// OptionScrollbarThumbColor to change a thumb color on scrollbar.
|
||||
func OptionScrollbarThumbColor(x Color) Option {
|
||||
return func(p *Prompt) error {
|
||||
p.renderer.scrollbarThumbColor = x
|
||||
@@ -149,6 +181,7 @@ func OptionScrollbarThumbColor(x Color) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// OptionScrollbarBGColor to change a background color of scrollbar.
|
||||
func OptionScrollbarBGColor(x Color) Option {
|
||||
return func(p *Prompt) error {
|
||||
p.renderer.scrollbarBGColor = x
|
||||
@@ -181,6 +214,14 @@ func OptionSwitchKeyBindMode(m KeyBindMode) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// OptionCompletionOnDown allows for Down arrow key to trigger completion.
|
||||
func OptionCompletionOnDown() Option {
|
||||
return func(p *Prompt) error {
|
||||
p.completionOnDown = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// SwitchKeyBindMode to set a key bind mode.
|
||||
// Deprecated: Please use OptionSwitchKeyBindMode.
|
||||
var SwitchKeyBindMode = OptionSwitchKeyBindMode
|
||||
@@ -193,13 +234,48 @@ func OptionAddKeyBind(b ...KeyBind) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// OptionAddASCIICodeBind to set a custom key bind.
|
||||
func OptionAddASCIICodeBind(b ...ASCIICodeBind) Option {
|
||||
return func(p *Prompt) error {
|
||||
p.ASCIICodeBindings = append(p.ASCIICodeBindings, b...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// OptionShowCompletionAtStart to set completion window is open at start.
|
||||
func OptionShowCompletionAtStart() Option {
|
||||
return func(p *Prompt) error {
|
||||
p.completion.showAtStart = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// OptionBreakLineCallback to run a callback at every break line
|
||||
func OptionBreakLineCallback(fn func(*Document)) Option {
|
||||
return func(p *Prompt) error {
|
||||
p.renderer.breakLineCallback = fn
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// OptionSetExitCheckerOnInput set an exit function which checks if go-prompt exits its Run loop
|
||||
func OptionSetExitCheckerOnInput(fn ExitChecker) Option {
|
||||
return func(p *Prompt) error {
|
||||
p.exitChecker = fn
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// New returns a Prompt with powerful auto-completion.
|
||||
func New(executor Executor, completer Completer, opts ...Option) *Prompt {
|
||||
defaultWriter := NewStdoutWriter()
|
||||
registerConsoleWriter(defaultWriter)
|
||||
|
||||
pt := &Prompt{
|
||||
in: NewStandardInputParser(),
|
||||
renderer: &Render{
|
||||
prefix: "> ",
|
||||
out: NewStandardOutputWriter(),
|
||||
out: defaultWriter,
|
||||
livePrefixCallback: func() (string, bool) { return "", false },
|
||||
prefixTextColor: Blue,
|
||||
prefixBGColor: DefaultColor,
|
||||
|
||||
Generated
Vendored
+60
-20
@@ -1,63 +1,103 @@
|
||||
package prompt
|
||||
|
||||
// WinSize represents the width and height of terminal.
|
||||
type WinSize struct {
|
||||
Row uint16
|
||||
Col uint16
|
||||
import "sync"
|
||||
|
||||
var (
|
||||
consoleWriterMu sync.Mutex
|
||||
consoleWriter ConsoleWriter
|
||||
)
|
||||
|
||||
func registerConsoleWriter(f ConsoleWriter) {
|
||||
consoleWriterMu.Lock()
|
||||
defer consoleWriterMu.Unlock()
|
||||
consoleWriter = f
|
||||
}
|
||||
|
||||
// DisplayAttribute represents display attributes like Blinking, Bold, Italic and so on.
|
||||
type DisplayAttribute int
|
||||
|
||||
const (
|
||||
// DisplayReset reset all display attributes.
|
||||
DisplayReset DisplayAttribute = iota
|
||||
// DisplayBold set bold or increases intensity.
|
||||
DisplayBold
|
||||
// DisplayLowIntensity decreases intensity. Not widely supported.
|
||||
DisplayLowIntensity
|
||||
// DisplayItalic set italic. Not widely supported.
|
||||
DisplayItalic
|
||||
// DisplayUnderline set underline
|
||||
DisplayUnderline
|
||||
// DisplayBlink set blink (less than 150 per minute).
|
||||
DisplayBlink
|
||||
// DisplayRapidBlink set blink (more than 150 per minute). Not widely supported.
|
||||
DisplayRapidBlink
|
||||
// DisplayReverse swap foreground and background colors.
|
||||
DisplayReverse
|
||||
// DisplayInvisible set invisible. Not widely supported.
|
||||
DisplayInvisible
|
||||
// DisplayCrossedOut set characters legible, but marked for deletion. Not widely supported.
|
||||
DisplayCrossedOut
|
||||
// DisplayDefaultFont set primary(default) font
|
||||
DisplayDefaultFont
|
||||
)
|
||||
|
||||
// Color represents color on terminal.
|
||||
type Color int
|
||||
|
||||
const (
|
||||
// DefaultColor represents a default color.
|
||||
DefaultColor Color = iota
|
||||
|
||||
// Low intensity
|
||||
|
||||
// Black represents a black.
|
||||
Black
|
||||
// DarkRed represents a dark red.
|
||||
DarkRed
|
||||
// DarkGreen represents a dark green.
|
||||
DarkGreen
|
||||
// Brown represents a brown.
|
||||
Brown
|
||||
// DarkBlue represents a dark blue.
|
||||
DarkBlue
|
||||
// Purple represents a purple.
|
||||
Purple
|
||||
// Cyan represents a cyan.
|
||||
Cyan
|
||||
// LightGray represents a light gray.
|
||||
LightGray
|
||||
|
||||
// High intensity
|
||||
|
||||
// DarkGray represents a dark gray.
|
||||
DarkGray
|
||||
// Red represents a red.
|
||||
Red
|
||||
// Green represents a green.
|
||||
Green
|
||||
// Yellow represents a yellow.
|
||||
Yellow
|
||||
// Blue represents a blue.
|
||||
Blue
|
||||
// Fuchsia represents a fuchsia.
|
||||
Fuchsia
|
||||
// Turquoise represents a turquoise.
|
||||
Turquoise
|
||||
// White represents a white.
|
||||
White
|
||||
)
|
||||
|
||||
// ConsoleParser is an interface to abstract input layer.
|
||||
type ConsoleParser interface {
|
||||
// Setup should be called before starting input
|
||||
Setup() error
|
||||
// TearDown should be called after stopping input
|
||||
TearDown() error
|
||||
// GetKey returns Key correspond to input byte codes.
|
||||
GetKey(b []byte) Key
|
||||
// GetWinSize returns WinSize object to represent width and height of terminal.
|
||||
GetWinSize() *WinSize
|
||||
// Read returns byte array.
|
||||
Read() ([]byte, error)
|
||||
}
|
||||
|
||||
// ConsoleWriter is an interface to abstract output layer.
|
||||
type ConsoleWriter interface {
|
||||
/* Write */
|
||||
|
||||
// WriteRaw to write raw byte array.
|
||||
WriteRaw(data []byte)
|
||||
// Write to write byte array without control sequences.
|
||||
// Write to write safety byte array by removing control sequences.
|
||||
Write(data []byte)
|
||||
// WriteStr to write raw string.
|
||||
WriteRawStr(data string)
|
||||
// WriteStr to write string without control sequences.
|
||||
// WriteStr to write safety string by removing control sequences.
|
||||
WriteStr(data string)
|
||||
// Flush to flush buffer.
|
||||
Flush() error
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
// +build !windows
|
||||
|
||||
package prompt
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const flushMaxRetryCount = 3
|
||||
|
||||
// PosixWriter is a ConsoleWriter implementation for POSIX environment.
|
||||
// To control terminal emulator, this outputs VT100 escape sequences.
|
||||
type PosixWriter struct {
|
||||
VT100Writer
|
||||
fd int
|
||||
}
|
||||
|
||||
// Flush to flush buffer
|
||||
func (w *PosixWriter) Flush() error {
|
||||
l := len(w.buffer)
|
||||
offset := 0
|
||||
retry := 0
|
||||
for {
|
||||
n, err := syscall.Write(w.fd, w.buffer[offset:])
|
||||
if err != nil {
|
||||
if retry < flushMaxRetryCount {
|
||||
retry++
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
offset += n
|
||||
if offset == l {
|
||||
break
|
||||
}
|
||||
}
|
||||
w.buffer = []byte{}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ ConsoleWriter = &PosixWriter{}
|
||||
|
||||
var (
|
||||
// NewStandardOutputWriter returns ConsoleWriter object to write to stdout.
|
||||
// This generates VT100 escape sequences because almost terminal emulators
|
||||
// in POSIX OS built on top of a VT100 specification.
|
||||
// Deprecated: Please use NewStdoutWriter
|
||||
NewStandardOutputWriter = NewStdoutWriter
|
||||
)
|
||||
|
||||
// NewStdoutWriter returns ConsoleWriter object to write to stdout.
|
||||
// This generates VT100 escape sequences because almost terminal emulators
|
||||
// in POSIX OS built on top of a VT100 specification.
|
||||
func NewStdoutWriter() ConsoleWriter {
|
||||
return &PosixWriter{
|
||||
fd: syscall.Stdout,
|
||||
}
|
||||
}
|
||||
|
||||
// NewStderrWriter returns ConsoleWriter object to write to stderr.
|
||||
// This generates VT100 escape sequences because almost terminal emulators
|
||||
// in POSIX OS built on top of a VT100 specification.
|
||||
func NewStderrWriter() ConsoleWriter {
|
||||
return &PosixWriter{
|
||||
fd: syscall.Stderr,
|
||||
}
|
||||
}
|
||||
+310
@@ -0,0 +1,310 @@
|
||||
package prompt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// VT100Writer generates VT100 escape sequences.
|
||||
type VT100Writer struct {
|
||||
buffer []byte
|
||||
}
|
||||
|
||||
// WriteRaw to write raw byte array
|
||||
func (w *VT100Writer) WriteRaw(data []byte) {
|
||||
w.buffer = append(w.buffer, data...)
|
||||
}
|
||||
|
||||
// Write to write safety byte array by removing control sequences.
|
||||
func (w *VT100Writer) Write(data []byte) {
|
||||
w.WriteRaw(bytes.Replace(data, []byte{0x1b}, []byte{'?'}, -1))
|
||||
}
|
||||
|
||||
// WriteRawStr to write raw string
|
||||
func (w *VT100Writer) WriteRawStr(data string) {
|
||||
w.WriteRaw([]byte(data))
|
||||
}
|
||||
|
||||
// WriteStr to write safety string by removing control sequences.
|
||||
func (w *VT100Writer) WriteStr(data string) {
|
||||
w.Write([]byte(data))
|
||||
}
|
||||
|
||||
/* Erase */
|
||||
|
||||
// EraseScreen erases the screen with the background colour and moves the cursor to home.
|
||||
func (w *VT100Writer) EraseScreen() {
|
||||
w.WriteRaw([]byte{0x1b, '[', '2', 'J'})
|
||||
}
|
||||
|
||||
// EraseUp erases the screen from the current line up to the top of the screen.
|
||||
func (w *VT100Writer) EraseUp() {
|
||||
w.WriteRaw([]byte{0x1b, '[', '1', 'J'})
|
||||
}
|
||||
|
||||
// EraseDown erases the screen from the current line down to the bottom of the screen.
|
||||
func (w *VT100Writer) EraseDown() {
|
||||
w.WriteRaw([]byte{0x1b, '[', 'J'})
|
||||
}
|
||||
|
||||
// EraseStartOfLine erases from the current cursor position to the start of the current line.
|
||||
func (w *VT100Writer) EraseStartOfLine() {
|
||||
w.WriteRaw([]byte{0x1b, '[', '1', 'K'})
|
||||
}
|
||||
|
||||
// EraseEndOfLine erases from the current cursor position to the end of the current line.
|
||||
func (w *VT100Writer) EraseEndOfLine() {
|
||||
w.WriteRaw([]byte{0x1b, '[', 'K'})
|
||||
}
|
||||
|
||||
// EraseLine erases the entire current line.
|
||||
func (w *VT100Writer) EraseLine() {
|
||||
w.WriteRaw([]byte{0x1b, '[', '2', 'K'})
|
||||
}
|
||||
|
||||
/* Cursor */
|
||||
|
||||
// ShowCursor stops blinking cursor and show.
|
||||
func (w *VT100Writer) ShowCursor() {
|
||||
w.WriteRaw([]byte{0x1b, '[', '?', '1', '2', 'l', 0x1b, '[', '?', '2', '5', 'h'})
|
||||
}
|
||||
|
||||
// HideCursor hides cursor.
|
||||
func (w *VT100Writer) HideCursor() {
|
||||
w.WriteRaw([]byte{0x1b, '[', '?', '2', '5', 'l'})
|
||||
}
|
||||
|
||||
// CursorGoTo sets the cursor position where subsequent text will begin.
|
||||
func (w *VT100Writer) CursorGoTo(row, col int) {
|
||||
if row == 0 && col == 0 {
|
||||
// If no row/column parameters are provided (ie. <ESC>[H), the cursor will move to the home position.
|
||||
w.WriteRaw([]byte{0x1b, '[', 'H'})
|
||||
return
|
||||
}
|
||||
r := strconv.Itoa(row)
|
||||
c := strconv.Itoa(col)
|
||||
w.WriteRaw([]byte{0x1b, '['})
|
||||
w.WriteRaw([]byte(r))
|
||||
w.WriteRaw([]byte{';'})
|
||||
w.WriteRaw([]byte(c))
|
||||
w.WriteRaw([]byte{'H'})
|
||||
}
|
||||
|
||||
// CursorUp moves the cursor up by 'n' rows; the default count is 1.
|
||||
func (w *VT100Writer) CursorUp(n int) {
|
||||
if n == 0 {
|
||||
return
|
||||
} else if n < 0 {
|
||||
w.CursorDown(-n)
|
||||
return
|
||||
}
|
||||
s := strconv.Itoa(n)
|
||||
w.WriteRaw([]byte{0x1b, '['})
|
||||
w.WriteRaw([]byte(s))
|
||||
w.WriteRaw([]byte{'A'})
|
||||
}
|
||||
|
||||
// CursorDown moves the cursor down by 'n' rows; the default count is 1.
|
||||
func (w *VT100Writer) CursorDown(n int) {
|
||||
if n == 0 {
|
||||
return
|
||||
} else if n < 0 {
|
||||
w.CursorUp(-n)
|
||||
return
|
||||
}
|
||||
s := strconv.Itoa(n)
|
||||
w.WriteRaw([]byte{0x1b, '['})
|
||||
w.WriteRaw([]byte(s))
|
||||
w.WriteRaw([]byte{'B'})
|
||||
}
|
||||
|
||||
// CursorForward moves the cursor forward by 'n' columns; the default count is 1.
|
||||
func (w *VT100Writer) CursorForward(n int) {
|
||||
if n == 0 {
|
||||
return
|
||||
} else if n < 0 {
|
||||
w.CursorBackward(-n)
|
||||
return
|
||||
}
|
||||
s := strconv.Itoa(n)
|
||||
w.WriteRaw([]byte{0x1b, '['})
|
||||
w.WriteRaw([]byte(s))
|
||||
w.WriteRaw([]byte{'C'})
|
||||
}
|
||||
|
||||
// CursorBackward moves the cursor backward by 'n' columns; the default count is 1.
|
||||
func (w *VT100Writer) CursorBackward(n int) {
|
||||
if n == 0 {
|
||||
return
|
||||
} else if n < 0 {
|
||||
w.CursorForward(-n)
|
||||
return
|
||||
}
|
||||
s := strconv.Itoa(n)
|
||||
w.WriteRaw([]byte{0x1b, '['})
|
||||
w.WriteRaw([]byte(s))
|
||||
w.WriteRaw([]byte{'D'})
|
||||
}
|
||||
|
||||
// AskForCPR asks for a cursor position report (CPR).
|
||||
func (w *VT100Writer) AskForCPR() {
|
||||
// CPR: Cursor Position Request.
|
||||
w.WriteRaw([]byte{0x1b, '[', '6', 'n'})
|
||||
}
|
||||
|
||||
// SaveCursor saves current cursor position.
|
||||
func (w *VT100Writer) SaveCursor() {
|
||||
w.WriteRaw([]byte{0x1b, '[', 's'})
|
||||
}
|
||||
|
||||
// UnSaveCursor restores cursor position after a Save Cursor.
|
||||
func (w *VT100Writer) UnSaveCursor() {
|
||||
w.WriteRaw([]byte{0x1b, '[', 'u'})
|
||||
}
|
||||
|
||||
/* Scrolling */
|
||||
|
||||
// ScrollDown scrolls display down one line.
|
||||
func (w *VT100Writer) ScrollDown() {
|
||||
w.WriteRaw([]byte{0x1b, 'D'})
|
||||
}
|
||||
|
||||
// ScrollUp scroll display up one line.
|
||||
func (w *VT100Writer) ScrollUp() {
|
||||
w.WriteRaw([]byte{0x1b, 'M'})
|
||||
}
|
||||
|
||||
/* Title */
|
||||
|
||||
// SetTitle sets a title of terminal window.
|
||||
func (w *VT100Writer) SetTitle(title string) {
|
||||
titleBytes := []byte(title)
|
||||
patterns := []struct {
|
||||
from []byte
|
||||
to []byte
|
||||
}{
|
||||
{
|
||||
from: []byte{0x13},
|
||||
to: []byte{},
|
||||
},
|
||||
{
|
||||
from: []byte{0x07},
|
||||
to: []byte{},
|
||||
},
|
||||
}
|
||||
for i := range patterns {
|
||||
titleBytes = bytes.Replace(titleBytes, patterns[i].from, patterns[i].to, -1)
|
||||
}
|
||||
|
||||
w.WriteRaw([]byte{0x1b, ']', '2', ';'})
|
||||
w.WriteRaw(titleBytes)
|
||||
w.WriteRaw([]byte{0x07})
|
||||
}
|
||||
|
||||
// ClearTitle clears a title of terminal window.
|
||||
func (w *VT100Writer) ClearTitle() {
|
||||
w.WriteRaw([]byte{0x1b, ']', '2', ';', 0x07})
|
||||
}
|
||||
|
||||
/* Font */
|
||||
|
||||
// SetColor sets text and background colors. and specify whether text is bold.
|
||||
func (w *VT100Writer) SetColor(fg, bg Color, bold bool) {
|
||||
if bold {
|
||||
w.SetDisplayAttributes(fg, bg, DisplayBold)
|
||||
} else {
|
||||
// If using `DisplayDefualt`, it will be broken in some environment.
|
||||
// Details are https://github.com/c-bata/go-prompt/pull/85
|
||||
w.SetDisplayAttributes(fg, bg, DisplayReset)
|
||||
}
|
||||
}
|
||||
|
||||
// SetDisplayAttributes to set VT100 display attributes.
|
||||
func (w *VT100Writer) SetDisplayAttributes(fg, bg Color, attrs ...DisplayAttribute) {
|
||||
w.WriteRaw([]byte{0x1b, '['}) // control sequence introducer
|
||||
defer w.WriteRaw([]byte{'m'}) // final character
|
||||
|
||||
var separator byte = ';'
|
||||
for i := range attrs {
|
||||
p, ok := displayAttributeParameters[attrs[i]]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
w.WriteRaw(p)
|
||||
w.WriteRaw([]byte{separator})
|
||||
}
|
||||
|
||||
f, ok := foregroundANSIColors[fg]
|
||||
if !ok {
|
||||
f = foregroundANSIColors[DefaultColor]
|
||||
}
|
||||
w.WriteRaw(f)
|
||||
w.WriteRaw([]byte{separator})
|
||||
b, ok := backgroundANSIColors[bg]
|
||||
if !ok {
|
||||
b = backgroundANSIColors[DefaultColor]
|
||||
}
|
||||
w.WriteRaw(b)
|
||||
}
|
||||
|
||||
var displayAttributeParameters = map[DisplayAttribute][]byte{
|
||||
DisplayReset: {'0'},
|
||||
DisplayBold: {'1'},
|
||||
DisplayLowIntensity: {'2'},
|
||||
DisplayItalic: {'3'},
|
||||
DisplayUnderline: {'4'},
|
||||
DisplayBlink: {'5'},
|
||||
DisplayRapidBlink: {'6'},
|
||||
DisplayReverse: {'7'},
|
||||
DisplayInvisible: {'8'},
|
||||
DisplayCrossedOut: {'9'},
|
||||
DisplayDefaultFont: {'1', '0'},
|
||||
}
|
||||
|
||||
var foregroundANSIColors = map[Color][]byte{
|
||||
DefaultColor: {'3', '9'},
|
||||
|
||||
// Low intensity.
|
||||
Black: {'3', '0'},
|
||||
DarkRed: {'3', '1'},
|
||||
DarkGreen: {'3', '2'},
|
||||
Brown: {'3', '3'},
|
||||
DarkBlue: {'3', '4'},
|
||||
Purple: {'3', '5'},
|
||||
Cyan: {'3', '6'},
|
||||
LightGray: {'3', '7'},
|
||||
|
||||
// High intensity.
|
||||
DarkGray: {'9', '0'},
|
||||
Red: {'9', '1'},
|
||||
Green: {'9', '2'},
|
||||
Yellow: {'9', '3'},
|
||||
Blue: {'9', '4'},
|
||||
Fuchsia: {'9', '5'},
|
||||
Turquoise: {'9', '6'},
|
||||
White: {'9', '7'},
|
||||
}
|
||||
|
||||
var backgroundANSIColors = map[Color][]byte{
|
||||
DefaultColor: {'4', '9'},
|
||||
|
||||
// Low intensity.
|
||||
Black: {'4', '0'},
|
||||
DarkRed: {'4', '1'},
|
||||
DarkGreen: {'4', '2'},
|
||||
Brown: {'4', '3'},
|
||||
DarkBlue: {'4', '4'},
|
||||
Purple: {'4', '5'},
|
||||
Cyan: {'4', '6'},
|
||||
LightGray: {'4', '7'},
|
||||
|
||||
// High intensity
|
||||
DarkGray: {'1', '0', '0'},
|
||||
Red: {'1', '0', '1'},
|
||||
Green: {'1', '0', '2'},
|
||||
Yellow: {'1', '0', '3'},
|
||||
Blue: {'1', '0', '4'},
|
||||
Fuchsia: {'1', '0', '5'},
|
||||
Turquoise: {'1', '0', '6'},
|
||||
White: {'1', '0', '7'},
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
// +build windows
|
||||
|
||||
package prompt
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
colorable "github.com/mattn/go-colorable"
|
||||
)
|
||||
|
||||
// WindowsWriter is a ConsoleWriter implementation for Win32 console.
|
||||
// Output is converted from VT100 escape sequences by mattn/go-colorable.
|
||||
type WindowsWriter struct {
|
||||
VT100Writer
|
||||
out io.Writer
|
||||
}
|
||||
|
||||
// Flush to flush buffer
|
||||
func (w *WindowsWriter) Flush() error {
|
||||
_, err := w.out.Write(w.buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.buffer = []byte{}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ ConsoleWriter = &WindowsWriter{}
|
||||
|
||||
var (
|
||||
// NewStandardOutputWriter is Deprecated: Please use NewStdoutWriter
|
||||
NewStandardOutputWriter = NewStdoutWriter
|
||||
)
|
||||
|
||||
// NewStdoutWriter returns ConsoleWriter object to write to stdout.
|
||||
// This generates win32 control sequences.
|
||||
func NewStdoutWriter() ConsoleWriter {
|
||||
return &WindowsWriter{
|
||||
out: colorable.NewColorableStdout(),
|
||||
}
|
||||
}
|
||||
|
||||
// NewStderrWriter returns ConsoleWriter object to write to stderr.
|
||||
// This generates win32 control sequences.
|
||||
func NewStderrWriter() ConsoleWriter {
|
||||
return &WindowsWriter{
|
||||
out: colorable.NewColorableStderr(),
|
||||
}
|
||||
}
|
||||
-265
@@ -1,265 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
package prompt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/pkg/term/termios"
|
||||
)
|
||||
|
||||
const maxReadBytes = 1024
|
||||
|
||||
// PosixParser is a ConsoleParser implementation for POSIX environment.
|
||||
type PosixParser struct {
|
||||
fd int
|
||||
origTermios syscall.Termios
|
||||
}
|
||||
|
||||
// Setup should be called before starting input
|
||||
func (t *PosixParser) Setup() error {
|
||||
// Set NonBlocking mode because if syscall.Read block this goroutine, it cannot receive data from stopCh.
|
||||
if err := syscall.SetNonblock(t.fd, true); err != nil {
|
||||
log.Println("[ERROR] Cannot set non blocking mode.")
|
||||
return err
|
||||
}
|
||||
if err := t.setRawMode(); err != nil {
|
||||
log.Println("[ERROR] Cannot set raw mode.")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TearDown should be called after stopping input
|
||||
func (t *PosixParser) TearDown() error {
|
||||
if err := syscall.SetNonblock(t.fd, false); err != nil {
|
||||
log.Println("[ERROR] Cannot set blocking mode.")
|
||||
return err
|
||||
}
|
||||
if err := t.resetRawMode(); err != nil {
|
||||
log.Println("[ERROR] Cannot reset from raw mode.")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read returns byte array.
|
||||
func (t *PosixParser) Read() ([]byte, error) {
|
||||
buf := make([]byte, maxReadBytes)
|
||||
n, err := syscall.Read(syscall.Stdin, buf)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
return buf[:n], nil
|
||||
}
|
||||
|
||||
func (t *PosixParser) setRawMode() error {
|
||||
x := t.origTermios.Lflag
|
||||
if x &^= syscall.ICANON; x != 0 && x == t.origTermios.Lflag {
|
||||
// fd is already raw mode
|
||||
return nil
|
||||
}
|
||||
var n syscall.Termios
|
||||
if err := termios.Tcgetattr(uintptr(t.fd), &t.origTermios); err != nil {
|
||||
return err
|
||||
}
|
||||
n = t.origTermios
|
||||
// "&^=" used like: https://play.golang.org/p/8eJw3JxS4O
|
||||
n.Lflag &^= syscall.ECHO | syscall.ICANON | syscall.IEXTEN | syscall.ISIG
|
||||
n.Cc[syscall.VMIN] = 1
|
||||
n.Cc[syscall.VTIME] = 0
|
||||
termios.Tcsetattr(uintptr(t.fd), termios.TCSANOW, &n)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *PosixParser) resetRawMode() error {
|
||||
if t.origTermios.Lflag == 0 {
|
||||
return nil
|
||||
}
|
||||
return termios.Tcsetattr(uintptr(t.fd), termios.TCSANOW, &t.origTermios)
|
||||
}
|
||||
|
||||
// GetKey returns Key correspond to input byte codes.
|
||||
func (t *PosixParser) GetKey(b []byte) Key {
|
||||
for _, k := range asciiSequences {
|
||||
if bytes.Equal(k.ASCIICode, b) {
|
||||
return k.Key
|
||||
}
|
||||
}
|
||||
return NotDefined
|
||||
}
|
||||
|
||||
// winsize is winsize struct got from the ioctl(2) system call.
|
||||
type ioctlWinsize struct {
|
||||
Row uint16
|
||||
Col uint16
|
||||
X uint16 // pixel value
|
||||
Y uint16 // pixel value
|
||||
}
|
||||
|
||||
// GetWinSize returns WinSize object to represent width and height of terminal.
|
||||
func (t *PosixParser) GetWinSize() *WinSize {
|
||||
ws := &ioctlWinsize{}
|
||||
retCode, _, errno := syscall.Syscall(
|
||||
syscall.SYS_IOCTL,
|
||||
uintptr(t.fd),
|
||||
uintptr(syscall.TIOCGWINSZ),
|
||||
uintptr(unsafe.Pointer(ws)))
|
||||
|
||||
if int(retCode) == -1 {
|
||||
panic(errno)
|
||||
}
|
||||
return &WinSize{
|
||||
Row: ws.Row,
|
||||
Col: ws.Col,
|
||||
}
|
||||
}
|
||||
|
||||
var asciiSequences = []*ASCIICode{
|
||||
{Key: Escape, ASCIICode: []byte{0x1b}},
|
||||
|
||||
{Key: ControlSpace, ASCIICode: []byte{0x00}},
|
||||
{Key: ControlA, ASCIICode: []byte{0x1}},
|
||||
{Key: ControlB, ASCIICode: []byte{0x2}},
|
||||
{Key: ControlC, ASCIICode: []byte{0x3}},
|
||||
{Key: ControlD, ASCIICode: []byte{0x4}},
|
||||
{Key: ControlE, ASCIICode: []byte{0x5}},
|
||||
{Key: ControlF, ASCIICode: []byte{0x6}},
|
||||
{Key: ControlG, ASCIICode: []byte{0x7}},
|
||||
{Key: ControlH, ASCIICode: []byte{0x8}},
|
||||
//{Key: ControlI, ASCIICode: []byte{0x9}},
|
||||
//{Key: ControlJ, ASCIICode: []byte{0xa}},
|
||||
{Key: ControlK, ASCIICode: []byte{0xb}},
|
||||
{Key: ControlL, ASCIICode: []byte{0xc}},
|
||||
{Key: ControlM, ASCIICode: []byte{0xd}},
|
||||
{Key: ControlN, ASCIICode: []byte{0xe}},
|
||||
{Key: ControlO, ASCIICode: []byte{0xf}},
|
||||
{Key: ControlP, ASCIICode: []byte{0x10}},
|
||||
{Key: ControlQ, ASCIICode: []byte{0x11}},
|
||||
{Key: ControlR, ASCIICode: []byte{0x12}},
|
||||
{Key: ControlS, ASCIICode: []byte{0x13}},
|
||||
{Key: ControlT, ASCIICode: []byte{0x14}},
|
||||
{Key: ControlU, ASCIICode: []byte{0x15}},
|
||||
{Key: ControlV, ASCIICode: []byte{0x16}},
|
||||
{Key: ControlW, ASCIICode: []byte{0x17}},
|
||||
{Key: ControlX, ASCIICode: []byte{0x18}},
|
||||
{Key: ControlY, ASCIICode: []byte{0x19}},
|
||||
{Key: ControlZ, ASCIICode: []byte{0x1a}},
|
||||
|
||||
{Key: ControlBackslash, ASCIICode: []byte{0x1c}},
|
||||
{Key: ControlSquareClose, ASCIICode: []byte{0x1d}},
|
||||
{Key: ControlCircumflex, ASCIICode: []byte{0x1e}},
|
||||
{Key: ControlUnderscore, ASCIICode: []byte{0x1f}},
|
||||
{Key: Backspace, ASCIICode: []byte{0x7f}},
|
||||
|
||||
{Key: Up, ASCIICode: []byte{0x1b, 0x5b, 0x41}},
|
||||
{Key: Down, ASCIICode: []byte{0x1b, 0x5b, 0x42}},
|
||||
{Key: Right, ASCIICode: []byte{0x1b, 0x5b, 0x43}},
|
||||
{Key: Left, ASCIICode: []byte{0x1b, 0x5b, 0x44}},
|
||||
{Key: Home, ASCIICode: []byte{0x1b, 0x5b, 0x48}},
|
||||
{Key: Home, ASCIICode: []byte{0x1b, 0x30, 0x48}},
|
||||
{Key: End, ASCIICode: []byte{0x1b, 0x5b, 0x46}},
|
||||
{Key: End, ASCIICode: []byte{0x1b, 0x30, 0x46}},
|
||||
|
||||
{Key: Enter, ASCIICode: []byte{0xa}},
|
||||
{Key: Delete, ASCIICode: []byte{0x1b, 0x5b, 0x33, 0x7e}},
|
||||
{Key: ShiftDelete, ASCIICode: []byte{0x1b, 0x5b, 0x33, 0x3b, 0x32, 0x7e}},
|
||||
{Key: ControlDelete, ASCIICode: []byte{0x1b, 0x5b, 0x33, 0x3b, 0x35, 0x7e}},
|
||||
{Key: Home, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x7e}},
|
||||
{Key: End, ASCIICode: []byte{0x1b, 0x5b, 0x34, 0x7e}},
|
||||
{Key: PageUp, ASCIICode: []byte{0x1b, 0x5b, 0x35, 0x7e}},
|
||||
{Key: PageDown, ASCIICode: []byte{0x1b, 0x5b, 0x36, 0x7e}},
|
||||
{Key: Home, ASCIICode: []byte{0x1b, 0x5b, 0x37, 0x7e}},
|
||||
{Key: End, ASCIICode: []byte{0x1b, 0x5b, 0x38, 0x7e}},
|
||||
{Key: Tab, ASCIICode: []byte{0x9}},
|
||||
{Key: BackTab, ASCIICode: []byte{0x1b, 0x5b, 0x5a}},
|
||||
{Key: Insert, ASCIICode: []byte{0x1b, 0x5b, 0x32, 0x7e}},
|
||||
|
||||
{Key: F1, ASCIICode: []byte{0x1b, 0x4f, 0x50}},
|
||||
{Key: F2, ASCIICode: []byte{0x1b, 0x4f, 0x51}},
|
||||
{Key: F3, ASCIICode: []byte{0x1b, 0x4f, 0x52}},
|
||||
{Key: F4, ASCIICode: []byte{0x1b, 0x4f, 0x53}},
|
||||
|
||||
{Key: F1, ASCIICode: []byte{0x1b, 0x4f, 0x50, 0x41}}, // Linux console
|
||||
{Key: F2, ASCIICode: []byte{0x1b, 0x5b, 0x5b, 0x42}}, // Linux console
|
||||
{Key: F3, ASCIICode: []byte{0x1b, 0x5b, 0x5b, 0x43}}, // Linux console
|
||||
{Key: F4, ASCIICode: []byte{0x1b, 0x5b, 0x5b, 0x44}}, // Linux console
|
||||
{Key: F5, ASCIICode: []byte{0x1b, 0x5b, 0x5b, 0x45}}, // Linux console
|
||||
|
||||
{Key: F1, ASCIICode: []byte{0x1b, 0x5b, 0x11, 0x7e}}, // rxvt-unicode
|
||||
{Key: F2, ASCIICode: []byte{0x1b, 0x5b, 0x12, 0x7e}}, // rxvt-unicode
|
||||
{Key: F3, ASCIICode: []byte{0x1b, 0x5b, 0x13, 0x7e}}, // rxvt-unicode
|
||||
{Key: F4, ASCIICode: []byte{0x1b, 0x5b, 0x14, 0x7e}}, // rxvt-unicode
|
||||
|
||||
{Key: F5, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x35, 0x7e}},
|
||||
{Key: F6, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x37, 0x7e}},
|
||||
{Key: F7, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x38, 0x7e}},
|
||||
{Key: F8, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x39, 0x7e}},
|
||||
{Key: F9, ASCIICode: []byte{0x1b, 0x5b, 0x32, 0x30, 0x7e}},
|
||||
{Key: F10, ASCIICode: []byte{0x1b, 0x5b, 0x32, 0x31, 0x7e}},
|
||||
{Key: F11, ASCIICode: []byte{0x1b, 0x5b, 0x32, 0x32, 0x7e}},
|
||||
{Key: F12, ASCIICode: []byte{0x1b, 0x5b, 0x32, 0x34, 0x7e, 0x8}},
|
||||
{Key: F13, ASCIICode: []byte{0x1b, 0x5b, 0x25, 0x7e}},
|
||||
{Key: F14, ASCIICode: []byte{0x1b, 0x5b, 0x26, 0x7e}},
|
||||
{Key: F15, ASCIICode: []byte{0x1b, 0x5b, 0x28, 0x7e}},
|
||||
{Key: F16, ASCIICode: []byte{0x1b, 0x5b, 0x29, 0x7e}},
|
||||
{Key: F17, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x7e}},
|
||||
{Key: F18, ASCIICode: []byte{0x1b, 0x5b, 0x32, 0x7e}},
|
||||
{Key: F19, ASCIICode: []byte{0x1b, 0x5b, 0x33, 0x7e}},
|
||||
{Key: F20, ASCIICode: []byte{0x1b, 0x5b, 0x34, 0x7e}},
|
||||
|
||||
// Xterm
|
||||
{Key: F13, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x32, 0x50}},
|
||||
{Key: F14, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x32, 0x51}},
|
||||
// &ASCIICode{Key: F15, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x32, 0x52}}, // Conflicts with CPR response
|
||||
{Key: F16, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x32, 0x52}},
|
||||
{Key: F17, ASCIICode: []byte{0x1b, 0x5b, 0x15, 0x3b, 0x32, 0x7e}},
|
||||
{Key: F18, ASCIICode: []byte{0x1b, 0x5b, 0x17, 0x3b, 0x32, 0x7e}},
|
||||
{Key: F19, ASCIICode: []byte{0x1b, 0x5b, 0x18, 0x3b, 0x32, 0x7e}},
|
||||
{Key: F20, ASCIICode: []byte{0x1b, 0x5b, 0x19, 0x3b, 0x32, 0x7e}},
|
||||
{Key: F21, ASCIICode: []byte{0x1b, 0x5b, 0x20, 0x3b, 0x32, 0x7e}},
|
||||
{Key: F22, ASCIICode: []byte{0x1b, 0x5b, 0x21, 0x3b, 0x32, 0x7e}},
|
||||
{Key: F23, ASCIICode: []byte{0x1b, 0x5b, 0x23, 0x3b, 0x32, 0x7e}},
|
||||
{Key: F24, ASCIICode: []byte{0x1b, 0x5b, 0x24, 0x3b, 0x32, 0x7e}},
|
||||
|
||||
{Key: ControlUp, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x35, 0x41}},
|
||||
{Key: ControlDown, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x35, 0x42}},
|
||||
{Key: ControlRight, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x35, 0x43}},
|
||||
{Key: ControlLeft, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x35, 0x44}},
|
||||
|
||||
{Key: ShiftUp, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x32, 0x41}},
|
||||
{Key: ShiftDown, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x32, 0x42}},
|
||||
{Key: ShiftRight, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x32, 0x43}},
|
||||
{Key: ShiftLeft, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x32, 0x44}},
|
||||
|
||||
// Tmux sends following keystrokes when control+arrow is pressed, but for
|
||||
// Emacs ansi-term sends the same sequences for normal arrow keys. Consider
|
||||
// it a normal arrow press, because that's more important.
|
||||
{Key: Up, ASCIICode: []byte{0x1b, 0x4f, 0x41}},
|
||||
{Key: Down, ASCIICode: []byte{0x1b, 0x4f, 0x42}},
|
||||
{Key: Right, ASCIICode: []byte{0x1b, 0x4f, 0x43}},
|
||||
{Key: Left, ASCIICode: []byte{0x1b, 0x4f, 0x44}},
|
||||
|
||||
{Key: ControlUp, ASCIICode: []byte{0x1b, 0x5b, 0x35, 0x41}},
|
||||
{Key: ControlDown, ASCIICode: []byte{0x1b, 0x5b, 0x35, 0x42}},
|
||||
{Key: ControlRight, ASCIICode: []byte{0x1b, 0x5b, 0x35, 0x43}},
|
||||
{Key: ControlLeft, ASCIICode: []byte{0x1b, 0x5b, 0x35, 0x44}},
|
||||
|
||||
{Key: ControlRight, ASCIICode: []byte{0x1b, 0x5b, 0x4f, 0x63}}, // rxvt
|
||||
{Key: ControlLeft, ASCIICode: []byte{0x1b, 0x5b, 0x4f, 0x64}}, // rxvt
|
||||
|
||||
{Key: Ignore, ASCIICode: []byte{0x1b, 0x5b, 0x45}}, // Xterm
|
||||
{Key: Ignore, ASCIICode: []byte{0x1b, 0x5b, 0x46}}, // Linux console
|
||||
}
|
||||
|
||||
var _ ConsoleParser = &PosixParser{}
|
||||
|
||||
// NewStandardInputParser returns ConsoleParser object to read from stdin.
|
||||
func NewStandardInputParser() *PosixParser {
|
||||
return &PosixParser{
|
||||
fd: syscall.Stdin,
|
||||
}
|
||||
}
|
||||
-335
@@ -1,335 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
package prompt
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// PosixWriter is a ConsoleWriter implementation for POSIX environment.
|
||||
// To control terminal emulator, this outputs VT100 escape sequences.
|
||||
type PosixWriter struct {
|
||||
fd int
|
||||
buffer []byte
|
||||
}
|
||||
|
||||
// WriteRaw to write raw byte array
|
||||
func (w *PosixWriter) WriteRaw(data []byte) {
|
||||
w.buffer = append(w.buffer, data...)
|
||||
// Flush because sometimes the render is broken when a large amount data in buffer.
|
||||
w.Flush()
|
||||
return
|
||||
}
|
||||
|
||||
// Write to write byte array without control sequences
|
||||
func (w *PosixWriter) Write(data []byte) {
|
||||
w.WriteRaw(byteFilter(data, writeFilter))
|
||||
return
|
||||
}
|
||||
|
||||
// WriteRawStr to write raw string
|
||||
func (w *PosixWriter) WriteRawStr(data string) {
|
||||
w.WriteRaw([]byte(data))
|
||||
return
|
||||
}
|
||||
|
||||
// WriteStr to write string without control sequences
|
||||
func (w *PosixWriter) WriteStr(data string) {
|
||||
w.Write([]byte(data))
|
||||
return
|
||||
}
|
||||
|
||||
// Flush to flush buffer
|
||||
func (w *PosixWriter) Flush() error {
|
||||
_, err := syscall.Write(w.fd, w.buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.buffer = []byte{}
|
||||
return nil
|
||||
}
|
||||
|
||||
/* Erase */
|
||||
|
||||
// EraseScreen erases the screen with the background colour and moves the cursor to home.
|
||||
func (w *PosixWriter) EraseScreen() {
|
||||
w.WriteRaw([]byte{0x1b, 0x5b, 0x32, 0x4a})
|
||||
return
|
||||
}
|
||||
|
||||
// EraseUp erases the screen from the current line up to the top of the screen.
|
||||
func (w *PosixWriter) EraseUp() {
|
||||
w.WriteRaw([]byte{0x1b, 0x5b, 0x31, 0x4a})
|
||||
return
|
||||
}
|
||||
|
||||
// EraseDown erases the screen from the current line down to the bottom of the screen.
|
||||
func (w *PosixWriter) EraseDown() {
|
||||
w.WriteRaw([]byte{0x1b, 0x5b, 0x4a})
|
||||
return
|
||||
}
|
||||
|
||||
// EraseStartOfLine erases from the current cursor position to the start of the current line.
|
||||
func (w *PosixWriter) EraseStartOfLine() {
|
||||
w.WriteRaw([]byte{0x1b, 0x5b, 0x31, 0x4b})
|
||||
return
|
||||
}
|
||||
|
||||
// EraseEndOfLine erases from the current cursor position to the end of the current line.
|
||||
func (w *PosixWriter) EraseEndOfLine() {
|
||||
w.WriteRaw([]byte{0x1b, 0x5b, 0x4b})
|
||||
return
|
||||
}
|
||||
|
||||
// EraseLine erases the entire current line.
|
||||
func (w *PosixWriter) EraseLine() {
|
||||
w.WriteRaw([]byte{0x1b, 0x5b, 0x32, 0x4b})
|
||||
return
|
||||
}
|
||||
|
||||
/* Cursor */
|
||||
|
||||
// ShowCursor stops blinking cursor and show.
|
||||
func (w *PosixWriter) ShowCursor() {
|
||||
w.WriteRaw([]byte{0x1b, 0x5b, 0x3f, 0x31, 0x32, 0x6c, 0x1b, 0x5b, 0x3f, 0x32, 0x35, 0x68})
|
||||
}
|
||||
|
||||
// HideCursor hides cursor.
|
||||
func (w *PosixWriter) HideCursor() {
|
||||
w.WriteRaw([]byte{0x1b, 0x5b, 0x3f, 0x32, 0x35, 0x6c})
|
||||
return
|
||||
}
|
||||
|
||||
// CursorGoTo sets the cursor position where subsequent text will begin.
|
||||
func (w *PosixWriter) CursorGoTo(row, col int) {
|
||||
if row == 0 && col == 0 {
|
||||
// If no row/column parameters are provided (ie. <ESC>[H), the cursor will move to the home position.
|
||||
w.WriteRaw([]byte{0x1b, 0x5b, 0x3b, 0x48})
|
||||
return
|
||||
}
|
||||
r := strconv.Itoa(row)
|
||||
c := strconv.Itoa(col)
|
||||
w.WriteRaw([]byte{0x1b, 0x5b})
|
||||
w.WriteRaw([]byte(r))
|
||||
w.WriteRaw([]byte{0x3b})
|
||||
w.WriteRaw([]byte(c))
|
||||
w.WriteRaw([]byte{0x48})
|
||||
return
|
||||
}
|
||||
|
||||
// CursorUp moves the cursor up by 'n' rows; the default count is 1.
|
||||
func (w *PosixWriter) CursorUp(n int) {
|
||||
if n == 0 {
|
||||
return
|
||||
} else if n < 0 {
|
||||
w.CursorDown(-n)
|
||||
return
|
||||
}
|
||||
s := strconv.Itoa(n)
|
||||
w.WriteRaw([]byte{0x1b, 0x5b})
|
||||
w.WriteRaw([]byte(s))
|
||||
w.WriteRaw([]byte{0x41})
|
||||
return
|
||||
}
|
||||
|
||||
// CursorDown moves the cursor down by 'n' rows; the default count is 1.
|
||||
func (w *PosixWriter) CursorDown(n int) {
|
||||
if n == 0 {
|
||||
return
|
||||
} else if n < 0 {
|
||||
w.CursorUp(-n)
|
||||
return
|
||||
}
|
||||
s := strconv.Itoa(n)
|
||||
w.WriteRaw([]byte{0x1b, 0x5b})
|
||||
w.WriteRaw([]byte(s))
|
||||
w.WriteRaw([]byte{0x42})
|
||||
return
|
||||
}
|
||||
|
||||
// CursorForward moves the cursor forward by 'n' columns; the default count is 1.
|
||||
func (w *PosixWriter) CursorForward(n int) {
|
||||
if n == 0 {
|
||||
return
|
||||
} else if n < 0 {
|
||||
w.CursorBackward(-n)
|
||||
return
|
||||
}
|
||||
s := strconv.Itoa(n)
|
||||
w.WriteRaw([]byte{0x1b, 0x5b})
|
||||
w.WriteRaw([]byte(s))
|
||||
w.WriteRaw([]byte{0x43})
|
||||
return
|
||||
}
|
||||
|
||||
// CursorBackward moves the cursor backward by 'n' columns; the default count is 1.
|
||||
func (w *PosixWriter) CursorBackward(n int) {
|
||||
if n == 0 {
|
||||
return
|
||||
} else if n < 0 {
|
||||
w.CursorForward(-n)
|
||||
return
|
||||
}
|
||||
s := strconv.Itoa(n)
|
||||
w.WriteRaw([]byte{0x1b, 0x5b})
|
||||
w.WriteRaw([]byte(s))
|
||||
w.WriteRaw([]byte{0x44})
|
||||
return
|
||||
}
|
||||
|
||||
// AskForCPR asks for a cursor position report (CPR).
|
||||
func (w *PosixWriter) AskForCPR() {
|
||||
// CPR: Cursor Position Request.
|
||||
w.WriteRaw([]byte{0x1b, 0x5b, 0x36, 0x6e})
|
||||
w.Flush()
|
||||
return
|
||||
}
|
||||
|
||||
// SaveCursor saves current cursor position.
|
||||
func (w *PosixWriter) SaveCursor() {
|
||||
w.WriteRaw([]byte{0x1b, 0x5b, 0x73})
|
||||
return
|
||||
}
|
||||
|
||||
// UnSaveCursor restores cursor position after a Save Cursor.
|
||||
func (w *PosixWriter) UnSaveCursor() {
|
||||
w.WriteRaw([]byte{0x1b, 0x5b, 0x75})
|
||||
return
|
||||
}
|
||||
|
||||
/* Scrolling */
|
||||
|
||||
// ScrollDown scrolls display down one line.
|
||||
func (w *PosixWriter) ScrollDown() {
|
||||
w.WriteRaw([]byte{0x1b, 0x44})
|
||||
return
|
||||
}
|
||||
|
||||
// ScrollUp scroll display up one line.
|
||||
func (w *PosixWriter) ScrollUp() {
|
||||
w.WriteRaw([]byte{0x1b, 0x4d})
|
||||
return
|
||||
}
|
||||
|
||||
/* Title */
|
||||
|
||||
// SetTitle sets a title of terminal window.
|
||||
func (w *PosixWriter) SetTitle(title string) {
|
||||
w.WriteRaw([]byte{0x1b, 0x5d, 0x32, 0x3b})
|
||||
w.WriteRaw(byteFilter([]byte(title), setTextFilter))
|
||||
w.WriteRaw([]byte{0x07})
|
||||
return
|
||||
}
|
||||
|
||||
// ClearTitle clears a title of terminal window.
|
||||
func (w *PosixWriter) ClearTitle() {
|
||||
w.WriteRaw([]byte{0x1b, 0x5d, 0x32, 0x3b, 0x07})
|
||||
return
|
||||
}
|
||||
|
||||
/* Font */
|
||||
|
||||
// SetColor sets text and background colors. and specify whether text is bold.
|
||||
func (w *PosixWriter) SetColor(fg, bg Color, bold bool) {
|
||||
f, ok := foregroundANSIColors[fg]
|
||||
if !ok {
|
||||
f = foregroundANSIColors[DefaultColor]
|
||||
}
|
||||
b, ok := backgroundANSIColors[bg]
|
||||
if !ok {
|
||||
b = backgroundANSIColors[DefaultColor]
|
||||
}
|
||||
syscall.Write(syscall.Stdout, []byte{0x1b, 0x5b, 0x33, 0x39, 0x3b, 0x34, 0x39, 0x6d})
|
||||
w.WriteRaw([]byte{0x1b, 0x5b})
|
||||
if !bold {
|
||||
w.WriteRaw([]byte{0x30, 0x3b})
|
||||
}
|
||||
w.WriteRaw(f)
|
||||
w.WriteRaw([]byte{0x3b})
|
||||
w.WriteRaw(b)
|
||||
if bold {
|
||||
w.WriteRaw([]byte{0x3b, 0x31})
|
||||
}
|
||||
w.WriteRaw([]byte{0x6d})
|
||||
return
|
||||
}
|
||||
|
||||
var foregroundANSIColors = map[Color][]byte{
|
||||
DefaultColor: {0x33, 0x39}, // 39
|
||||
|
||||
// Low intensity.
|
||||
Black: {0x33, 0x30}, // 30
|
||||
DarkRed: {0x33, 0x31}, // 31
|
||||
DarkGreen: {0x33, 0x32}, // 32
|
||||
Brown: {0x33, 0x33}, // 33
|
||||
DarkBlue: {0x33, 0x34}, // 34
|
||||
Purple: {0x33, 0x35}, // 35
|
||||
Cyan: {0x33, 0x36}, //36
|
||||
LightGray: {0x33, 0x37}, //37
|
||||
|
||||
// High intensity.
|
||||
DarkGray: {0x39, 0x30}, // 90
|
||||
Red: {0x39, 0x31}, // 91
|
||||
Green: {0x39, 0x32}, // 92
|
||||
Yellow: {0x39, 0x33}, // 93
|
||||
Blue: {0x39, 0x34}, // 94
|
||||
Fuchsia: {0x39, 0x35}, // 95
|
||||
Turquoise: {0x39, 0x36}, // 96
|
||||
White: {0x39, 0x37}, // 97
|
||||
}
|
||||
|
||||
var backgroundANSIColors = map[Color][]byte{
|
||||
DefaultColor: {0x34, 0x39}, // 49
|
||||
|
||||
// Low intensity.
|
||||
Black: {0x34, 0x30}, // 40
|
||||
DarkRed: {0x34, 0x31}, // 41
|
||||
DarkGreen: {0x34, 0x32}, // 42
|
||||
Brown: {0x34, 0x33}, // 43
|
||||
DarkBlue: {0x34, 0x34}, // 44
|
||||
Purple: {0x34, 0x35}, // 45
|
||||
Cyan: {0x34, 0x36}, // 46
|
||||
LightGray: {0x34, 0x37}, // 47
|
||||
|
||||
// High intensity
|
||||
DarkGray: {0x31, 0x30, 0x30}, // 100
|
||||
Red: {0x31, 0x30, 0x31}, // 101
|
||||
Green: {0x31, 0x30, 0x32}, // 102
|
||||
Yellow: {0x31, 0x30, 0x33}, // 103
|
||||
Blue: {0x31, 0x30, 0x34}, // 104
|
||||
Fuchsia: {0x31, 0x30, 0x35}, // 105
|
||||
Turquoise: {0x31, 0x30, 0x36}, // 106
|
||||
White: {0x31, 0x30, 0x37}, // 107
|
||||
}
|
||||
|
||||
func writeFilter(buf byte) bool {
|
||||
return buf != 0x1b && buf != 0x3f
|
||||
}
|
||||
|
||||
func setTextFilter(buf byte) bool {
|
||||
return buf != 0x1b && buf != 0x07
|
||||
}
|
||||
|
||||
func byteFilter(buf []byte, fn ...func(b byte) bool) []byte {
|
||||
if len(fn) == 0 {
|
||||
return buf
|
||||
}
|
||||
ret := make([]byte, 0, len(buf))
|
||||
f := fn[0]
|
||||
for i, n := range buf {
|
||||
if f(n) {
|
||||
ret = append(ret, buf[i])
|
||||
}
|
||||
}
|
||||
return byteFilter(ret, fn[1:]...)
|
||||
}
|
||||
|
||||
var _ ConsoleWriter = &PosixWriter{}
|
||||
|
||||
// NewStandardOutputWriter returns ConsoleWriter object to write to stdout.
|
||||
func NewStandardOutputWriter() *PosixWriter {
|
||||
return &PosixWriter{
|
||||
fd: syscall.Stdout,
|
||||
}
|
||||
}
|
||||
+105
-71
@@ -1,32 +1,40 @@
|
||||
package prompt
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"bytes"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
envDebugLogPath = "GO_PROMPT_LOG_PATH"
|
||||
"github.com/c-bata/go-prompt/internal/debug"
|
||||
)
|
||||
|
||||
// Executor is called when user input something text.
|
||||
type Executor func(string)
|
||||
|
||||
// ExitChecker is called after user input to check if prompt must stop and exit go-prompt Run loop.
|
||||
// User input means: selecting/typing an entry, then, if said entry content matches the ExitChecker function criteria:
|
||||
// - immediate exit (if breakline is false) without executor called
|
||||
// - exit after typing <return> (meaning breakline is true), and the executor is called first, before exit.
|
||||
// Exit means exit go-prompt (not the overall Go program)
|
||||
type ExitChecker func(in string, breakline bool) bool
|
||||
|
||||
// Completer should return the suggest item from Document.
|
||||
type Completer func(Document) []Suggest
|
||||
|
||||
// Prompt is core struct of go-prompt.
|
||||
type Prompt struct {
|
||||
in ConsoleParser
|
||||
buf *Buffer
|
||||
renderer *Render
|
||||
executor Executor
|
||||
history *History
|
||||
completion *CompletionManager
|
||||
keyBindings []KeyBind
|
||||
keyBindMode KeyBindMode
|
||||
in ConsoleParser
|
||||
buf *Buffer
|
||||
renderer *Render
|
||||
executor Executor
|
||||
history *History
|
||||
completion *CompletionManager
|
||||
keyBindings []KeyBind
|
||||
ASCIICodeBindings []ASCIICodeBind
|
||||
keyBindMode KeyBindMode
|
||||
completionOnDown bool
|
||||
exitChecker ExitChecker
|
||||
skipTearDown bool
|
||||
}
|
||||
|
||||
// Exec is the struct contains user input context.
|
||||
@@ -36,19 +44,16 @@ type Exec struct {
|
||||
|
||||
// Run starts prompt.
|
||||
func (p *Prompt) Run() {
|
||||
if l := os.Getenv(envDebugLogPath); l == "" {
|
||||
log.SetOutput(ioutil.Discard)
|
||||
} else if f, err := os.OpenFile(l, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666); err != nil {
|
||||
log.SetOutput(ioutil.Discard)
|
||||
} else {
|
||||
defer f.Close()
|
||||
log.SetOutput(f)
|
||||
log.Println("[INFO] Logging is enabled.")
|
||||
}
|
||||
|
||||
p.skipTearDown = false
|
||||
defer debug.Teardown()
|
||||
debug.Log("start prompt")
|
||||
p.setUp()
|
||||
defer p.tearDown()
|
||||
|
||||
if p.completion.showAtStart {
|
||||
p.completion.Update(*p.buf.Document())
|
||||
}
|
||||
|
||||
p.renderer.Render(p.buf, p.completion)
|
||||
|
||||
bufCh := make(chan []byte, 128)
|
||||
@@ -65,6 +70,8 @@ func (p *Prompt) Run() {
|
||||
case b := <-bufCh:
|
||||
if shouldExit, e := p.feed(b); shouldExit {
|
||||
p.renderer.BreakLine(p.buf)
|
||||
stopReadBufCh <- struct{}{}
|
||||
stopHandleSignalCh <- struct{}{}
|
||||
return
|
||||
} else if e != nil {
|
||||
// Stop goroutine to run readBuffer function
|
||||
@@ -73,14 +80,19 @@ func (p *Prompt) Run() {
|
||||
|
||||
// Unset raw mode
|
||||
// Reset to Blocking mode because returned EAGAIN when still set non-blocking mode.
|
||||
p.in.TearDown()
|
||||
debug.AssertNoError(p.in.TearDown())
|
||||
p.executor(e.input)
|
||||
|
||||
p.completion.Update(*p.buf.Document())
|
||||
|
||||
p.renderer.Render(p.buf, p.completion)
|
||||
|
||||
if p.exitChecker != nil && p.exitChecker(e.input, true) {
|
||||
p.skipTearDown = true
|
||||
return
|
||||
}
|
||||
// Set raw mode
|
||||
p.in.Setup()
|
||||
debug.AssertNoError(p.in.Setup())
|
||||
go p.readBuffer(bufCh, stopReadBufCh)
|
||||
go p.handleSignals(exitCh, winSizeCh, stopHandleSignalCh)
|
||||
} else {
|
||||
@@ -101,42 +113,17 @@ func (p *Prompt) Run() {
|
||||
}
|
||||
|
||||
func (p *Prompt) feed(b []byte) (shouldExit bool, exec *Exec) {
|
||||
key := p.in.GetKey(b)
|
||||
|
||||
key := GetKey(b)
|
||||
p.buf.lastKeyStroke = key
|
||||
// completion
|
||||
completing := p.completion.Completing()
|
||||
switch key {
|
||||
case Down:
|
||||
if completing {
|
||||
p.completion.Next()
|
||||
}
|
||||
case Tab, ControlI:
|
||||
p.completion.Next()
|
||||
case Up:
|
||||
if completing {
|
||||
p.completion.Previous()
|
||||
}
|
||||
case BackTab:
|
||||
p.completion.Previous()
|
||||
case ControlSpace:
|
||||
return
|
||||
default:
|
||||
if s, ok := p.completion.GetSelectedSuggestion(); ok {
|
||||
w := p.buf.Document().GetWordBeforeCursor()
|
||||
if w != "" {
|
||||
p.buf.DeleteBeforeCursor(len([]rune(w)))
|
||||
}
|
||||
p.buf.InsertText(s.Text, false, true)
|
||||
}
|
||||
p.completion.Reset()
|
||||
}
|
||||
p.handleCompletionKeyBinding(key, completing)
|
||||
|
||||
switch key {
|
||||
case Enter, ControlJ, ControlM:
|
||||
p.renderer.BreakLine(p.buf)
|
||||
|
||||
exec = &Exec{input: p.buf.Text()}
|
||||
log.Printf("[History] %s", p.buf.Text())
|
||||
p.buf = NewBuffer()
|
||||
if exec.input != "" {
|
||||
p.history.Add(exec.input)
|
||||
@@ -164,10 +151,44 @@ func (p *Prompt) feed(b []byte) (shouldExit bool, exec *Exec) {
|
||||
return
|
||||
}
|
||||
case NotDefined:
|
||||
if p.handleASCIICodeBinding(b) {
|
||||
return
|
||||
}
|
||||
p.buf.InsertText(string(b), false, true)
|
||||
}
|
||||
|
||||
// Key bindings
|
||||
shouldExit = p.handleKeyBinding(key)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Prompt) handleCompletionKeyBinding(key Key, completing bool) {
|
||||
switch key {
|
||||
case Down:
|
||||
if completing || p.completionOnDown {
|
||||
p.completion.Next()
|
||||
}
|
||||
case Tab, ControlI:
|
||||
p.completion.Next()
|
||||
case Up:
|
||||
if completing {
|
||||
p.completion.Previous()
|
||||
}
|
||||
case BackTab:
|
||||
p.completion.Previous()
|
||||
default:
|
||||
if s, ok := p.completion.GetSelectedSuggestion(); ok {
|
||||
w := p.buf.Document().GetWordBeforeCursorUntilSeparator(p.completion.wordSeparator)
|
||||
if w != "" {
|
||||
p.buf.DeleteBeforeCursor(len([]rune(w)))
|
||||
}
|
||||
p.buf.InsertText(s.Text, false, true)
|
||||
}
|
||||
p.completion.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Prompt) handleKeyBinding(key Key) bool {
|
||||
shouldExit := false
|
||||
for i := range commonKeyBindings {
|
||||
kb := commonKeyBindings[i]
|
||||
if kb.Key == key {
|
||||
@@ -191,24 +212,34 @@ func (p *Prompt) feed(b []byte) (shouldExit bool, exec *Exec) {
|
||||
kb.Fn(p.buf)
|
||||
}
|
||||
}
|
||||
return
|
||||
if p.exitChecker != nil && p.exitChecker(p.buf.Text(), false) {
|
||||
shouldExit = true
|
||||
}
|
||||
return shouldExit
|
||||
}
|
||||
|
||||
func (p *Prompt) handleASCIICodeBinding(b []byte) bool {
|
||||
checked := false
|
||||
for _, kb := range p.ASCIICodeBindings {
|
||||
if bytes.Equal(kb.ASCIICode, b) {
|
||||
kb.Fn(p.buf)
|
||||
checked = true
|
||||
}
|
||||
}
|
||||
return checked
|
||||
}
|
||||
|
||||
// Input just returns user input text.
|
||||
func (p *Prompt) Input() string {
|
||||
if l := os.Getenv(envDebugLogPath); l == "" {
|
||||
log.SetOutput(ioutil.Discard)
|
||||
} else if f, err := os.OpenFile(l, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666); err != nil {
|
||||
log.SetOutput(ioutil.Discard)
|
||||
} else {
|
||||
defer f.Close()
|
||||
log.SetOutput(f)
|
||||
log.Println("[INFO] Logging is enabled.")
|
||||
}
|
||||
|
||||
defer debug.Teardown()
|
||||
debug.Log("start prompt")
|
||||
p.setUp()
|
||||
defer p.tearDown()
|
||||
|
||||
if p.completion.showAtStart {
|
||||
p.completion.Update(*p.buf.Document())
|
||||
}
|
||||
|
||||
p.renderer.Render(p.buf, p.completion)
|
||||
bufCh := make(chan []byte, 128)
|
||||
stopReadBufCh := make(chan struct{})
|
||||
@@ -219,6 +250,7 @@ func (p *Prompt) Input() string {
|
||||
case b := <-bufCh:
|
||||
if shouldExit, e := p.feed(b); shouldExit {
|
||||
p.renderer.BreakLine(p.buf)
|
||||
stopReadBufCh <- struct{}{}
|
||||
return ""
|
||||
} else if e != nil {
|
||||
// Stop goroutine to run readBuffer function
|
||||
@@ -235,28 +267,30 @@ func (p *Prompt) Input() string {
|
||||
}
|
||||
|
||||
func (p *Prompt) readBuffer(bufCh chan []byte, stopCh chan struct{}) {
|
||||
log.Printf("[INFO] readBuffer start")
|
||||
debug.Log("start reading buffer")
|
||||
for {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
select {
|
||||
case <-stopCh:
|
||||
log.Print("[INFO] stop readBuffer")
|
||||
debug.Log("stop reading buffer")
|
||||
return
|
||||
default:
|
||||
if b, err := p.in.Read(); err == nil {
|
||||
if b, err := p.in.Read(); err == nil && !(len(b) == 1 && b[0] == 0) {
|
||||
bufCh <- b
|
||||
}
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Prompt) setUp() {
|
||||
p.in.Setup()
|
||||
debug.AssertNoError(p.in.Setup())
|
||||
p.renderer.Setup()
|
||||
p.renderer.UpdateWinSize(p.in.GetWinSize())
|
||||
}
|
||||
|
||||
func (p *Prompt) tearDown() {
|
||||
p.in.TearDown()
|
||||
if !p.skipTearDown {
|
||||
debug.AssertNoError(p.in.TearDown())
|
||||
}
|
||||
p.renderer.TearDown()
|
||||
}
|
||||
|
||||
+67
-44
@@ -1,10 +1,18 @@
|
||||
package prompt
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"github.com/c-bata/go-prompt/internal/debug"
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
// Render to render prompt information from state of Buffer.
|
||||
type Render struct {
|
||||
out ConsoleWriter
|
||||
prefix string
|
||||
livePrefixCallback func() (prefix string, useLivePrefix bool)
|
||||
breakLineCallback func(*Document)
|
||||
title string
|
||||
row uint16
|
||||
col uint16
|
||||
@@ -34,7 +42,7 @@ type Render struct {
|
||||
func (r *Render) Setup() {
|
||||
if r.title != "" {
|
||||
r.out.SetTitle(r.title)
|
||||
r.out.Flush()
|
||||
debug.AssertNoError(r.out.Flush())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +65,7 @@ func (r *Render) renderPrefix() {
|
||||
func (r *Render) TearDown() {
|
||||
r.out.ClearTitle()
|
||||
r.out.EraseDown()
|
||||
r.out.Flush()
|
||||
debug.AssertNoError(r.out.Flush())
|
||||
}
|
||||
|
||||
func (r *Render) prepareArea(lines int) {
|
||||
@@ -67,14 +75,12 @@ func (r *Render) prepareArea(lines int) {
|
||||
for i := 0; i < lines; i++ {
|
||||
r.out.ScrollUp()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateWinSize called when window size is changed.
|
||||
func (r *Render) UpdateWinSize(ws *WinSize) {
|
||||
r.row = ws.Row
|
||||
r.col = ws.Col
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Render) renderWindowTooSmall() {
|
||||
@@ -82,8 +88,6 @@ func (r *Render) renderWindowTooSmall() {
|
||||
r.out.EraseScreen()
|
||||
r.out.SetColor(DarkRed, White, false)
|
||||
r.out.WriteStr("Your console window is too small...")
|
||||
r.out.Flush()
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Render) renderCompletion(buf *Buffer, completions *CompletionManager) {
|
||||
@@ -94,7 +98,7 @@ func (r *Render) renderCompletion(buf *Buffer, completions *CompletionManager) {
|
||||
prefix := r.getCurrentPrefix()
|
||||
formatted, width := formatSuggestions(
|
||||
suggestions,
|
||||
int(r.col)-len(prefix)-1, // -1 means a width of scrollbar
|
||||
int(r.col)-runewidth.StringWidth(prefix)-1, // -1 means a width of scrollbar
|
||||
)
|
||||
// +1 means a width of scrollbar.
|
||||
width++
|
||||
@@ -106,10 +110,10 @@ func (r *Render) renderCompletion(buf *Buffer, completions *CompletionManager) {
|
||||
formatted = formatted[completions.verticalScroll : completions.verticalScroll+windowHeight]
|
||||
r.prepareArea(windowHeight)
|
||||
|
||||
cursor := len(prefix) + len(buf.Document().TextBeforeCursor())
|
||||
cursor := runewidth.StringWidth(prefix) + runewidth.StringWidth(buf.Document().TextBeforeCursor())
|
||||
x, _ := r.toPos(cursor)
|
||||
if x+width >= int(r.col) {
|
||||
r.out.CursorBackward(x + width - int(r.col))
|
||||
cursor = r.backward(cursor, x+width-int(r.col))
|
||||
}
|
||||
|
||||
contentHeight := len(completions.tmp)
|
||||
@@ -148,7 +152,10 @@ func (r *Render) renderCompletion(buf *Buffer, completions *CompletionManager) {
|
||||
r.out.SetColor(DefaultColor, r.scrollbarBGColor, false)
|
||||
}
|
||||
r.out.WriteStr(" ")
|
||||
r.out.CursorBackward(width)
|
||||
r.out.SetColor(DefaultColor, DefaultColor, false)
|
||||
|
||||
r.lineWrap(cursor + width)
|
||||
r.backward(cursor+width, width)
|
||||
}
|
||||
|
||||
if x+width >= int(r.col) {
|
||||
@@ -157,101 +164,117 @@ func (r *Render) renderCompletion(buf *Buffer, completions *CompletionManager) {
|
||||
|
||||
r.out.CursorUp(windowHeight)
|
||||
r.out.SetColor(DefaultColor, DefaultColor, false)
|
||||
return
|
||||
}
|
||||
|
||||
// Render renders to the console.
|
||||
func (r *Render) Render(buffer *Buffer, completion *CompletionManager) {
|
||||
// In situations where a pseudo tty is allocated (e.g. within a docker container),
|
||||
// window size via TIOCGWINSZ is not immediately available and will result in 0,0 dimensions.
|
||||
if r.col == 0 {
|
||||
return
|
||||
}
|
||||
defer func() { debug.AssertNoError(r.out.Flush()) }()
|
||||
r.move(r.previousCursor, 0)
|
||||
|
||||
line := buffer.Text()
|
||||
prefix := r.getCurrentPrefix()
|
||||
cursor := len(prefix) + len(line)
|
||||
cursor := runewidth.StringWidth(prefix) + runewidth.StringWidth(line)
|
||||
|
||||
// In situations where a psuedo tty is allocated (e.g. within a docker container),
|
||||
// window size via TIOCGWINSZ is not immediately available and will result in 0,0 dimensions.
|
||||
if r.col > 0 {
|
||||
// Erasing
|
||||
r.clear(r.previousCursor)
|
||||
// prepare area
|
||||
_, y := r.toPos(cursor)
|
||||
|
||||
// prepare area
|
||||
_, y := r.toPos(cursor)
|
||||
|
||||
h := y + 1 + int(completion.max)
|
||||
if h > int(r.row) || completionMargin > int(r.col) {
|
||||
r.renderWindowTooSmall()
|
||||
return
|
||||
}
|
||||
h := y + 1 + int(completion.max)
|
||||
if h > int(r.row) || completionMargin > int(r.col) {
|
||||
r.renderWindowTooSmall()
|
||||
return
|
||||
}
|
||||
|
||||
// Rendering
|
||||
r.out.HideCursor()
|
||||
defer r.out.ShowCursor()
|
||||
|
||||
r.renderPrefix()
|
||||
r.out.SetColor(r.inputTextColor, r.inputBGColor, false)
|
||||
r.out.WriteStr(line)
|
||||
r.out.SetColor(DefaultColor, DefaultColor, false)
|
||||
r.lineWrap(cursor)
|
||||
|
||||
cursor = r.backward(cursor, len(line)-buffer.CursorPosition)
|
||||
r.out.EraseDown()
|
||||
|
||||
cursor = r.backward(cursor, runewidth.StringWidth(line)-buffer.DisplayCursorPosition())
|
||||
|
||||
r.renderCompletion(buffer, completion)
|
||||
if suggest, ok := completion.GetSelectedSuggestion(); ok {
|
||||
cursor = r.backward(cursor, len(buffer.Document().GetWordBeforeCursor()))
|
||||
cursor = r.backward(cursor, runewidth.StringWidth(buffer.Document().GetWordBeforeCursorUntilSeparator(completion.wordSeparator)))
|
||||
|
||||
r.out.SetColor(r.previewSuggestionTextColor, r.previewSuggestionBGColor, false)
|
||||
r.out.WriteStr(suggest.Text)
|
||||
r.out.SetColor(DefaultColor, DefaultColor, false)
|
||||
cursor += runewidth.StringWidth(suggest.Text)
|
||||
|
||||
cursor += len(suggest.Text)
|
||||
rest := buffer.Document().TextAfterCursor()
|
||||
r.out.WriteStr(rest)
|
||||
cursor += runewidth.StringWidth(rest)
|
||||
r.lineWrap(cursor)
|
||||
|
||||
cursor = r.backward(cursor, runewidth.StringWidth(rest))
|
||||
}
|
||||
r.out.Flush()
|
||||
|
||||
r.previousCursor = cursor
|
||||
}
|
||||
|
||||
// BreakLine to break line.
|
||||
func (r *Render) BreakLine(buffer *Buffer) {
|
||||
// Erasing and Render
|
||||
cursor := len(buffer.Document().TextBeforeCursor()) + len(r.getCurrentPrefix())
|
||||
cursor := runewidth.StringWidth(buffer.Document().TextBeforeCursor()) + runewidth.StringWidth(r.getCurrentPrefix())
|
||||
r.clear(cursor)
|
||||
r.renderPrefix()
|
||||
r.out.SetColor(r.inputTextColor, r.inputBGColor, false)
|
||||
r.out.WriteStr(buffer.Document().Text + "\n")
|
||||
r.out.SetColor(DefaultColor, DefaultColor, false)
|
||||
r.out.Flush()
|
||||
debug.AssertNoError(r.out.Flush())
|
||||
if r.breakLineCallback != nil {
|
||||
r.breakLineCallback(buffer.Document())
|
||||
}
|
||||
|
||||
r.previousCursor = 0
|
||||
}
|
||||
|
||||
// clear erases the screen from a beginning of input
|
||||
// even if there is line break which means input length exceeds a window's width.
|
||||
func (r *Render) clear(cursor int) {
|
||||
r.backward(cursor, cursor)
|
||||
r.move(cursor, 0)
|
||||
r.out.EraseDown()
|
||||
}
|
||||
|
||||
// backward moves cursor to backward from a current cursor position
|
||||
// regardless there is a line break.
|
||||
func (r *Render) backward(from, n int) int {
|
||||
return r.move(from, from-n)
|
||||
}
|
||||
|
||||
// move moves cursor to specified position from the beginning of input
|
||||
// even if there is a line break.
|
||||
func (r *Render) move(from, to int) int {
|
||||
_, fromY := r.toPos(from)
|
||||
fromX, fromY := r.toPos(from)
|
||||
toX, toY := r.toPos(to)
|
||||
|
||||
r.out.CursorUp(fromY - toY)
|
||||
r.out.WriteRaw([]byte{'\r'})
|
||||
r.out.CursorForward(toX)
|
||||
r.out.CursorBackward(fromX - toX)
|
||||
return to
|
||||
}
|
||||
|
||||
// toPos returns the relative position from the beginning of the string.
|
||||
// the coordinate system with the beginning of the string as (0,0) and the width as r.col.
|
||||
// the cursor points to the next character, but it points to that character only at the right end (x == r.col - 1).
|
||||
// x will not return 0 except for the first row.
|
||||
func (r *Render) toPos(cursor int) (x, y int) {
|
||||
col := int(r.col)
|
||||
|
||||
if cursor > 0 && cursor%col == 0 {
|
||||
return col - 1, cursor/col - 1
|
||||
}
|
||||
|
||||
return cursor % col, cursor / col
|
||||
}
|
||||
|
||||
func (r *Render) lineWrap(cursor int) {
|
||||
if runtime.GOOS != "windows" && cursor > 0 && cursor%int(r.col) == 0 {
|
||||
r.out.WriteRaw([]byte{'\n'})
|
||||
}
|
||||
}
|
||||
|
||||
func clamp(high, low, x float64) float64 {
|
||||
switch {
|
||||
case high < x:
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package prompt
|
||||
|
||||
func dummyExecutor(in string) {}
|
||||
|
||||
// Input get the input data from the user and return it.
|
||||
func Input(prefix string, completer Completer, opts ...Option) string {
|
||||
pt := New(dummyExecutor, completer)
|
||||
pt.renderer.prefixTextColor = DefaultColor
|
||||
pt.renderer.prefix = prefix
|
||||
|
||||
for _, opt := range opts {
|
||||
if err := opt(pt); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return pt.Input()
|
||||
}
|
||||
|
||||
// Choose to the shortcut of input function to select from string array.
|
||||
// Deprecated: Maybe anyone want to use this.
|
||||
func Choose(prefix string, choices []string, opts ...Option) string {
|
||||
completer := newChoiceCompleter(choices, FilterHasPrefix)
|
||||
pt := New(dummyExecutor, completer)
|
||||
pt.renderer.prefixTextColor = DefaultColor
|
||||
pt.renderer.prefix = prefix
|
||||
|
||||
for _, opt := range opts {
|
||||
if err := opt(pt); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return pt.Input()
|
||||
}
|
||||
|
||||
func newChoiceCompleter(choices []string, filter Filter) Completer {
|
||||
s := make([]Suggest, len(choices))
|
||||
for i := range choices {
|
||||
s[i] = Suggest{Text: choices[i]}
|
||||
}
|
||||
return func(x Document) []Suggest {
|
||||
return filter(s, x.GetWordBeforeCursor(), true)
|
||||
}
|
||||
}
|
||||
+7
-6
@@ -3,10 +3,11 @@
|
||||
package prompt
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/c-bata/go-prompt/internal/debug"
|
||||
)
|
||||
|
||||
func (p *Prompt) handleSignals(exitCh chan int, winSizeCh chan *WinSize, stop chan struct{}) {
|
||||
@@ -23,24 +24,24 @@ func (p *Prompt) handleSignals(exitCh chan int, winSizeCh chan *WinSize, stop ch
|
||||
for {
|
||||
select {
|
||||
case <-stop:
|
||||
log.Println("[INFO] stop handleSignals")
|
||||
debug.Log("stop handleSignals")
|
||||
return
|
||||
case s := <-sigCh:
|
||||
switch s {
|
||||
case syscall.SIGINT: // kill -SIGINT XXXX or Ctrl+c
|
||||
log.Println("[SIGNAL] Catch SIGINT")
|
||||
debug.Log("Catch SIGINT")
|
||||
exitCh <- 0
|
||||
|
||||
case syscall.SIGTERM: // kill -SIGTERM XXXX
|
||||
log.Println("[SIGNAL] Catch SIGTERM")
|
||||
debug.Log("Catch SIGTERM")
|
||||
exitCh <- 1
|
||||
|
||||
case syscall.SIGQUIT: // kill -SIGQUIT XXXX
|
||||
log.Println("[SIGNAL] Catch SIGQUIT")
|
||||
debug.Log("Catch SIGQUIT")
|
||||
exitCh <- 0
|
||||
|
||||
case syscall.SIGWINCH:
|
||||
log.Println("[SIGNAL] Catch SIGWINCH")
|
||||
debug.Log("Catch SIGWINCH")
|
||||
winSizeCh <- in.GetWinSize()
|
||||
}
|
||||
}
|
||||
|
||||
+6
-5
@@ -3,10 +3,11 @@
|
||||
package prompt
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/c-bata/go-prompt/internal/debug"
|
||||
)
|
||||
|
||||
func (p *Prompt) handleSignals(exitCh chan int, winSizeCh chan *WinSize, stop chan struct{}) {
|
||||
@@ -21,21 +22,21 @@ func (p *Prompt) handleSignals(exitCh chan int, winSizeCh chan *WinSize, stop ch
|
||||
for {
|
||||
select {
|
||||
case <-stop:
|
||||
log.Println("[INFO] stop handleSignals")
|
||||
debug.Log("stop handleSignals")
|
||||
return
|
||||
case s := <-sigCh:
|
||||
switch s {
|
||||
|
||||
case syscall.SIGINT: // kill -SIGINT XXXX or Ctrl+c
|
||||
log.Println("[SIGNAL] Catch SIGINT")
|
||||
debug.Log("Catch SIGINT")
|
||||
exitCh <- 0
|
||||
|
||||
case syscall.SIGTERM: // kill -SIGTERM XXXX
|
||||
log.Println("[SIGNAL] Catch SIGTERM")
|
||||
debug.Log("Catch SIGTERM")
|
||||
exitCh <- 1
|
||||
|
||||
case syscall.SIGQUIT: // kill -SIGQUIT XXXX
|
||||
log.Println("[SIGNAL] Catch SIGQUIT")
|
||||
debug.Log("Catch SIGQUIT")
|
||||
exitCh <- 0
|
||||
}
|
||||
}
|
||||
|
||||
-214
@@ -1,214 +0,0 @@
|
||||
// +build windows
|
||||
|
||||
package prompt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/mattn/go-tty"
|
||||
)
|
||||
|
||||
const maxReadBytes = 1024
|
||||
|
||||
// WindowsParser is a ConsoleParser implementation for Win32 console.
|
||||
type WindowsParser struct {
|
||||
tty *tty.TTY
|
||||
}
|
||||
|
||||
// Setup should be called before starting input
|
||||
func (p *WindowsParser) Setup() error {
|
||||
t, err := tty.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.tty = t
|
||||
return nil
|
||||
}
|
||||
|
||||
// TearDown should be called after stopping input
|
||||
func (p *WindowsParser) TearDown() error {
|
||||
return p.tty.Close()
|
||||
}
|
||||
|
||||
// GetKey returns Key correspond to input byte codes.
|
||||
func (p *WindowsParser) GetKey(b []byte) Key {
|
||||
for _, k := range asciiSequences {
|
||||
if bytes.Compare(k.ASCIICode, b) == 0 {
|
||||
return k.Key
|
||||
}
|
||||
}
|
||||
return NotDefined
|
||||
}
|
||||
|
||||
// Read returns byte array.
|
||||
func (p *WindowsParser) Read() ([]byte, error) {
|
||||
buf := make([]byte, maxReadBytes)
|
||||
r, err := p.tty.ReadRune()
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
n := utf8.EncodeRune(buf[:], r)
|
||||
for p.tty.Buffered() && n < maxReadBytes {
|
||||
r, err := p.tty.ReadRune()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
n += utf8.EncodeRune(buf[n:], r)
|
||||
}
|
||||
return buf[:n], nil
|
||||
}
|
||||
|
||||
// GetWinSize returns WinSize object to represent width and height of terminal.
|
||||
func (p *WindowsParser) GetWinSize() *WinSize {
|
||||
w, h, err := p.tty.Size()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &WinSize{
|
||||
Row: uint16(h),
|
||||
Col: uint16(w),
|
||||
}
|
||||
}
|
||||
|
||||
var asciiSequences []*ASCIICode = []*ASCIICode{
|
||||
{Key: Escape, ASCIICode: []byte{0x1b}},
|
||||
|
||||
{Key: ControlSpace, ASCIICode: []byte{0x00}},
|
||||
{Key: ControlA, ASCIICode: []byte{0x1}},
|
||||
{Key: ControlB, ASCIICode: []byte{0x2}},
|
||||
{Key: ControlC, ASCIICode: []byte{0x3}},
|
||||
{Key: ControlD, ASCIICode: []byte{0x4}},
|
||||
{Key: ControlE, ASCIICode: []byte{0x5}},
|
||||
{Key: ControlF, ASCIICode: []byte{0x6}},
|
||||
{Key: ControlG, ASCIICode: []byte{0x7}},
|
||||
{Key: ControlH, ASCIICode: []byte{0x8}},
|
||||
//{Key: ControlI, ASCIICode: []byte{0x9}},
|
||||
//{Key: ControlJ, ASCIICode: []byte{0xa}},
|
||||
{Key: ControlK, ASCIICode: []byte{0xb}},
|
||||
{Key: ControlL, ASCIICode: []byte{0xc}},
|
||||
{Key: ControlM, ASCIICode: []byte{0xd}},
|
||||
{Key: ControlN, ASCIICode: []byte{0xe}},
|
||||
{Key: ControlO, ASCIICode: []byte{0xf}},
|
||||
{Key: ControlP, ASCIICode: []byte{0x10}},
|
||||
{Key: ControlQ, ASCIICode: []byte{0x11}},
|
||||
{Key: ControlR, ASCIICode: []byte{0x12}},
|
||||
{Key: ControlS, ASCIICode: []byte{0x13}},
|
||||
{Key: ControlT, ASCIICode: []byte{0x14}},
|
||||
{Key: ControlU, ASCIICode: []byte{0x15}},
|
||||
{Key: ControlV, ASCIICode: []byte{0x16}},
|
||||
{Key: ControlW, ASCIICode: []byte{0x17}},
|
||||
{Key: ControlX, ASCIICode: []byte{0x18}},
|
||||
{Key: ControlY, ASCIICode: []byte{0x19}},
|
||||
{Key: ControlZ, ASCIICode: []byte{0x1a}},
|
||||
|
||||
{Key: ControlBackslash, ASCIICode: []byte{0x1c}},
|
||||
{Key: ControlSquareClose, ASCIICode: []byte{0x1d}},
|
||||
{Key: ControlCircumflex, ASCIICode: []byte{0x1e}},
|
||||
{Key: ControlUnderscore, ASCIICode: []byte{0x1f}},
|
||||
{Key: Backspace, ASCIICode: []byte{0x7f}},
|
||||
|
||||
{Key: Up, ASCIICode: []byte{0x1b, 0x5b, 0x41}},
|
||||
{Key: Down, ASCIICode: []byte{0x1b, 0x5b, 0x42}},
|
||||
{Key: Right, ASCIICode: []byte{0x1b, 0x5b, 0x43}},
|
||||
{Key: Left, ASCIICode: []byte{0x1b, 0x5b, 0x44}},
|
||||
{Key: Home, ASCIICode: []byte{0x1b, 0x5b, 0x48}},
|
||||
{Key: Home, ASCIICode: []byte{0x1b, 0x4f, 0x48}},
|
||||
{Key: End, ASCIICode: []byte{0x1b, 0x5b, 0x70}},
|
||||
{Key: End, ASCIICode: []byte{0x1b, 0x4f, 0x70}},
|
||||
|
||||
{Key: Enter, ASCIICode: []byte{0xa}},
|
||||
{Key: Delete, ASCIICode: []byte{0x1b, 0x5b, 0x33, 0x7e}},
|
||||
{Key: ShiftDelete, ASCIICode: []byte{0x1b, 0x5b, 0x33, 0x3b, 0x02, 0x7e}},
|
||||
{Key: ControlDelete, ASCIICode: []byte{0x1b, 0x5b, 0x33, 0x3b, 0x05, 0x7e}},
|
||||
{Key: Home, ASCIICode: []byte{0x1b, 0x5b, 0x01, 0x7e}},
|
||||
{Key: End, ASCIICode: []byte{0x1b, 0x5b, 0x04, 0x7e}},
|
||||
{Key: PageUp, ASCIICode: []byte{0x1b, 0x5b, 0x05, 0x7e}},
|
||||
{Key: PageDown, ASCIICode: []byte{0x1b, 0x5b, 0x06, 0x7e}},
|
||||
{Key: Home, ASCIICode: []byte{0x1b, 0x5b, 0x07, 0x7e}},
|
||||
{Key: End, ASCIICode: []byte{0x1b, 0x5b, 0x09, 0x7e}},
|
||||
{Key: Tab, ASCIICode: []byte{0x9}},
|
||||
{Key: BackTab, ASCIICode: []byte{0x1b, 0x5b, 0x5a}},
|
||||
{Key: Insert, ASCIICode: []byte{0x1b, 0x5b, 0x02, 0x7e}},
|
||||
|
||||
{Key: F1, ASCIICode: []byte{0x1b, 0x4f, 0x50}},
|
||||
{Key: F2, ASCIICode: []byte{0x1b, 0x4f, 0x51}},
|
||||
{Key: F3, ASCIICode: []byte{0x1b, 0x4f, 0x52}},
|
||||
{Key: F4, ASCIICode: []byte{0x1b, 0x4f, 0x53}},
|
||||
|
||||
{Key: F1, ASCIICode: []byte{0x1b, 0x4f, 0x50, 0x41}}, // Linux console
|
||||
{Key: F2, ASCIICode: []byte{0x1b, 0x5b, 0x5b, 0x42}}, // Linux console
|
||||
{Key: F3, ASCIICode: []byte{0x1b, 0x5b, 0x5b, 0x43}}, // Linux console
|
||||
{Key: F4, ASCIICode: []byte{0x1b, 0x5b, 0x5b, 0x44}}, // Linux console
|
||||
{Key: F5, ASCIICode: []byte{0x1b, 0x5b, 0x5b, 0x45}}, // Linux console
|
||||
|
||||
{Key: F1, ASCIICode: []byte{0x1b, 0x5b, 0x11, 0x7e}}, // rxvt-unicode
|
||||
{Key: F2, ASCIICode: []byte{0x1b, 0x5b, 0x12, 0x7e}}, // rxvt-unicode
|
||||
{Key: F3, ASCIICode: []byte{0x1b, 0x5b, 0x13, 0x7e}}, // rxvt-unicode
|
||||
{Key: F4, ASCIICode: []byte{0x1b, 0x5b, 0x14, 0x7e}}, // rxvt-unicode
|
||||
|
||||
{Key: F5, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x35, 0x7e}},
|
||||
{Key: F6, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x37, 0x7e}},
|
||||
{Key: F7, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x38, 0x7e}},
|
||||
{Key: F8, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x39, 0x7e}},
|
||||
{Key: F9, ASCIICode: []byte{0x1b, 0x5b, 0x32, 0x30, 0x7e}},
|
||||
{Key: F10, ASCIICode: []byte{0x1b, 0x5b, 0x32, 0x31, 0x7e}},
|
||||
{Key: F11, ASCIICode: []byte{0x1b, 0x5b, 0x32, 0x32, 0x7e}},
|
||||
{Key: F12, ASCIICode: []byte{0x1b, 0x5b, 0x32, 0x34, 0x7e, 0x8}},
|
||||
{Key: F13, ASCIICode: []byte{0x1b, 0x5b, 0x25, 0x7e}},
|
||||
{Key: F14, ASCIICode: []byte{0x1b, 0x5b, 0x26, 0x7e}},
|
||||
{Key: F15, ASCIICode: []byte{0x1b, 0x5b, 0x28, 0x7e}},
|
||||
{Key: F16, ASCIICode: []byte{0x1b, 0x5b, 0x29, 0x7e}},
|
||||
{Key: F17, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x7e}},
|
||||
{Key: F18, ASCIICode: []byte{0x1b, 0x5b, 0x32, 0x7e}},
|
||||
{Key: F19, ASCIICode: []byte{0x1b, 0x5b, 0x33, 0x7e}},
|
||||
{Key: F20, ASCIICode: []byte{0x1b, 0x5b, 0x34, 0x7e}},
|
||||
|
||||
// Xterm
|
||||
{Key: F13, ASCIICode: []byte{0x1b, 0x5b, 0x01, 0x3b, 0x02, 0x50}},
|
||||
{Key: F14, ASCIICode: []byte{0x1b, 0x5b, 0x01, 0x3b, 0x02, 0x51}},
|
||||
// &ASCIICode{Key: F15, ASCIICode: []byte{0x1b, 0x5b, 0x01, 0x3b, 0x02, 0x52}}, // Conflicts with CPR response
|
||||
{Key: F16, ASCIICode: []byte{0x1b, 0x5b, 0x01, 0x3b, 0x02, 0x52}},
|
||||
{Key: F17, ASCIICode: []byte{0x1b, 0x5b, 0x15, 0x3b, 0x02, 0x7e}},
|
||||
{Key: F18, ASCIICode: []byte{0x1b, 0x5b, 0x17, 0x3b, 0x02, 0x7e}},
|
||||
{Key: F19, ASCIICode: []byte{0x1b, 0x5b, 0x18, 0x3b, 0x02, 0x7e}},
|
||||
{Key: F20, ASCIICode: []byte{0x1b, 0x5b, 0x19, 0x3b, 0x02, 0x7e}},
|
||||
{Key: F21, ASCIICode: []byte{0x1b, 0x5b, 0x20, 0x3b, 0x02, 0x7e}},
|
||||
{Key: F22, ASCIICode: []byte{0x1b, 0x5b, 0x21, 0x3b, 0x02, 0x7e}},
|
||||
{Key: F23, ASCIICode: []byte{0x1b, 0x5b, 0x23, 0x3b, 0x02, 0x7e}},
|
||||
{Key: F24, ASCIICode: []byte{0x1b, 0x5b, 0x24, 0x3b, 0x02, 0x7e}},
|
||||
|
||||
{Key: ControlUp, ASCIICode: []byte{0x1b, 0x5b, 0x01, 0x3b, 0x5a}},
|
||||
{Key: ControlDown, ASCIICode: []byte{0x1b, 0x5b, 0x01, 0x3b, 0x5b}},
|
||||
{Key: ControlRight, ASCIICode: []byte{0x1b, 0x5b, 0x01, 0x3b, 0x5c}},
|
||||
{Key: ControlLeft, ASCIICode: []byte{0x1b, 0x5b, 0x01, 0x3b, 0x5d}},
|
||||
|
||||
{Key: ShiftUp, ASCIICode: []byte{0x1b, 0x5b, 0x01, 0x2a}},
|
||||
{Key: ShiftDown, ASCIICode: []byte{0x1b, 0x5b, 0x01, 0x2b}},
|
||||
{Key: ShiftRight, ASCIICode: []byte{0x1b, 0x5b, 0x01, 0x2c}},
|
||||
{Key: ShiftLeft, ASCIICode: []byte{0x1b, 0x5b, 0x01, 0x2d}},
|
||||
|
||||
// Tmux sends following keystrokes when control+arrow is pressed, but for
|
||||
// Emacs ansi-term sends the same sequences for normal arrow keys. Consider
|
||||
// it a normal arrow press, because that's more important.
|
||||
{Key: Up, ASCIICode: []byte{0x1b, 0x4f, 0x41}},
|
||||
{Key: Down, ASCIICode: []byte{0x1b, 0x4f, 0x42}},
|
||||
{Key: Right, ASCIICode: []byte{0x1b, 0x4f, 0x43}},
|
||||
{Key: Left, ASCIICode: []byte{0x1b, 0x4f, 0x44}},
|
||||
|
||||
{Key: ControlUp, ASCIICode: []byte{0x1b, 0x5b, 0x05, 0x41}},
|
||||
{Key: ControlDown, ASCIICode: []byte{0x1b, 0x5b, 0x05, 0x42}},
|
||||
{Key: ControlRight, ASCIICode: []byte{0x1b, 0x5b, 0x05, 0x43}},
|
||||
{Key: ControlLeft, ASCIICode: []byte{0x1b, 0x5b, 0x05, 0x44}},
|
||||
|
||||
{Key: ControlRight, ASCIICode: []byte{0x1b, 0x5b, 0x4f, 0x63}}, // rxvt
|
||||
{Key: ControlLeft, ASCIICode: []byte{0x1b, 0x5b, 0x4f, 0x64}}, // rxvt
|
||||
|
||||
{Key: Ignore, ASCIICode: []byte{0x1b, 0x5b, 0x45}}, // Xterm
|
||||
{Key: Ignore, ASCIICode: []byte{0x1b, 0x5b, 0x46}}, // Linux console
|
||||
}
|
||||
|
||||
// NewStandardInputParser returns ConsoleParser object to read from stdin.
|
||||
func NewStandardInputParser() *WindowsParser {
|
||||
return &WindowsParser{}
|
||||
}
|
||||
-333
@@ -1,333 +0,0 @@
|
||||
// +build windows
|
||||
|
||||
package prompt
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strconv"
|
||||
|
||||
"github.com/mattn/go-colorable"
|
||||
)
|
||||
|
||||
// WindowsWriter is a ConsoleWriter implementation for Win32 console.
|
||||
// Output is converted from VT100 escape sequences by mattn/go-colorable.
|
||||
type WindowsWriter struct {
|
||||
out io.Writer
|
||||
buffer []byte
|
||||
}
|
||||
|
||||
// WriteRaw to write raw byte array
|
||||
func (w *WindowsWriter) WriteRaw(data []byte) {
|
||||
w.buffer = append(w.buffer, data...)
|
||||
// Flush because sometimes the render is broken when a large amount data in buffer.
|
||||
w.Flush()
|
||||
return
|
||||
}
|
||||
|
||||
// Write to write byte array without control sequences
|
||||
func (w *WindowsWriter) Write(data []byte) {
|
||||
w.WriteRaw(byteFilter(data, writeFilter))
|
||||
return
|
||||
}
|
||||
|
||||
// WriteRawStr to write raw string
|
||||
func (w *WindowsWriter) WriteRawStr(data string) {
|
||||
w.WriteRaw([]byte(data))
|
||||
return
|
||||
}
|
||||
|
||||
// WriteStr to write string without control sequences
|
||||
func (w *WindowsWriter) WriteStr(data string) {
|
||||
w.Write([]byte(data))
|
||||
return
|
||||
}
|
||||
|
||||
// Flush to flush buffer
|
||||
func (w *WindowsWriter) Flush() error {
|
||||
_, err := w.out.Write(w.buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.buffer = []byte{}
|
||||
return nil
|
||||
}
|
||||
|
||||
/* Erase */
|
||||
|
||||
// EraseScreen erases the screen with the background colour and moves the cursor to home.
|
||||
func (w *WindowsWriter) EraseScreen() {
|
||||
w.WriteRaw([]byte{0x1b, 0x5b, 0x32, 0x4a})
|
||||
return
|
||||
}
|
||||
|
||||
// EraseUp erases the screen from the current line up to the top of the screen.
|
||||
func (w *WindowsWriter) EraseUp() {
|
||||
w.WriteRaw([]byte{0x1b, 0x5b, 0x31, 0x4a})
|
||||
return
|
||||
}
|
||||
|
||||
// EraseDown erases the screen from the current line down to the bottom of the screen.
|
||||
func (w *WindowsWriter) EraseDown() {
|
||||
w.WriteRaw([]byte{0x1b, 0x5b, 0x4a})
|
||||
return
|
||||
}
|
||||
|
||||
// EraseStartOfLine erases from the current cursor position to the start of the current line.
|
||||
func (w *WindowsWriter) EraseStartOfLine() {
|
||||
w.WriteRaw([]byte{0x1b, 0x5b, 0x31, 0x4b})
|
||||
return
|
||||
}
|
||||
|
||||
// EraseEndOfLine erases from the current cursor position to the end of the current line.
|
||||
func (w *WindowsWriter) EraseEndOfLine() {
|
||||
w.WriteRaw([]byte{0x1b, 0x5b, 0x4b})
|
||||
return
|
||||
}
|
||||
|
||||
// EraseLine erases the entire current line.
|
||||
func (w *WindowsWriter) EraseLine() {
|
||||
w.WriteRaw([]byte{0x1b, 0x5b, 0x32, 0x4b})
|
||||
return
|
||||
}
|
||||
|
||||
/* Cursor */
|
||||
|
||||
// ShowCursor stops blinking cursor and show.
|
||||
func (w *WindowsWriter) ShowCursor() {
|
||||
w.WriteRaw([]byte{0x1b, 0x5b, 0x3f, 0x31, 0x32, 0x6c, 0x1b, 0x5b, 0x3f, 0x32, 0x35, 0x68})
|
||||
}
|
||||
|
||||
// HideCursor hides cursor.
|
||||
func (w *WindowsWriter) HideCursor() {
|
||||
w.WriteRaw([]byte{0x1b, 0x5b, 0x3f, 0x32, 0x35, 0x6c})
|
||||
return
|
||||
}
|
||||
|
||||
// CursorGoTo sets the cursor position where subsequent text will begin.
|
||||
func (w *WindowsWriter) CursorGoTo(row, col int) {
|
||||
if row == 0 && col == 0 {
|
||||
// If no row/column parameters are provided (ie. <ESC>[H), the cursor will move to the home position.
|
||||
w.WriteRaw([]byte{0x1b, 0x5b, 0x3b, 0x48})
|
||||
return
|
||||
}
|
||||
r := strconv.Itoa(row)
|
||||
c := strconv.Itoa(col)
|
||||
w.WriteRaw([]byte{0x1b, 0x5b})
|
||||
w.WriteRaw([]byte(r))
|
||||
w.WriteRaw([]byte{0x3b})
|
||||
w.WriteRaw([]byte(c))
|
||||
w.WriteRaw([]byte{0x48})
|
||||
return
|
||||
}
|
||||
|
||||
// CursorUp moves the cursor up by 'n' rows; the default count is 1.
|
||||
func (w *WindowsWriter) CursorUp(n int) {
|
||||
if n < 0 {
|
||||
w.CursorDown(n)
|
||||
return
|
||||
}
|
||||
s := strconv.Itoa(n)
|
||||
w.WriteRaw([]byte{0x1b, 0x5b})
|
||||
w.WriteRaw([]byte(s))
|
||||
w.WriteRaw([]byte{0x41})
|
||||
return
|
||||
}
|
||||
|
||||
// CursorDown moves the cursor down by 'n' rows; the default count is 1.
|
||||
func (w *WindowsWriter) CursorDown(n int) {
|
||||
if n < 0 {
|
||||
w.CursorUp(n)
|
||||
return
|
||||
}
|
||||
s := strconv.Itoa(n)
|
||||
w.WriteRaw([]byte{0x1b, 0x5b})
|
||||
w.WriteRaw([]byte(s))
|
||||
w.WriteRaw([]byte{0x42})
|
||||
return
|
||||
}
|
||||
|
||||
// CursorForward moves the cursor forward by 'n' columns; the default count is 1.
|
||||
func (w *WindowsWriter) CursorForward(n int) {
|
||||
if n == 0 {
|
||||
return
|
||||
} else if n < 0 {
|
||||
w.CursorBackward(-n)
|
||||
return
|
||||
}
|
||||
s := strconv.Itoa(n)
|
||||
w.WriteRaw([]byte{0x1b, 0x5b})
|
||||
w.WriteRaw([]byte(s))
|
||||
w.WriteRaw([]byte{0x43})
|
||||
return
|
||||
}
|
||||
|
||||
// CursorBackward moves the cursor backward by 'n' columns; the default count is 1.
|
||||
func (w *WindowsWriter) CursorBackward(n int) {
|
||||
if n == 0 {
|
||||
return
|
||||
} else if n < 0 {
|
||||
w.CursorForward(-n)
|
||||
return
|
||||
}
|
||||
s := strconv.Itoa(n)
|
||||
w.WriteRaw([]byte{0x1b, 0x5b})
|
||||
w.WriteRaw([]byte(s))
|
||||
w.WriteRaw([]byte{0x44})
|
||||
return
|
||||
}
|
||||
|
||||
// AskForCPR asks for a cursor position report (CPR).
|
||||
func (w *WindowsWriter) AskForCPR() {
|
||||
// CPR: Cursor Position Request.
|
||||
w.WriteRaw([]byte{0x1b, 0x5b, 0x36, 0x6e})
|
||||
w.Flush()
|
||||
return
|
||||
}
|
||||
|
||||
// SaveCursor saves current cursor position.
|
||||
func (w *WindowsWriter) SaveCursor() {
|
||||
w.WriteRaw([]byte{0x1b, 0x5b, 0x73})
|
||||
return
|
||||
}
|
||||
|
||||
// UnSaveCursor restores cursor position after a Save Cursor.
|
||||
func (w *WindowsWriter) UnSaveCursor() {
|
||||
w.WriteRaw([]byte{0x1b, 0x5b, 0x75})
|
||||
return
|
||||
}
|
||||
|
||||
/* Scrolling */
|
||||
|
||||
// ScrollDown scrolls display down one line.
|
||||
func (w *WindowsWriter) ScrollDown() {
|
||||
w.WriteRaw([]byte{0x1b, 0x44})
|
||||
return
|
||||
}
|
||||
|
||||
// ScrollUp scroll display up one line.
|
||||
func (w *WindowsWriter) ScrollUp() {
|
||||
w.WriteRaw([]byte{0x1b, 0x4d})
|
||||
return
|
||||
}
|
||||
|
||||
/* Title */
|
||||
|
||||
// SetTitle sets a title of terminal window.
|
||||
func (w *WindowsWriter) SetTitle(title string) {
|
||||
w.WriteRaw([]byte{0x1b, 0x5d, 0x32, 0x3b})
|
||||
w.WriteRaw(byteFilter([]byte(title), setTextFilter))
|
||||
w.WriteRaw([]byte{0x07})
|
||||
return
|
||||
}
|
||||
|
||||
// ClearTitle clears a title of terminal window.
|
||||
func (w *WindowsWriter) ClearTitle() {
|
||||
w.WriteRaw([]byte{0x1b, 0x5d, 0x32, 0x3b, 0x07})
|
||||
return
|
||||
}
|
||||
|
||||
/* Font */
|
||||
|
||||
// SetColor sets text and background colors. and specify whether text is bold.
|
||||
func (w *WindowsWriter) SetColor(fg, bg Color, bold bool) {
|
||||
f, ok := foregroundANSIColors[fg]
|
||||
if !ok {
|
||||
f, _ = foregroundANSIColors[DefaultColor]
|
||||
}
|
||||
b, ok := backgroundANSIColors[bg]
|
||||
if !ok {
|
||||
b, _ = backgroundANSIColors[DefaultColor]
|
||||
}
|
||||
w.out.Write([]byte{0x1b, 0x5b, 0x33, 0x39, 0x3b, 0x34, 0x39, 0x6d})
|
||||
w.WriteRaw([]byte{0x1b, 0x5b})
|
||||
if !bold {
|
||||
w.WriteRaw([]byte{0x30, 0x3b})
|
||||
}
|
||||
w.WriteRaw(f)
|
||||
w.WriteRaw([]byte{0x3b})
|
||||
w.WriteRaw(b)
|
||||
if bold {
|
||||
w.WriteRaw([]byte{0x3b, 0x31})
|
||||
}
|
||||
w.WriteRaw([]byte{0x6d})
|
||||
return
|
||||
}
|
||||
|
||||
var foregroundANSIColors = map[Color][]byte{
|
||||
DefaultColor: {0x33, 0x39}, // 39
|
||||
|
||||
// Low intensity.
|
||||
Black: {0x33, 0x30}, // 30
|
||||
DarkRed: {0x33, 0x31}, // 31
|
||||
DarkGreen: {0x33, 0x32}, // 32
|
||||
Brown: {0x33, 0x33}, // 33
|
||||
DarkBlue: {0x33, 0x34}, // 34
|
||||
Purple: {0x33, 0x35}, // 35
|
||||
Cyan: {0x33, 0x36}, //36
|
||||
LightGray: {0x33, 0x37}, //37
|
||||
|
||||
// High intensity.
|
||||
DarkGray: {0x39, 0x30}, // 90
|
||||
Red: {0x39, 0x31}, // 91
|
||||
Green: {0x39, 0x32}, // 92
|
||||
Yellow: {0x39, 0x33}, // 93
|
||||
Blue: {0x39, 0x34}, // 94
|
||||
Fuchsia: {0x39, 0x35}, // 95
|
||||
Turquoise: {0x39, 0x36}, // 96
|
||||
White: {0x39, 0x37}, // 97
|
||||
}
|
||||
|
||||
var backgroundANSIColors = map[Color][]byte{
|
||||
DefaultColor: {0x34, 0x39}, // 49
|
||||
|
||||
// Low intensity.
|
||||
Black: {0x34, 0x30}, // 40
|
||||
DarkRed: {0x34, 0x31}, // 41
|
||||
DarkGreen: {0x34, 0x32}, // 42
|
||||
Brown: {0x34, 0x33}, // 43
|
||||
DarkBlue: {0x34, 0x34}, // 44
|
||||
Purple: {0x34, 0x35}, // 45
|
||||
Cyan: {0x34, 0x36}, // 46
|
||||
LightGray: {0x34, 0x37}, // 47
|
||||
|
||||
// High intensity
|
||||
DarkGray: {0x31, 0x30, 0x30}, // 100
|
||||
Red: {0x31, 0x30, 0x31}, // 101
|
||||
Green: {0x31, 0x30, 0x32}, // 102
|
||||
Yellow: {0x31, 0x30, 0x33}, // 103
|
||||
Blue: {0x31, 0x30, 0x34}, // 104
|
||||
Fuchsia: {0x31, 0x30, 0x35}, // 105
|
||||
Turquoise: {0x31, 0x30, 0x36}, // 106
|
||||
White: {0x31, 0x30, 0x37}, // 107
|
||||
}
|
||||
|
||||
func writeFilter(buf byte) bool {
|
||||
return buf != 0x1b && buf != 0x3f
|
||||
}
|
||||
|
||||
func setTextFilter(buf byte) bool {
|
||||
return buf != 0x1b && buf != 0x07
|
||||
}
|
||||
|
||||
func byteFilter(buf []byte, fn ...func(b byte) bool) []byte {
|
||||
if len(fn) == 0 {
|
||||
return buf
|
||||
}
|
||||
ret := make([]byte, 0, len(buf))
|
||||
f := fn[0]
|
||||
for i, n := range buf {
|
||||
if f(n) {
|
||||
ret = append(ret, buf[i])
|
||||
}
|
||||
}
|
||||
return byteFilter(ret, fn[1:]...)
|
||||
}
|
||||
|
||||
var _ ConsoleWriter = &WindowsWriter{}
|
||||
|
||||
// NewStandardOutputWriter returns ConsoleWriter object to write to stdout.
|
||||
func NewStandardOutputWriter() *WindowsWriter {
|
||||
return &WindowsWriter{
|
||||
out: colorable.NewColorableStdout(),
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -2,10 +2,10 @@
|
||||
|
||||
package termios
|
||||
|
||||
import "syscall"
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
func ioctl(fd, request, argp uintptr) error {
|
||||
if _, _, e := syscall.Syscall6(syscall.SYS_IOCTL, fd, request, argp, 0, 0, 0); e != 0 {
|
||||
if _, _, e := unix.Syscall6(unix.SYS_IOCTL, fd, request, argp, 0, 0, 0); e != 0 {
|
||||
return e
|
||||
}
|
||||
return nil
|
||||
|
||||
+1
-1
@@ -3,5 +3,5 @@ package termios
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
func ioctl(fd, request, argp uintptr) error {
|
||||
return unix.IoctlSetInt(int(fd), int(request), int(argp))
|
||||
return unix.IoctlSetInt(int(fd), uint(request), int(argp))
|
||||
}
|
||||
|
||||
+5
-2
@@ -1,13 +1,16 @@
|
||||
// +build !windows
|
||||
|
||||
package termios
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func open_device(path string) (uintptr, error) {
|
||||
fd, err := syscall.Open(path, syscall.O_NOCTTY|syscall.O_RDWR|syscall.O_CLOEXEC, 0666)
|
||||
fd, err := unix.Open(path, unix.O_NOCTTY|unix.O_RDWR|unix.O_CLOEXEC, 0666)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("unable to open %q: %v", path, err)
|
||||
}
|
||||
|
||||
+6
-5
@@ -2,8 +2,9 @@ package termios
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func open_pty_master() (uintptr, error) {
|
||||
@@ -11,9 +12,9 @@ func open_pty_master() (uintptr, error) {
|
||||
}
|
||||
|
||||
func Ptsname(fd uintptr) (string, error) {
|
||||
n := make([]byte, _IOC_PARM_LEN(syscall.TIOCPTYGNAME))
|
||||
n := make([]byte, _IOC_PARM_LEN(unix.TIOCPTYGNAME))
|
||||
|
||||
err := ioctl(fd, syscall.TIOCPTYGNAME, uintptr(unsafe.Pointer(&n[0])))
|
||||
err := ioctl(fd, unix.TIOCPTYGNAME, uintptr(unsafe.Pointer(&n[0])))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -27,9 +28,9 @@ func Ptsname(fd uintptr) (string, error) {
|
||||
}
|
||||
|
||||
func grantpt(fd uintptr) error {
|
||||
return ioctl(fd, syscall.TIOCPTYGRANT, 0)
|
||||
return ioctl(fd, unix.TIOCPTYGRANT, 0)
|
||||
}
|
||||
|
||||
func unlockpt(fd uintptr) error {
|
||||
return ioctl(fd, syscall.TIOCPTYUNLK, 0)
|
||||
return ioctl(fd, unix.TIOCPTYUNLK, 0)
|
||||
}
|
||||
|
||||
+4
-5
@@ -2,13 +2,12 @@ package termios
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func posix_openpt(oflag int) (fd uintptr, err error) {
|
||||
// Copied from debian-golang-pty/pty_freebsd.go.
|
||||
r0, _, e1 := syscall.Syscall(syscall.SYS_POSIX_OPENPT, uintptr(oflag), 0, 0)
|
||||
r0, _, e1 := unix.Syscall(unix.SYS_POSIX_OPENPT, uintptr(oflag), 0, 0)
|
||||
fd = uintptr(r0)
|
||||
if e1 != 0 {
|
||||
err = e1
|
||||
@@ -17,12 +16,12 @@ func posix_openpt(oflag int) (fd uintptr, err error) {
|
||||
}
|
||||
|
||||
func open_pty_master() (uintptr, error) {
|
||||
return posix_openpt(syscall.O_NOCTTY|syscall.O_RDWR|syscall.O_CLOEXEC)
|
||||
return posix_openpt(unix.O_NOCTTY | unix.O_RDWR | unix.O_CLOEXEC)
|
||||
}
|
||||
|
||||
func Ptsname(fd uintptr) (string, error) {
|
||||
var n uintptr
|
||||
err := ioctl(fd, syscall.TIOCGPTN, uintptr(unsafe.Pointer(&n)))
|
||||
err := ioctl(fd, unix.TIOCGPTN, uintptr(unsafe.Pointer(&n)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -31,7 +30,7 @@ func Ptsname(fd uintptr) (string, error) {
|
||||
|
||||
func grantpt(fd uintptr) error {
|
||||
var n uintptr
|
||||
return ioctl(fd, syscall.TIOCGPTN, uintptr(unsafe.Pointer(&n)))
|
||||
return ioctl(fd, unix.TIOCGPTN, uintptr(unsafe.Pointer(&n)))
|
||||
}
|
||||
|
||||
func unlockpt(fd uintptr) error {
|
||||
|
||||
+5
-4
@@ -2,8 +2,9 @@ package termios
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func open_pty_master() (uintptr, error) {
|
||||
@@ -12,7 +13,7 @@ func open_pty_master() (uintptr, error) {
|
||||
|
||||
func Ptsname(fd uintptr) (string, error) {
|
||||
var n uintptr
|
||||
err := ioctl(fd, syscall.TIOCGPTN, uintptr(unsafe.Pointer(&n)))
|
||||
err := ioctl(fd, unix.TIOCGPTN, uintptr(unsafe.Pointer(&n)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -21,10 +22,10 @@ func Ptsname(fd uintptr) (string, error) {
|
||||
|
||||
func grantpt(fd uintptr) error {
|
||||
var n uintptr
|
||||
return ioctl(fd, syscall.TIOCGPTN, uintptr(unsafe.Pointer(&n)))
|
||||
return ioctl(fd, unix.TIOCGPTN, uintptr(unsafe.Pointer(&n)))
|
||||
}
|
||||
|
||||
func unlockpt(fd uintptr) error {
|
||||
var n uintptr
|
||||
return ioctl(fd, syscall.TIOCSPTLCK, uintptr(unsafe.Pointer(&n)))
|
||||
return ioctl(fd, unix.TIOCSPTLCK, uintptr(unsafe.Pointer(&n)))
|
||||
}
|
||||
|
||||
+18
-17
@@ -3,44 +3,45 @@
|
||||
package termios
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Tiocmget returns the state of the MODEM bits.
|
||||
func Tiocmget(fd uintptr, status *int) error {
|
||||
return ioctl(fd, syscall.TIOCMGET, uintptr(unsafe.Pointer(status)))
|
||||
return ioctl(fd, unix.TIOCMGET, uintptr(unsafe.Pointer(status)))
|
||||
}
|
||||
|
||||
// Tiocmset sets the state of the MODEM bits.
|
||||
func Tiocmset(fd uintptr, status *int) error {
|
||||
return ioctl(fd, syscall.TIOCMSET, uintptr(unsafe.Pointer(status)))
|
||||
return ioctl(fd, unix.TIOCMSET, uintptr(unsafe.Pointer(status)))
|
||||
}
|
||||
|
||||
// Tiocmbis sets the indicated modem bits.
|
||||
func Tiocmbis(fd uintptr, status *int) error {
|
||||
return ioctl(fd, syscall.TIOCMBIS, uintptr(unsafe.Pointer(status)))
|
||||
return ioctl(fd, unix.TIOCMBIS, uintptr(unsafe.Pointer(status)))
|
||||
}
|
||||
|
||||
// Tiocmbic clears the indicated modem bits.
|
||||
func Tiocmbic(fd uintptr, status *int) error {
|
||||
return ioctl(fd, syscall.TIOCMBIC, uintptr(unsafe.Pointer(status)))
|
||||
return ioctl(fd, unix.TIOCMBIC, uintptr(unsafe.Pointer(status)))
|
||||
}
|
||||
|
||||
// Cfmakecbreak modifies attr for cbreak mode.
|
||||
func Cfmakecbreak(attr *syscall.Termios) {
|
||||
attr.Lflag &^= syscall.ECHO | syscall.ICANON
|
||||
attr.Cc[syscall.VMIN] = 1
|
||||
attr.Cc[syscall.VTIME] = 0
|
||||
func Cfmakecbreak(attr *unix.Termios) {
|
||||
attr.Lflag &^= unix.ECHO | unix.ICANON
|
||||
attr.Cc[unix.VMIN] = 1
|
||||
attr.Cc[unix.VTIME] = 0
|
||||
}
|
||||
|
||||
// Cfmakeraw modifies attr for raw mode.
|
||||
func Cfmakeraw(attr *syscall.Termios) {
|
||||
attr.Iflag &^= syscall.BRKINT | syscall.ICRNL | syscall.INPCK | syscall.ISTRIP | syscall.IXON
|
||||
attr.Oflag &^= syscall.OPOST
|
||||
attr.Cflag &^= syscall.CSIZE | syscall.PARENB
|
||||
attr.Cflag |= syscall.CS8
|
||||
attr.Lflag &^= syscall.ECHO | syscall.ICANON | syscall.IEXTEN | syscall.ISIG
|
||||
attr.Cc[syscall.VMIN] = 1
|
||||
attr.Cc[syscall.VTIME] = 0
|
||||
func Cfmakeraw(attr *unix.Termios) {
|
||||
attr.Iflag &^= unix.BRKINT | unix.ICRNL | unix.INPCK | unix.ISTRIP | unix.IXON
|
||||
attr.Oflag &^= unix.OPOST
|
||||
attr.Cflag &^= unix.CSIZE | unix.PARENB
|
||||
attr.Cflag |= unix.CS8
|
||||
attr.Lflag &^= unix.ECHO | unix.ICANON | unix.IEXTEN | unix.ISIG
|
||||
attr.Cc[unix.VMIN] = 1
|
||||
attr.Cc[unix.VTIME] = 0
|
||||
}
|
||||
|
||||
+20
-19
@@ -3,9 +3,10 @@
|
||||
package termios
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -21,21 +22,21 @@ const (
|
||||
)
|
||||
|
||||
// Tcgetattr gets the current serial port settings.
|
||||
func Tcgetattr(fd uintptr, argp *syscall.Termios) error {
|
||||
return ioctl(fd, syscall.TIOCGETA, uintptr(unsafe.Pointer(argp)))
|
||||
func Tcgetattr(fd uintptr, argp *unix.Termios) error {
|
||||
return ioctl(fd, unix.TIOCGETA, uintptr(unsafe.Pointer(argp)))
|
||||
}
|
||||
|
||||
// Tcsetattr sets the current serial port settings.
|
||||
func Tcsetattr(fd, opt uintptr, argp *syscall.Termios) error {
|
||||
func Tcsetattr(fd, opt uintptr, argp *unix.Termios) error {
|
||||
switch opt {
|
||||
case TCSANOW:
|
||||
opt = syscall.TIOCSETA
|
||||
opt = unix.TIOCSETA
|
||||
case TCSADRAIN:
|
||||
opt = syscall.TIOCSETAW
|
||||
opt = unix.TIOCSETAW
|
||||
case TCSAFLUSH:
|
||||
opt = syscall.TIOCSETAF
|
||||
opt = unix.TIOCSETAF
|
||||
default:
|
||||
return syscall.EINVAL
|
||||
return unix.EINVAL
|
||||
}
|
||||
return ioctl(fd, opt, uintptr(unsafe.Pointer(argp)))
|
||||
}
|
||||
@@ -44,39 +45,39 @@ func Tcsetattr(fd, opt uintptr, argp *syscall.Termios) error {
|
||||
// four-tenths of a second to the terminal referenced by fildes. The duration
|
||||
// parameter is ignored in this implementation.
|
||||
func Tcsendbreak(fd, duration uintptr) error {
|
||||
if err := ioctl(fd, syscall.TIOCSBRK, 0); err != nil {
|
||||
if err := ioctl(fd, unix.TIOCSBRK, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(4 / 10 * time.Second)
|
||||
return ioctl(fd, syscall.TIOCCBRK, 0)
|
||||
return ioctl(fd, unix.TIOCCBRK, 0)
|
||||
}
|
||||
|
||||
// Tcdrain waits until all output written to the terminal referenced by fd has been transmitted to the terminal.
|
||||
func Tcdrain(fd uintptr) error {
|
||||
return ioctl(fd, syscall.TIOCDRAIN, 0)
|
||||
return ioctl(fd, unix.TIOCDRAIN, 0)
|
||||
}
|
||||
|
||||
// Tcflush discards data written to the object referred to by fd but not transmitted, or data received but not read, depending on the value of which.
|
||||
func Tcflush(fd, which uintptr) error {
|
||||
var com int
|
||||
switch which {
|
||||
case syscall.TCIFLUSH:
|
||||
case unix.TCIFLUSH:
|
||||
com = FREAD
|
||||
case syscall.TCOFLUSH:
|
||||
case unix.TCOFLUSH:
|
||||
com = FWRITE
|
||||
case syscall.TCIOFLUSH:
|
||||
case unix.TCIOFLUSH:
|
||||
com = FREAD | FWRITE
|
||||
default:
|
||||
return syscall.EINVAL
|
||||
return unix.EINVAL
|
||||
}
|
||||
return ioctl(fd, syscall.TIOCFLUSH, uintptr(unsafe.Pointer(&com)))
|
||||
return ioctl(fd, unix.TIOCFLUSH, uintptr(unsafe.Pointer(&com)))
|
||||
}
|
||||
|
||||
// Cfgetispeed returns the input baud rate stored in the termios structure.
|
||||
func Cfgetispeed(attr *syscall.Termios) uint32 { return uint32(attr.Ispeed) }
|
||||
func Cfgetispeed(attr *unix.Termios) uint32 { return uint32(attr.Ispeed) }
|
||||
|
||||
// Cfgetospeed returns the output baud rate stored in the termios structure.
|
||||
func Cfgetospeed(attr *syscall.Termios) uint32 { return uint32(attr.Ospeed) }
|
||||
func Cfgetospeed(attr *unix.Termios) uint32 { return uint32(attr.Ospeed) }
|
||||
|
||||
// Tiocinq returns the number of bytes in the input buffer.
|
||||
func Tiocinq(fd uintptr, argp *int) error {
|
||||
@@ -86,5 +87,5 @@ func Tiocinq(fd uintptr, argp *int) error {
|
||||
|
||||
// Tiocoutq return the number of bytes in the output buffer.
|
||||
func Tiocoutq(fd uintptr, argp *int) error {
|
||||
return ioctl(fd, syscall.TIOCOUTQ, uintptr(unsafe.Pointer(argp)))
|
||||
return ioctl(fd, unix.TIOCOUTQ, uintptr(unsafe.Pointer(argp)))
|
||||
}
|
||||
|
||||
+11
-10
@@ -1,8 +1,9 @@
|
||||
package termios
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -20,12 +21,12 @@ const (
|
||||
)
|
||||
|
||||
// Tcgetattr gets the current serial port settings.
|
||||
func Tcgetattr(fd uintptr, argp *syscall.Termios) error {
|
||||
return ioctl(fd, syscall.TCGETS, uintptr(unsafe.Pointer(argp)))
|
||||
func Tcgetattr(fd uintptr, argp *unix.Termios) error {
|
||||
return ioctl(fd, unix.TCGETS, uintptr(unsafe.Pointer(argp)))
|
||||
}
|
||||
|
||||
// Tcsetattr sets the current serial port settings.
|
||||
func Tcsetattr(fd, action uintptr, argp *syscall.Termios) error {
|
||||
func Tcsetattr(fd, action uintptr, argp *unix.Termios) error {
|
||||
var request uintptr
|
||||
switch action {
|
||||
case TCSANOW:
|
||||
@@ -35,7 +36,7 @@ func Tcsetattr(fd, action uintptr, argp *syscall.Termios) error {
|
||||
case TCSAFLUSH:
|
||||
request = TCSETSF
|
||||
default:
|
||||
return syscall.EINVAL
|
||||
return unix.EINVAL
|
||||
}
|
||||
return ioctl(fd, request, uintptr(unsafe.Pointer(argp)))
|
||||
}
|
||||
@@ -52,7 +53,7 @@ func Tcsendbreak(fd, duration uintptr) error {
|
||||
// Tcdrain waits until all output written to the object referred to by fd has been transmitted.
|
||||
func Tcdrain(fd uintptr) error {
|
||||
// simulate drain with TCSADRAIN
|
||||
var attr syscall.Termios
|
||||
var attr unix.Termios
|
||||
if err := Tcgetattr(fd, &attr); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -66,16 +67,16 @@ func Tcflush(fd, selector uintptr) error {
|
||||
|
||||
// Tiocinq returns the number of bytes in the input buffer.
|
||||
func Tiocinq(fd uintptr, argp *int) error {
|
||||
return ioctl(fd, syscall.TIOCINQ, uintptr(unsafe.Pointer(argp)))
|
||||
return ioctl(fd, unix.TIOCINQ, uintptr(unsafe.Pointer(argp)))
|
||||
}
|
||||
|
||||
// Tiocoutq return the number of bytes in the output buffer.
|
||||
func Tiocoutq(fd uintptr, argp *int) error {
|
||||
return ioctl(fd, syscall.TIOCOUTQ, uintptr(unsafe.Pointer(argp)))
|
||||
return ioctl(fd, unix.TIOCOUTQ, uintptr(unsafe.Pointer(argp)))
|
||||
}
|
||||
|
||||
// Cfgetispeed returns the input baud rate stored in the termios structure.
|
||||
func Cfgetispeed(attr *syscall.Termios) uint32 { return attr.Ispeed }
|
||||
func Cfgetispeed(attr *unix.Termios) uint32 { return attr.Ispeed }
|
||||
|
||||
// Cfgetospeed returns the output baud rate stored in the termios structure.
|
||||
func Cfgetospeed(attr *syscall.Termios) uint32 { return attr.Ospeed }
|
||||
func Cfgetospeed(attr *unix.Termios) uint32 { return attr.Ospeed }
|
||||
|
||||
+38
-16
@@ -5,8 +5,6 @@ package termios
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"unsafe"
|
||||
)
|
||||
@@ -29,15 +27,15 @@ const (
|
||||
const FIORDCHK = C.FIORDCHK
|
||||
|
||||
// Tcgetattr gets the current serial port settings.
|
||||
func Tcgetattr(fd uintptr, argp *syscall.Termios) error {
|
||||
func Tcgetattr(fd uintptr, argp *unix.Termios) error {
|
||||
termios, err := unix.IoctlGetTermios(int(fd), unix.TCGETS)
|
||||
*argp = syscall.Termios(*termios)
|
||||
*argp = *(tiosTounix(termios))
|
||||
return err
|
||||
}
|
||||
|
||||
// Tcsetattr sets the current serial port settings.
|
||||
func Tcsetattr(fd, action uintptr, argp *syscall.Termios) error {
|
||||
return unix.IoctlSetTermios(int(fd), int(action), (*unix.Termios)(argp))
|
||||
func Tcsetattr(fd, action uintptr, argp *unix.Termios) error {
|
||||
return unix.IoctlSetTermios(int(fd), uint(action), tiosToUnix(argp))
|
||||
}
|
||||
|
||||
// Tcsendbreak transmits a continuous stream of zero-valued bits for a specific
|
||||
@@ -52,7 +50,7 @@ func Tcsendbreak(fd, duration uintptr) error {
|
||||
// Tcdrain waits until all output written to the object referred to by fd has been transmitted.
|
||||
func Tcdrain(fd uintptr) error {
|
||||
// simulate drain with TCSADRAIN
|
||||
var attr syscall.Termios
|
||||
var attr unix.Termios
|
||||
if err := Tcgetattr(fd, &attr); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -72,31 +70,55 @@ func Tiocinq(fd uintptr, argp *int) (err error) {
|
||||
|
||||
// Tiocoutq return the number of bytes in the output buffer.
|
||||
func Tiocoutq(fd uintptr, argp *int) error {
|
||||
return ioctl(fd, syscall.TIOCOUTQ, uintptr(unsafe.Pointer(argp)))
|
||||
return ioctl(fd, unix.TIOCOUTQ, uintptr(unsafe.Pointer(argp)))
|
||||
}
|
||||
|
||||
// Cfgetispeed returns the input baud rate stored in the termios structure.
|
||||
func Cfgetispeed(attr *syscall.Termios) uint32 {
|
||||
solTermios := (*unix.Termios)(attr)
|
||||
func Cfgetispeed(attr *unix.Termios) uint32 {
|
||||
solTermios := tiosToUnix(attr)
|
||||
return uint32(C.cfgetispeed((*C.termios_t)(unsafe.Pointer(solTermios))))
|
||||
}
|
||||
|
||||
// Cfsetispeed sets the input baud rate stored in the termios structure.
|
||||
func Cfsetispeed(attr *syscall.Termios, speed uintptr) error {
|
||||
solTermios := (*unix.Termios)(attr)
|
||||
func Cfsetispeed(attr *unix.Termios, speed uintptr) error {
|
||||
solTermios := tiosToUnix(attr)
|
||||
_, err := C.cfsetispeed((*C.termios_t)(unsafe.Pointer(solTermios)), C.speed_t(speed))
|
||||
return err
|
||||
}
|
||||
|
||||
// Cfgetospeed returns the output baud rate stored in the termios structure.
|
||||
func Cfgetospeed(attr *syscall.Termios) uint32 {
|
||||
solTermios := (*unix.Termios)(attr)
|
||||
func Cfgetospeed(attr *unix.Termios) uint32 {
|
||||
solTermios := tiosToUnix(attr)
|
||||
return uint32(C.cfgetospeed((*C.termios_t)(unsafe.Pointer(solTermios))))
|
||||
}
|
||||
|
||||
// Cfsetospeed sets the output baud rate stored in the termios structure.
|
||||
func Cfsetospeed(attr *syscall.Termios, speed uintptr) error {
|
||||
solTermios := (*unix.Termios)(attr)
|
||||
func Cfsetospeed(attr *unix.Termios, speed uintptr) error {
|
||||
solTermios := tiosToUnix(attr)
|
||||
_, err := C.cfsetospeed((*C.termios_t)(unsafe.Pointer(solTermios)), C.speed_t(speed))
|
||||
return err
|
||||
}
|
||||
|
||||
// tiosToUnix copies a unix.Termios to a x/sys/unix.Termios.
|
||||
// This is needed since type conversions between the two fail due to
|
||||
// more recent x/sys/unix.Termios renaming the padding field.
|
||||
func tiosToUnix(st *unix.Termios) *unix.Termios {
|
||||
return &unix.Termios{
|
||||
Iflag: st.Iflag,
|
||||
Oflag: st.Oflag,
|
||||
Cflag: st.Cflag,
|
||||
Lflag: st.Lflag,
|
||||
Cc: st.Cc,
|
||||
}
|
||||
}
|
||||
|
||||
// tiosTounix copies a x/sys/unix.Termios to a unix.Termios.
|
||||
func tiosTounix(ut *unix.Termios) *unix.Termios {
|
||||
return &unix.Termios{
|
||||
Iflag: ut.Iflag,
|
||||
Oflag: ut.Oflag,
|
||||
Cflag: ut.Cflag,
|
||||
Lflag: ut.Lflag,
|
||||
Cc: ut.Cc,
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+7
-3
@@ -217,8 +217,12 @@ github.com/boombuler/barcode/qr
|
||||
github.com/boombuler/barcode/utils
|
||||
# github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2
|
||||
github.com/bradfitz/iter
|
||||
# github.com/c-bata/go-prompt v0.2.1
|
||||
# github.com/c-bata/go-prompt v0.2.4
|
||||
github.com/c-bata/go-prompt
|
||||
github.com/c-bata/go-prompt/internal/bisect
|
||||
github.com/c-bata/go-prompt/internal/debug
|
||||
github.com/c-bata/go-prompt/internal/strings
|
||||
github.com/c-bata/go-prompt/internal/term
|
||||
# github.com/cheggaaa/pb/v3 v3.0.8
|
||||
github.com/cheggaaa/pb/v3
|
||||
github.com/cheggaaa/pb/v3/termutil
|
||||
@@ -597,7 +601,7 @@ github.com/pierrec/lz4/v4/internal/lz4stream
|
||||
github.com/pierrec/lz4/v4/internal/xxh32
|
||||
# github.com/pkg/errors v0.9.1
|
||||
github.com/pkg/errors
|
||||
# github.com/pkg/term v0.0.0-20181116001808-27bbf2edb814
|
||||
# github.com/pkg/term v1.0.0
|
||||
github.com/pkg/term/termios
|
||||
# github.com/pmezard/go-difflib v1.0.0
|
||||
github.com/pmezard/go-difflib/difflib
|
||||
@@ -1266,5 +1270,5 @@ yunion.io/x/sqlchemy/backends
|
||||
yunion.io/x/sqlchemy/backends/clickhouse
|
||||
yunion.io/x/sqlchemy/backends/mysql
|
||||
yunion.io/x/sqlchemy/backends/sqlite
|
||||
# yunion.io/x/structarg v0.0.0-20200720093445-9f850fa222ce
|
||||
# yunion.io/x/structarg v0.0.0-20220224030024-02b7582b2546
|
||||
yunion.io/x/structarg
|
||||
|
||||
+121
-2
@@ -100,11 +100,87 @@ type ArgumentParser struct {
|
||||
prog string
|
||||
description string
|
||||
epilog string
|
||||
help bool
|
||||
optArgs []Argument
|
||||
posArgs []Argument
|
||||
}
|
||||
|
||||
func NewArgumentParser(target interface{}, prog, desc, epilog string) (*ArgumentParser, error) {
|
||||
type sHelpArg struct {
|
||||
}
|
||||
|
||||
func (self *sHelpArg) AliasToken() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (self *sHelpArg) DoAction(nega bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *sHelpArg) HelpString(indent string) string {
|
||||
return indent + "Print usage and this help message and exit."
|
||||
}
|
||||
|
||||
func (self *sHelpArg) NeedData() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (self *sHelpArg) Token() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (self *sHelpArg) ShortToken() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (self *sHelpArg) NegativeToken() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (self *sHelpArg) MetaVar() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (self *sHelpArg) IsPositional() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (self *sHelpArg) IsRequired() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (self *sHelpArg) IsMulti() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (self *sHelpArg) IsSubcommand() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (self *sHelpArg) String() string {
|
||||
return "[--help]"
|
||||
}
|
||||
|
||||
func (self *sHelpArg) SetValue(val string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *sHelpArg) Reset() {
|
||||
return
|
||||
}
|
||||
|
||||
func (self *sHelpArg) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *sHelpArg) SetDefault() {
|
||||
|
||||
}
|
||||
|
||||
func (self *sHelpArg) IsSet() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func newArgumentParser(target interface{}, prog, desc, epilog string, withHelp bool) (*ArgumentParser, error) {
|
||||
parser := ArgumentParser{prog: prog, description: desc,
|
||||
epilog: epilog, target: target}
|
||||
targetValue := reflect.ValueOf(target)
|
||||
@@ -116,9 +192,21 @@ func NewArgumentParser(target interface{}, prog, desc, epilog string) (*Argument
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
if withHelp {
|
||||
help := &sHelpArg{}
|
||||
parser.AddArgument(help)
|
||||
}
|
||||
return &parser, nil
|
||||
}
|
||||
|
||||
func NewArgumentParser(target interface{}, prog, desc, epilog string) (*ArgumentParser, error) {
|
||||
return newArgumentParser(target, prog, desc, epilog, false)
|
||||
}
|
||||
|
||||
func NewArgumentParserWithHelp(target interface{}, prog, desc, epilog string) (*ArgumentParser, error) {
|
||||
return newArgumentParser(target, prog, desc, epilog, true)
|
||||
}
|
||||
|
||||
const (
|
||||
/*
|
||||
help text of the argument
|
||||
@@ -710,8 +798,16 @@ func (this *SubcommandArgument) String() string {
|
||||
}
|
||||
|
||||
func (this *SubcommandArgument) AddSubParser(target interface{}, command string, desc string, callback interface{}) (*ArgumentParser, error) {
|
||||
return this.addSubParser(target, command, desc, callback, false)
|
||||
}
|
||||
|
||||
func (this *SubcommandArgument) AddSubParserWithHelp(target interface{}, command string, desc string, callback interface{}) (*ArgumentParser, error) {
|
||||
return this.addSubParser(target, command, desc, callback, true)
|
||||
}
|
||||
|
||||
func (this *SubcommandArgument) addSubParser(target interface{}, command string, desc string, callback interface{}, withHelp bool) (*ArgumentParser, error) {
|
||||
prog := fmt.Sprintf("%s %s", this.parser.prog, command)
|
||||
parser, e := NewArgumentParser(target, prog, desc, "")
|
||||
parser, e := newArgumentParser(target, prog, desc, "", withHelp)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
@@ -922,8 +1018,14 @@ func (this *ArgumentParser) ParseArgs2(args []string, ignore_unknown bool, setDe
|
||||
|
||||
this.reset()
|
||||
|
||||
isAllowInputHelp := this.allowInputHelp()
|
||||
|
||||
for i := 0; i < len(args) && err == nil; i++ {
|
||||
argStr = args[i]
|
||||
if isAllowInputHelp && argStr == "--help" {
|
||||
this.help = true
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(argStr, "-") {
|
||||
arg, nega := this.findOptionalArgument(strings.TrimLeft(argStr, "-"), false)
|
||||
if arg != nil {
|
||||
@@ -978,6 +1080,9 @@ func (this *ArgumentParser) ParseArgs2(args []string, ignore_unknown bool, setDe
|
||||
}
|
||||
}
|
||||
}
|
||||
if this.help {
|
||||
return nil
|
||||
}
|
||||
if err == nil && pos_idx < len(this.posArgs) {
|
||||
err = &NotEnoughArgumentsError{argument: this.posArgs[pos_idx]}
|
||||
}
|
||||
@@ -1205,3 +1310,17 @@ func (this *ArgumentParser) GetOptArgs() []Argument {
|
||||
func (this *ArgumentParser) GetPosArgs() []Argument {
|
||||
return this.posArgs
|
||||
}
|
||||
|
||||
func (this *ArgumentParser) allowInputHelp() bool {
|
||||
help := &sHelpArg{}
|
||||
for _, arg := range this.optArgs {
|
||||
if arg.String() == help.String() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *ArgumentParser) IsHelpSet() bool {
|
||||
return this.help
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user