Merge pull request #13480 from ioito/hotfix/qx-climc-help

fix(climc): support --help args
This commit is contained in:
Zexi Li
2022-02-24 11:53:14 +08:00
committed by GitHub
57 changed files with 2182 additions and 1916 deletions
+13 -23
View File
@@ -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
}
+5 -5
View File
@@ -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)
}
+2 -3
View File
@@ -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
)
+9 -6
View File
@@ -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
View File
@@ -2,6 +2,7 @@
*.o
*.a
*.so
bin/
# Folders
pkg/
+39 -1
View File
@@ -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
View File
@@ -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:
![options](https://github.com/c-bata/assets/raw/master/go-prompt/prompt-options.png)
* **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 animation](https://github.com/c-bata/assets/raw/master/go-prompt/input-parser.gif)
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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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.
[![Go Report Card](https://goreportcard.com/badge/github.com/c-bata/go-prompt)](https://goreportcard.com/report/github.com/c-bata/go-prompt)
![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)
[![GoDoc](https://godoc.org/github.com/c-bata/go-prompt?status.svg)](https://godoc.org/github.com/c-bata/go-prompt)
[![Build Status](https://travis-ci.org/c-bata/go-prompt.svg?branch=master)](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.
[![options](https://github.com/c-bata/assets/raw/master/go-prompt/prompt-options.png)](#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](https://github.com/c-bata/assets/raw/master/go-prompt/keyboard-shortcuts.gif)](#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](https://github.com/c-bata/assets/raw/master/go-prompt/history.gif)](#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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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,
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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,
}
}
+7 -3
View File
@@ -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
View File
@@ -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
}