pax_global_header 0000666 0000000 0000000 00000000064 13731402157 0014515 g ustar 00root root 0000000 0000000 52 comment=01aad35f3856bd90de9cbb9b23c6850071a95430
go-prompt-0.2.5/ 0000775 0000000 0000000 00000000000 13731402157 0013445 5 ustar 00root root 0000000 0000000 go-prompt-0.2.5/.github/ 0000775 0000000 0000000 00000000000 13731402157 0015005 5 ustar 00root root 0000000 0000000 go-prompt-0.2.5/.github/ISSUE_TEMPLATE/ 0000775 0000000 0000000 00000000000 13731402157 0017170 5 ustar 00root root 0000000 0000000 go-prompt-0.2.5/.github/ISSUE_TEMPLATE/bug_report.md 0000664 0000000 0000000 00000001254 13731402157 0021664 0 ustar 00root root 0000000 0000000 ---
name: "Bug report"
about: Create a bug report to improve go-prompt
title: "[Bug]"
labels: bug
assignees: ''
---
# Bug reports
*Please file a bug report here.*
## Expected Behavior
*Please describe the behavior you are expecting*
## Current Behavior and Steps to Reproduce
*What is the current behavior? Please provide detailed steps for reproducing the issue.*
*A picture or gif animation tells a thousand words*
## Context
Please provide any relevant information about your setup. This is important in case the issue is not reproducible except for under certain conditions.
* Operating System:
* Terminal Emulator: (i.e. iTerm2)
* tag of go-prompt or commit revision:
go-prompt-0.2.5/.github/ISSUE_TEMPLATE/feature_request.md 0000664 0000000 0000000 00000000316 13731402157 0022715 0 ustar 00root root 0000000 0000000 ---
name: "Feature request"
about: Suggest an idea for new features in go-prompt.
title: "[Feature Request]"
labels: enhancement
assignees: ''
---
# Feature Request
*Please write your suggestion here.*
go-prompt-0.2.5/.github/workflows/ 0000775 0000000 0000000 00000000000 13731402157 0017042 5 ustar 00root root 0000000 0000000 go-prompt-0.2.5/.github/workflows/test.yml 0000664 0000000 0000000 00000003126 13731402157 0020546 0 ustar 00root root 0000000 0000000 name: tests
on:
pull_request:
branches:
- master
jobs:
test:
name: Run tests
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- name: Set up Go 1.14
uses: actions/setup-go@v2
with:
go-version: 1.14
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@master
- name: Running go tests
env:
GO111MODULE: on
run: make test
examples:
name: Build examples
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.14
uses: actions/setup-go@v2
with:
go-version: 1.14
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@master
- name: Building go examples
env:
GO111MODULE: on
run: ./_example/build.sh
lint:
name: Run lint checks
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.14
uses: actions/setup-go@v2
with:
go-version: 1.14
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@master
- name: Download golangci-lint
run: |
wget https://github.com/golangci/golangci-lint/releases/download/v1.31.0/golangci-lint-1.31.0-linux-amd64.tar.gz
tar -xvf ./golangci-lint-1.31.0-linux-amd64.tar.gz
- name: Running golangci-lint
env:
GO111MODULE: on
GOPATH: /home/runner/work/
run: GOCILINT=./golangci-lint-1.31.0-linux-amd64/golangci-lint make lint
go-prompt-0.2.5/.gitignore 0000664 0000000 0000000 00000000416 13731402157 0015436 0 ustar 00root root 0000000 0000000 # Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
bin/
# Folders
pkg/
_obj
_test
# Architecture specific extensions/prefixes
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
# Glide
vendor/
go-prompt-0.2.5/CHANGELOG.md 0000664 0000000 0000000 00000005517 13731402157 0015266 0 ustar 00root root 0000000 0000000 # 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.~~
* 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
* Fix a Shift+Tab handling on Windows.
* Fix 4-dimension arrow keys handling on Windows.
## v0.2.0 (2018/02/13)
### What's New?
* Supports scrollbar when there are too many matched suggestions
* Windows support (but please caution because this is still not perfect).
* Add OptionLivePrefix to update the prefix dynamically
* Implement clear screen by `Ctrl+L`.
### Fixed
* Fix the behavior of `Ctrl+W` keybind.
* Fix the panic because when running on a docker container (please see [here](https://github.com/c-bata/go-prompt/pull/32) for details).
* Fix panic when making terminal window small size after input 2 lines of texts. See [here](https://github.com/c-bata/go-prompt/issues/37) for details).
* And also fixed many bugs that layout is broken when using Terminal.app, GNU Terminal and a Goland(IntelliJ).
### News
New core developers are joined (alphabetical order).
* Nao Yonashiro (Github @orisano)
* Ryoma Abe (Github @Allajah)
* Yusuke Nakamura (Github @unasuke)
## v0.1.0 (2017/08/15)
Initial Release
go-prompt-0.2.5/LICENSE 0000664 0000000 0000000 00000002060 13731402157 0014450 0 ustar 00root root 0000000 0000000 MIT License
Copyright (c) 2017 Masashi SHIBATA
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
go-prompt-0.2.5/Makefile 0000664 0000000 0000000 00000002231 13731402157 0015103 0 ustar 00root root 0000000 0000000 .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 -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: $(SOURCES) ## Formatting source codes.
@$(GOIMPORTS) -w $^
.PHONY: lint
lint: ## Run golangci-lint.
@$(GOCILINT) run --no-config --disable-all --enable=goimports --enable=misspell ./...
.PHONY: test
test: ## Run tests with race condition checking.
@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
@echo "Commands:"
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-10s\033[0m %s\n", $$1, $$2}'
go-prompt-0.2.5/README.md 0000664 0000000 0000000 00000013555 13731402157 0014735 0 ustar 00root root 0000000 0000000 # go-prompt
[](https://goreportcard.com/report/github.com/c-bata/go-prompt)

[](https://godoc.org/github.com/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
import (
"fmt"
"github.com/c-bata/go-prompt"
)
func completer(d prompt.Document) []prompt.Suggest {
s := []prompt.Suggest{
{Text: "users", Description: "Store the username and age"},
{Text: "articles", Description: "Store the article text posted by user"},
{Text: "comments", Description: "Store the text commented to articles"},
}
return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
}
func main() {
fmt.Println("Please select table.")
t := prompt.Input("> ", completer)
fmt.Println("You selected " + t)
}
```
#### 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)
* [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)
* [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
### Powerful auto-completion
[](https://github.com/c-bata/kube-prompt)
(This is a GIF animation of kube-prompt.)
### Flexible options
go-prompt provides many options. Please check [option section of GoDoc](https://godoc.org/github.com/c-bata/go-prompt#Option) for more details.
[](#flexible-options)
### Keyboard Shortcuts
Emacs-like keyboard shortcuts are available by default (these also are the default shortcuts in Bash shell).
You can customize and expand these shortcuts.
[](#keyboard-shortcuts)
Key Binding | Description
---------------------|---------------------------------------------------------
Ctrl + A | Go to the beginning of the line (Home)
Ctrl + E | Go to the end of the line (End)
Ctrl + P | Previous command (Up arrow)
Ctrl + N | Next command (Down arrow)
Ctrl + F | Forward one character
Ctrl + B | Backward one character
Ctrl + D | Delete character under the cursor
Ctrl + H | Delete character before the cursor (Backspace)
Ctrl + W | Cut the word before the cursor to the clipboard
Ctrl + K | Cut the line after the cursor to the clipboard
Ctrl + U | Cut the line before the cursor to the clipboard
Ctrl + L | Clear the screen
### History
You can use Up arrow and Down arrow to walk through the history of commands executed.
[](#history)
### Multiple platform support
We have confirmed go-prompt works fine in the following terminals:
* iTerm2 (macOS)
* Terminal.app (macOS)
* Command Prompt (Windows)
* gnome-terminal (Ubuntu)
## Links
* [Change Log](./CHANGELOG.md)
* [GoDoc](http://godoc.org/github.com/c-bata/go-prompt)
* [gocover.io](https://gocover.io/github.com/c-bata/go-prompt)
## Author
Masashi Shibata
* Twitter: [@c\_bata\_](https://twitter.com/c_bata_/)
* Github: [@c-bata](https://github.com/c-bata/)
## License
This software is licensed under the MIT license, see [LICENSE](./LICENSE) for more information.
go-prompt-0.2.5/_example/ 0000775 0000000 0000000 00000000000 13731402157 0015237 5 ustar 00root root 0000000 0000000 go-prompt-0.2.5/_example/README.md 0000664 0000000 0000000 00000002116 13731402157 0016516 0 ustar 00root root 0000000 0000000 # Examples of go-prompt
This directory includes some examples using go-prompt.
These examples are useful to know the usage of go-prompt and check behavior for development.
## simple-echo

A simple echo example using `prompt.Input`.
## http-prompt

A simple [http-prompt](https://github.com/eliangcs/http-prompt) implementation using go-prompt in less than 200 lines of Go.
## live-prefix

A example application which changes a prefix string dynamically.
This feature is used like [ktr0731/evans](https://github.com/ktr0731/evans) which is interactive gRPC client using go-prompt.
## exec-command
Run another CLI tool via `os/exec` package.
More practical example is [a source code of kube-prompt](https://github.com/c-bata/kube-prompt).
I recommend you to look this if you want to create tools like kube-prompt.
go-prompt-0.2.5/_example/build.sh 0000775 0000000 0000000 00000000725 13731402157 0016701 0 ustar 00root root 0000000 0000000 #!/bin/sh
export GO111MODULE=on
DIR=$(cd $(dirname $0); pwd)
BIN_DIR=$(cd $(dirname $(dirname $0)); pwd)/bin
mkdir -p ${BIN_DIR}
go build -o ${BIN_DIR}/exec-command ${DIR}/exec-command/main.go
go build -o ${BIN_DIR}/http-prompt ${DIR}/http-prompt/main.go
go build -o ${BIN_DIR}/live-prefix ${DIR}/live-prefix/main.go
go build -o ${BIN_DIR}/simple-echo ${DIR}/simple-echo/main.go
go build -o ${BIN_DIR}/simple-echo-cjk-cyrillic ${DIR}/simple-echo/cjk-cyrillic/main.go
go-prompt-0.2.5/_example/exec-command/ 0000775 0000000 0000000 00000000000 13731402157 0017577 5 ustar 00root root 0000000 0000000 go-prompt-0.2.5/_example/exec-command/main.go 0000664 0000000 0000000 00000000655 13731402157 0021060 0 ustar 00root root 0000000 0000000 package main
import (
"os"
"os/exec"
prompt "github.com/c-bata/go-prompt"
)
func executor(t string) {
if t == "bash" {
cmd := exec.Command("bash")
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Run()
}
return
}
func completer(t prompt.Document) []prompt.Suggest {
return []prompt.Suggest{
{Text: "bash"},
}
}
func main() {
p := prompt.New(
executor,
completer,
)
p.Run()
}
go-prompt-0.2.5/_example/http-prompt/ 0000775 0000000 0000000 00000000000 13731402157 0017535 5 ustar 00root root 0000000 0000000 go-prompt-0.2.5/_example/http-prompt/api.py 0000664 0000000 0000000 00000000517 13731402157 0020663 0 ustar 00root root 0000000 0000000 from bottle import route, run, request
@route('/')
def hello():
return "Hello World!"
@route('/ping')
def hello():
return "pong!"
@route('/register', method='POST')
def register():
name = request.json.get("name")
return "Hello %s!" % name
if __name__ == "__main__":
run(host='localhost', port=8000, debug=True)
go-prompt-0.2.5/_example/http-prompt/main.go 0000664 0000000 0000000 00000012554 13731402157 0021017 0 ustar 00root root 0000000 0000000 package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"path"
"strings"
prompt "github.com/c-bata/go-prompt"
)
type RequestContext struct {
url *url.URL
header http.Header
client *http.Client
}
var ctx *RequestContext
// See https://github.com/eliangcs/http-prompt/blob/master/http_prompt/completion.py
var suggestions = []prompt.Suggest{
// Command
{"cd", "Change URL/path"},
{"exit", "Exit http-prompt"},
// HTTP Method
{"delete", "DELETE request"},
{"get", "GET request"},
{"patch", "GET request"},
{"post", "POST request"},
{"put", "PUT request"},
// HTTP Header
{"Accept", "Acceptable response media type"},
{"Accept-Charset", "Acceptable response charsets"},
{"Accept-Encoding", "Acceptable response content codings"},
{"Accept-Language", "Preferred natural languages in response"},
{"ALPN", "Application-layer protocol negotiation to use"},
{"Alt-Used", "Alternative host in use"},
{"Authorization", "Authentication information"},
{"Cache-Control", "Directives for caches"},
{"Connection", "Connection options"},
{"Content-Encoding", "Content codings"},
{"Content-Language", "Natural languages for content"},
{"Content-Length", "Anticipated size for payload body"},
{"Content-Location", "Where content was obtained"},
{"Content-MD5", "Base64-encoded MD5 sum of content"},
{"Content-Type", "Content media type"},
{"Cookie", "Stored cookies"},
{"Date", "Datetime when message was originated"},
{"Depth", "Applied only to resource or its members"},
{"DNT", "Do not track user"},
{"Expect", "Expected behaviors supported by server"},
{"Forwarded", "Proxies involved"},
{"From", "Sender email address"},
{"Host", "Target URI"},
{"HTTP2-Settings", "HTTP/2 connection parameters"},
{"If", "Request condition on state tokens and ETags"},
{"If-Match", "Request condition on target resource"},
{"If-Modified-Since", "Request condition on modification date"},
{"If-None-Match", "Request condition on target resource"},
{"If-Range", "Request condition on Range"},
{"If-Schedule-Tag-Match", "Request condition on Schedule-Tag"},
{"If-Unmodified-Since", "Request condition on modification date"},
{"Max-Forwards", "Max number of times forwarded by proxies"},
{"MIME-Version", "Version of MIME protocol"},
{"Origin", "Origin(s} issuing the request"},
{"Pragma", "Implementation-specific directives"},
{"Prefer", "Preferred server behaviors"},
{"Proxy-Authorization", "Proxy authorization credentials"},
{"Proxy-Connection", "Proxy connection options"},
{"Range", "Request transfer of only part of data"},
{"Referer", "Previous web page"},
{"TE", "Transfer codings willing to accept"},
{"Transfer-Encoding", "Transfer codings applied to payload body"},
{"Upgrade", "Invite server to upgrade to another protocol"},
{"User-Agent", "User agent string"},
{"Via", "Intermediate proxies"},
{"Warning", "Possible incorrectness with payload body"},
{"WWW-Authenticate", "Authentication scheme"},
{"X-Csrf-Token", "Prevent cross-site request forgery"},
{"X-CSRFToken", "Prevent cross-site request forgery"},
{"X-Forwarded-For", "Originating client IP address"},
{"X-Forwarded-Host", "Original host requested by client"},
{"X-Forwarded-Proto", "Originating protocol"},
{"X-Http-Method-Override", "Request method override"},
{"X-Requested-With", "Used to identify Ajax requests"},
{"X-XSRF-TOKEN", "Prevent cross-site request forgery"},
}
func livePrefix() (string, bool) {
if ctx.url.Path == "/" {
return "", false
}
return ctx.url.String() + "> ", true
}
func executor(in string) {
in = strings.TrimSpace(in)
var method, body string
blocks := strings.Split(in, " ")
switch blocks[0] {
case "exit":
fmt.Println("Bye!")
os.Exit(0)
case "cd":
if len(blocks) < 2 {
ctx.url.Path = "/"
} else {
ctx.url.Path = path.Join(ctx.url.Path, blocks[1])
}
return
case "get", "delete":
method = strings.ToUpper(blocks[0])
case "post", "put", "patch":
if len(blocks) < 2 {
fmt.Println("please set request body.")
return
}
body = strings.Join(blocks[1:], " ")
method = strings.ToUpper(blocks[0])
}
if method != "" {
req, err := http.NewRequest(method, ctx.url.String(), strings.NewReader(body))
if err != nil {
fmt.Println("err: " + err.Error())
return
}
req.Header = ctx.header
res, err := ctx.client.Do(req)
if err != nil {
fmt.Println("err: " + err.Error())
return
}
result, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println("err: " + err.Error())
return
}
fmt.Printf("%s\n", result)
ctx.header = http.Header{}
return
}
if h := strings.Split(in, ":"); len(h) == 2 {
// Handling HTTP Header
ctx.header.Add(strings.TrimSpace(h[0]), strings.Trim(h[1], ` '"`))
} else {
fmt.Println("Sorry, I don't understand.")
}
}
func completer(in prompt.Document) []prompt.Suggest {
w := in.GetWordBeforeCursor()
if w == "" {
return []prompt.Suggest{}
}
return prompt.FilterHasPrefix(suggestions, w, true)
}
func main() {
var baseURL = "http://localhost:8000/"
if len(os.Args) == 2 {
baseURL = os.Args[1]
if strings.HasSuffix(baseURL, "/") {
baseURL += "/"
}
}
u, err := url.Parse(baseURL)
if err != nil {
log.Fatal(err)
}
ctx = &RequestContext{
url: u,
header: http.Header{},
client: &http.Client{},
}
p := prompt.New(
executor,
completer,
prompt.OptionPrefix(u.String()+"> "),
prompt.OptionLivePrefix(livePrefix),
prompt.OptionTitle("http-prompt"),
)
p.Run()
}
go-prompt-0.2.5/_example/live-prefix/ 0000775 0000000 0000000 00000000000 13731402157 0017471 5 ustar 00root root 0000000 0000000 go-prompt-0.2.5/_example/live-prefix/main.go 0000664 0000000 0000000 00000002117 13731402157 0020745 0 ustar 00root root 0000000 0000000 package main
import (
"fmt"
prompt "github.com/c-bata/go-prompt"
)
var LivePrefixState struct {
LivePrefix string
IsEnable bool
}
func executor(in string) {
fmt.Println("Your input: " + in)
if in == "" {
LivePrefixState.IsEnable = false
LivePrefixState.LivePrefix = in
return
}
LivePrefixState.LivePrefix = in + "> "
LivePrefixState.IsEnable = true
}
func completer(in prompt.Document) []prompt.Suggest {
s := []prompt.Suggest{
{Text: "users", Description: "Store the username and age"},
{Text: "articles", Description: "Store the article text posted by user"},
{Text: "comments", Description: "Store the text commented to articles"},
{Text: "groups", Description: "Combine users with specific rules"},
}
return prompt.FilterHasPrefix(s, in.GetWordBeforeCursor(), true)
}
func changeLivePrefix() (string, bool) {
return LivePrefixState.LivePrefix, LivePrefixState.IsEnable
}
func main() {
p := prompt.New(
executor,
completer,
prompt.OptionPrefix(">>> "),
prompt.OptionLivePrefix(changeLivePrefix),
prompt.OptionTitle("live-prefix-example"),
)
p.Run()
}
go-prompt-0.2.5/_example/simple-echo/ 0000775 0000000 0000000 00000000000 13731402157 0017444 5 ustar 00root root 0000000 0000000 go-prompt-0.2.5/_example/simple-echo/cjk-cyrillic/ 0000775 0000000 0000000 00000000000 13731402157 0022023 5 ustar 00root root 0000000 0000000 go-prompt-0.2.5/_example/simple-echo/cjk-cyrillic/main.go 0000664 0000000 0000000 00000001440 13731402157 0023275 0 ustar 00root root 0000000 0000000 package main
import (
"fmt"
prompt "github.com/c-bata/go-prompt"
)
func executor(in string) {
fmt.Println("Your input: " + in)
}
func completer(in prompt.Document) []prompt.Suggest {
s := []prompt.Suggest{
{Text: "こんにちは", Description: "'こんにちは' means 'Hello' in Japanese"},
{Text: "감사합니다", Description: "'안녕하세요' means 'Hello' in Korean."},
{Text: "您好", Description: "'您好' means 'Hello' in Chinese."},
{Text: "Добрый день", Description: "'Добрый день' means 'Hello' in Russian."},
}
return prompt.FilterHasPrefix(s, in.GetWordBeforeCursor(), true)
}
func main() {
p := prompt.New(
executor,
completer,
prompt.OptionPrefix(">>> "),
prompt.OptionTitle("sql-prompt for multi width characters"),
)
p.Run()
}
go-prompt-0.2.5/_example/simple-echo/main.go 0000664 0000000 0000000 00000001606 13731402157 0020722 0 ustar 00root root 0000000 0000000 package main
import (
"fmt"
prompt "github.com/c-bata/go-prompt"
)
func completer(in prompt.Document) []prompt.Suggest {
s := []prompt.Suggest{
{Text: "users", Description: "Store the username and age"},
{Text: "articles", Description: "Store the article text posted by user"},
{Text: "comments", Description: "Store the text commented to articles"},
{Text: "groups", Description: "Combine users with specific rules"},
}
return prompt.FilterHasPrefix(s, in.GetWordBeforeCursor(), true)
}
func main() {
in := prompt.Input(">>> ", completer,
prompt.OptionTitle("sql-prompt"),
prompt.OptionHistory([]string{"SELECT * FROM users;"}),
prompt.OptionPrefixTextColor(prompt.Yellow),
prompt.OptionPreviewSuggestionTextColor(prompt.Blue),
prompt.OptionSelectedSuggestionBGColor(prompt.LightGray),
prompt.OptionSuggestionBGColor(prompt.DarkGray))
fmt.Println("Your input: " + in)
}
go-prompt-0.2.5/_tools/ 0000775 0000000 0000000 00000000000 13731402157 0014744 5 ustar 00root root 0000000 0000000 go-prompt-0.2.5/_tools/README.md 0000664 0000000 0000000 00000000352 13731402157 0016223 0 ustar 00root root 0000000 0000000 ## Tools of go-prompt
### vt100_debug

### sigwinch

go-prompt-0.2.5/_tools/complete_file/ 0000775 0000000 0000000 00000000000 13731402157 0017553 5 ustar 00root root 0000000 0000000 go-prompt-0.2.5/_tools/complete_file/main.go 0000664 0000000 0000000 00000001443 13731402157 0021030 0 ustar 00root root 0000000 0000000 package main
import (
"fmt"
"os"
"strings"
prompt "github.com/c-bata/go-prompt"
"github.com/c-bata/go-prompt/completer"
)
var filePathCompleter = completer.FilePathCompleter{
IgnoreCase: true,
Filter: func(fi os.FileInfo) bool {
return fi.IsDir() || strings.HasSuffix(fi.Name(), ".go")
},
}
func executor(in string) {
fmt.Println("Your input: " + in)
}
func completerFunc(d prompt.Document) []prompt.Suggest {
t := d.GetWordBeforeCursor()
if strings.HasPrefix(t, "--") {
return []prompt.Suggest{
{"--foo", ""},
{"--bar", ""},
{"--baz", ""},
}
}
return filePathCompleter.Complete(d)
}
func main() {
p := prompt.New(
executor,
completerFunc,
prompt.OptionPrefix(">>> "),
prompt.OptionCompletionWordSeparator(completer.FilePathCompletionSeparator),
)
p.Run()
}
go-prompt-0.2.5/_tools/sigwinch/ 0000775 0000000 0000000 00000000000 13731402157 0016557 5 ustar 00root root 0000000 0000000 go-prompt-0.2.5/_tools/sigwinch/main.go 0000664 0000000 0000000 00000002552 13731402157 0020036 0 ustar 00root root 0000000 0000000 package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"unsafe"
)
// Winsize is winsize struct got from the ioctl(2) system call.
type Winsize struct {
Row uint16
Col uint16
X uint16 // pixel value
Y uint16 // pixel value
}
// GetWinSize returns winsize struct which is the response of ioctl(2).
func GetWinSize(fd int) *Winsize {
ws := &Winsize{}
retCode, _, errno := syscall.Syscall(
syscall.SYS_IOCTL,
uintptr(fd),
uintptr(syscall.TIOCGWINSZ),
uintptr(unsafe.Pointer(ws)))
if int(retCode) == -1 {
panic(errno)
}
return ws
}
func main() {
signalChan := make(chan os.Signal, 1)
signal.Notify(
signalChan,
syscall.SIGHUP,
syscall.SIGINT,
syscall.SIGTERM,
syscall.SIGQUIT,
syscall.SIGWINCH,
)
ws := GetWinSize(syscall.Stdin)
fmt.Printf("Row %d : Col %d\n", ws.Row, ws.Col)
exitChan := make(chan int)
go func() {
for {
s := <-signalChan
switch s {
// kill -SIGHUP XXXX
case syscall.SIGHUP:
exitChan <- 0
// kill -SIGINT XXXX or Ctrl+c
case syscall.SIGINT:
exitChan <- 0
// kill -SIGTERM XXXX
case syscall.SIGTERM:
exitChan <- 0
// kill -SIGQUIT XXXX
case syscall.SIGQUIT:
exitChan <- 0
case syscall.SIGWINCH:
ws := GetWinSize(syscall.Stdin)
fmt.Printf("Row %d : Col %d\n", ws.Row, ws.Col)
default:
exitChan <- 1
}
}
}()
code := <-exitChan
os.Exit(code)
}
go-prompt-0.2.5/_tools/vt100_debug/ 0000775 0000000 0000000 00000000000 13731402157 0016764 5 ustar 00root root 0000000 0000000 go-prompt-0.2.5/_tools/vt100_debug/main.go 0000664 0000000 0000000 00000001432 13731402157 0020237 0 ustar 00root root 0000000 0000000 // +build !windows
package main
import (
"fmt"
"syscall"
prompt "github.com/c-bata/go-prompt"
"github.com/c-bata/go-prompt/internal/term"
)
func main() {
if err := term.SetRaw(syscall.Stdin); err != nil {
fmt.Println(err)
return
}
defer term.Restore()
bufCh := make(chan []byte, 128)
go readBuffer(bufCh)
fmt.Print("> ")
for {
b := <-bufCh
if key := prompt.GetKey(b); key == prompt.NotDefined {
fmt.Printf("Key '%s' data:'%#v'\n", string(b), b)
} else {
if key == prompt.ControlC {
fmt.Println("exit.")
return
}
fmt.Printf("Key '%s' data:'%#v'\n", key, b)
}
fmt.Print("> ")
}
}
func readBuffer(bufCh chan []byte) {
buf := make([]byte, 1024)
for {
if n, err := syscall.Read(syscall.Stdin, buf); err == nil {
bufCh <- buf[:n]
}
}
}
go-prompt-0.2.5/buffer.go 0000664 0000000 0000000 00000013225 13731402157 0015250 0 ustar 00root root 0000000 0000000 package prompt
import (
"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
cacheDocument *Document
preferredColumn int // Remember the original column for the next up/down movement.
lastKeyStroke Key
}
// Text returns string of the current line.
func (b *Buffer) Text() string {
return b.workingLines[b.workingIndex]
}
// Document method to return document instance from the current text and cursor position.
func (b *Buffer) Document() (d *Document) {
if b.cacheDocument == nil ||
b.cacheDocument.Text != b.Text() ||
b.cacheDocument.cursorPosition != b.cursorPosition {
b.cacheDocument = &Document{
Text: b.Text(),
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
if overwrite {
overwritten := string(or[oc : oc+len(v)])
if strings.Contains(overwritten, "\n") {
i := strings.IndexAny(overwritten, "\n")
overwritten = overwritten[:i]
}
b.setText(string(or[:oc]) + v + string(or[oc+len(overwritten):]))
} else {
b.setText(string(or[:oc]) + v + string(or[oc:]))
}
if moveCursor {
b.cursorPosition += len([]rune(v))
}
}
// 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) {
debug.Assert(b.cursorPosition <= len([]rune(v)), "length of input should be shorter than cursor position")
b.workingLines[b.workingIndex] = v
}
// Set cursor position. Return whether it changed.
func (b *Buffer) setCursorPosition(p int) {
if p > 0 {
b.cursorPosition = p
} else {
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.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
}
// CursorRight move to right on the current line.
func (b *Buffer) CursorRight(count int) {
l := b.Document().GetCursorRightPosition(count)
b.cursorPosition += l
}
// CursorUp move cursor to the previous line.
// (for multi-line edit).
func (b *Buffer) CursorUp(count int) {
orig := b.preferredColumn
if b.preferredColumn == -1 { // -1 means nil
orig = b.Document().CursorPositionCol()
}
b.cursorPosition += b.Document().GetCursorUpPosition(count, orig)
// Remember the original column for the next up/down movement.
b.preferredColumn = orig
}
// CursorDown move cursor to the next line.
// (for multi-line edit).
func (b *Buffer) CursorDown(count int) {
orig := b.preferredColumn
if b.preferredColumn == -1 { // -1 means nil
orig = b.Document().CursorPositionCol()
}
b.cursorPosition += b.Document().GetCursorDownPosition(count, orig)
// Remember the original column for the next up/down movement.
b.preferredColumn = orig
}
// DeleteBeforeCursor delete specified number of characters before cursor and return the deleted text.
func (b *Buffer) DeleteBeforeCursor(count int) (deleted string) {
debug.Assert(count >= 0, "count should be positive")
r := []rune(b.Text())
if b.cursorPosition > 0 {
start := b.cursorPosition - count
if start < 0 {
start = 0
}
deleted = string(r[start:b.cursorPosition])
b.setDocument(&Document{
Text: string(r[:start]) + string(r[b.cursorPosition:]),
cursorPosition: b.cursorPosition - len([]rune(deleted)),
})
}
return
}
// NewLine means CR.
func (b *Buffer) NewLine(copyMargin bool) {
if copyMargin {
b.InsertText("\n"+b.Document().leadingWhitespaceInCurrentLine(), false, true)
} else {
b.InsertText("\n", false, true)
}
}
// 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) {
deleted = b.Document().TextAfterCursor()[:count]
b.setText(string(r[:b.cursorPosition]) + string(r[b.cursorPosition+len(deleted):]))
}
return
}
// 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.Delete(1)
// Remove spaces
b.setText(b.Document().TextBeforeCursor() + separator + strings.TrimLeft(b.Document().TextAfterCursor(), " "))
}
}
// 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:])
}
}
// NewBuffer is constructor of Buffer struct.
func NewBuffer() (b *Buffer) {
b = &Buffer{
workingLines: []string{""},
workingIndex: 0,
preferredColumn: -1, // -1 means nil
}
return
}
go-prompt-0.2.5/buffer_test.go 0000664 0000000 0000000 00000011545 13731402157 0016312 0 ustar 00root root 0000000 0000000 package prompt
import (
"reflect"
"testing"
)
func TestNewBuffer(t *testing.T) {
b := NewBuffer()
if b.workingIndex != 0 {
t.Errorf("workingIndex should be %#v, got %#v", 0, b.workingIndex)
}
if !reflect.DeepEqual(b.workingLines, []string{""}) {
t.Errorf("workingLines should be %#v, got %#v", []string{""}, b.workingLines)
}
}
func TestBuffer_InsertText(t *testing.T) {
b := NewBuffer()
b.InsertText("some_text", false, true)
if b.Text() != "some_text" {
t.Errorf("Text should be %#v, got %#v", "some_text", b.Text())
}
if b.cursorPosition != len("some_text") {
t.Errorf("cursorPosition should be %#v, got %#v", len("some_text"), b.cursorPosition)
}
}
func TestBuffer_CursorMovement(t *testing.T) {
b := NewBuffer()
b.InsertText("some_text", false, true)
b.CursorLeft(1)
b.CursorLeft(2)
b.CursorRight(1)
b.InsertText("A", false, true)
if b.Text() != "some_teAxt" {
t.Errorf("Text should be %#v, got %#v", "some_teAxt", b.Text())
}
if b.cursorPosition != len("some_teA") {
t.Errorf("Text should be %#v, got %#v", len("some_teA"), b.cursorPosition)
}
// Moving over left character counts.
b.CursorLeft(100)
b.InsertText("A", false, true)
if b.Text() != "Asome_teAxt" {
t.Errorf("Text should be %#v, got %#v", "some_teAxt", b.Text())
}
if b.cursorPosition != len("A") {
t.Errorf("Text should be %#v, got %#v", len("some_teA"), b.cursorPosition)
}
// TODO: Going right already at right end.
}
func TestBuffer_CursorMovement_WithMultiByte(t *testing.T) {
b := NewBuffer()
b.InsertText("あいうえお", false, true)
b.CursorLeft(1)
if l := b.Document().TextAfterCursor(); l != "お" {
t.Errorf("Should be 'お', but got %s", l)
}
}
func TestBuffer_CursorUp(t *testing.T) {
b := NewBuffer()
b.InsertText("long line1\nline2", false, true)
b.CursorUp(1)
if b.Document().cursorPosition != 5 {
t.Errorf("Should be %#v, got %#v", 5, b.Document().cursorPosition)
}
// Going up when already at the top.
b.CursorUp(1)
if b.Document().cursorPosition != 5 {
t.Errorf("Should be %#v, got %#v", 5, b.Document().cursorPosition)
}
// Going up to a line that's shorter.
b.setDocument(&Document{})
b.InsertText("line1\nlong line2", false, true)
b.CursorUp(1)
if b.Document().cursorPosition != 5 {
t.Errorf("Should be %#v, got %#v", 5, b.Document().cursorPosition)
}
}
func TestBuffer_CursorDown(t *testing.T) {
b := NewBuffer()
b.InsertText("line1\nline2", false, true)
b.cursorPosition = 3
// Normally going down
b.CursorDown(1)
if b.Document().cursorPosition != len("line1\nlin") {
t.Errorf("Should be %#v, got %#v", len("line1\nlin"), b.Document().cursorPosition)
}
// Going down to a line that's storter.
b = NewBuffer()
b.InsertText("long line1\na\nb", false, true)
b.cursorPosition = 3
b.CursorDown(1)
if b.Document().cursorPosition != len("long line1\na") {
t.Errorf("Should be %#v, got %#v", len("long line1\na"), b.Document().cursorPosition)
}
}
func TestBuffer_DeleteBeforeCursor(t *testing.T) {
b := NewBuffer()
b.InsertText("some_text", false, true)
b.CursorLeft(2)
deleted := b.DeleteBeforeCursor(1)
if b.Text() != "some_txt" {
t.Errorf("Should be %#v, got %#v", "some_txt", b.Text())
}
if deleted != "e" {
t.Errorf("Should be %#v, got %#v", deleted, "e")
}
if b.cursorPosition != len("some_t") {
t.Errorf("Should be %#v, got %#v", len("some_t"), b.cursorPosition)
}
// Delete over the characters length before cursor.
deleted = b.DeleteBeforeCursor(100)
if deleted != "some_t" {
t.Errorf("Should be %#v, got %#v", "some_t", deleted)
}
if b.Text() != "xt" {
t.Errorf("Should be %#v, got %#v", "xt", b.Text())
}
// If cursor position is a beginning of line, it has no effect.
deleted = b.DeleteBeforeCursor(1)
if deleted != "" {
t.Errorf("Should be empty, got %#v", deleted)
}
}
func TestBuffer_NewLine(t *testing.T) {
b := NewBuffer()
b.InsertText(" hello", false, true)
b.NewLine(false)
ac := b.Text()
ex := " hello\n"
if ac != ex {
t.Errorf("Should be %#v, got %#v", ex, ac)
}
b = NewBuffer()
b.InsertText(" hello", false, true)
b.NewLine(true)
ac = b.Text()
ex = " hello\n "
if ac != ex {
t.Errorf("Should be %#v, got %#v", ex, ac)
}
}
func TestBuffer_JoinNextLine(t *testing.T) {
b := NewBuffer()
b.InsertText("line1\nline2\nline3", false, true)
b.CursorUp(1)
b.JoinNextLine(" ")
ac := b.Text()
ex := "line1\nline2 line3"
if ac != ex {
t.Errorf("Should be %#v, got %#v", ex, ac)
}
// Test when there is no '\n' in the text
b = NewBuffer()
b.InsertText("line1", false, true)
b.cursorPosition = 0
b.JoinNextLine(" ")
ac = b.Text()
ex = "line1"
if ac != ex {
t.Errorf("Should be %#v, got %#v", ex, ac)
}
}
func TestBuffer_SwapCharactersBeforeCursor(t *testing.T) {
b := NewBuffer()
b.InsertText("hello world", false, true)
b.CursorLeft(2)
b.SwapCharactersBeforeCursor()
ac := b.Text()
ex := "hello wrold"
if ac != ex {
t.Errorf("Should be %#v, got %#v", ex, ac)
}
}
go-prompt-0.2.5/completer/ 0000775 0000000 0000000 00000000000 13731402157 0015437 5 ustar 00root root 0000000 0000000 go-prompt-0.2.5/completer/file.go 0000664 0000000 0000000 00000004541 13731402157 0016711 0 ustar 00root root 0000000 0000000 package completer
import (
"io/ioutil"
"os"
"os/user"
"path/filepath"
"runtime"
prompt "github.com/c-bata/go-prompt"
"github.com/c-bata/go-prompt/internal/debug"
)
var (
// FilePathCompletionSeparator holds separate characters.
FilePathCompletionSeparator = string([]byte{' ', os.PathSeparator})
)
// FilePathCompleter is a completer for your local file system.
// Please caution that you need to set OptionCompletionWordSeparator(completer.FilePathCompletionSeparator)
// when you use this completer.
type FilePathCompleter struct {
Filter func(fi os.FileInfo) bool
IgnoreCase bool
fileListCache map[string][]prompt.Suggest
}
func cleanFilePath(path string) (dir, base string, err error) {
if path == "" {
return ".", "", nil
}
var endsWithSeparator bool
if len(path) >= 1 && path[len(path)-1] == os.PathSeparator {
endsWithSeparator = true
}
if runtime.GOOS != "windows" && len(path) >= 2 && path[0:2] == "~/" {
me, err := user.Current()
if err != nil {
return "", "", err
}
path = filepath.Join(me.HomeDir, path[1:])
}
path = filepath.Clean(os.ExpandEnv(path))
dir = filepath.Dir(path)
base = filepath.Base(path)
if endsWithSeparator {
dir = path + string(os.PathSeparator) // Append slash(in POSIX) if path ends with slash.
base = "" // Set empty string if path ends with separator.
}
return dir, base, nil
}
// Complete returns suggestions from your local file system.
func (c *FilePathCompleter) Complete(d prompt.Document) []prompt.Suggest {
if c.fileListCache == nil {
c.fileListCache = make(map[string][]prompt.Suggest, 4)
}
path := d.GetWordBeforeCursor()
dir, base, err := cleanFilePath(path)
if err != nil {
debug.Log("completer: cannot get current user:" + err.Error())
return nil
}
if cached, ok := c.fileListCache[dir]; ok {
return prompt.FilterHasPrefix(cached, base, c.IgnoreCase)
}
files, err := ioutil.ReadDir(dir)
if err != nil && os.IsNotExist(err) {
return nil
} else if err != nil {
debug.Log("completer: cannot read directory items:" + err.Error())
return nil
}
suggests := make([]prompt.Suggest, 0, len(files))
for _, f := range files {
if c.Filter != nil && !c.Filter(f) {
continue
}
suggests = append(suggests, prompt.Suggest{Text: f.Name()})
}
c.fileListCache[dir] = suggests
return prompt.FilterHasPrefix(suggests, base, c.IgnoreCase)
}
go-prompt-0.2.5/completion.go 0000664 0000000 0000000 00000010613 13731402157 0016146 0 ustar 00root root 0000000 0000000 package prompt
import (
"strings"
"github.com/c-bata/go-prompt/internal/debug"
runewidth "github.com/mattn/go-runewidth"
)
const (
shortenSuffix = "..."
leftPrefix = " "
leftSuffix = " "
rightPrefix = " "
rightSuffix = " "
)
var (
leftMargin = runewidth.StringWidth(leftPrefix + leftSuffix)
rightMargin = runewidth.StringWidth(rightPrefix + rightSuffix)
completionMargin = leftMargin + rightMargin
)
// Suggest is printed when completing.
type Suggest struct {
Text string
Description string
}
// CompletionManager manages which suggestion is now selected.
type CompletionManager struct {
selected int // -1 means nothing one is selected.
tmp []Suggest
max uint16
completer Completer
verticalScroll int
wordSeparator string
showAtStart bool
}
// GetSelectedSuggestion returns the selected item.
func (c *CompletionManager) GetSelectedSuggestion() (s Suggest, ok bool) {
if c.selected == -1 {
return Suggest{}, false
} else if c.selected < -1 {
debug.Assert(false, "must not reach here")
c.selected = -1
return Suggest{}, false
}
return c.tmp[c.selected], true
}
// GetSuggestions returns the list of suggestion.
func (c *CompletionManager) GetSuggestions() []Suggest {
return c.tmp
}
// Reset to select nothing.
func (c *CompletionManager) Reset() {
c.selected = -1
c.verticalScroll = 0
c.Update(*NewDocument())
}
// Update to update the suggestions.
func (c *CompletionManager) Update(in Document) {
c.tmp = c.completer(in)
}
// Previous to select the previous suggestion item.
func (c *CompletionManager) Previous() {
if c.verticalScroll == c.selected && c.selected > 0 {
c.verticalScroll--
}
c.selected--
c.update()
}
// Next to select the next suggestion item.
func (c *CompletionManager) Next() {
if c.verticalScroll+int(c.max)-1 == c.selected {
c.verticalScroll++
}
c.selected++
c.update()
}
// Completing returns whether the CompletionManager selects something one.
func (c *CompletionManager) Completing() bool {
return c.selected != -1
}
func (c *CompletionManager) update() {
max := int(c.max)
if len(c.tmp) < max {
max = len(c.tmp)
}
if c.selected >= len(c.tmp) {
c.Reset()
} else if c.selected < -1 {
c.selected = len(c.tmp) - 1
c.verticalScroll = len(c.tmp) - max
}
}
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 := runewidth.StringWidth(prefix)
lenSuffix := runewidth.StringWidth(suffix)
lenShorten := runewidth.StringWidth(shortenSuffix)
min := lenPrefix + lenSuffix + lenShorten
for i := 0; i < l; i++ {
o[i] = deleteBreakLineCharacters(o[i])
w := runewidth.StringWidth(o[i])
if width < w {
width = w
}
}
if width == 0 {
return n, 0
}
if min >= max {
return n, 0
}
if lenPrefix+width+lenSuffix > max {
width = max - lenPrefix - lenSuffix
}
for i := 0; i < l; i++ {
x := runewidth.StringWidth(o[i])
if x <= width {
spaces := strings.Repeat(" ", width-x)
n[i] = prefix + o[i] + spaces + suffix
} else if x > width {
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
}
func formatSuggestions(suggests []Suggest, max int) (new []Suggest, width int) {
num := len(suggests)
new = make([]Suggest, num)
left := make([]string, num)
for i := 0; i < num; i++ {
left[i] = suggests[i].Text
}
right := make([]string, num)
for i := 0; i < num; i++ {
right[i] = suggests[i].Description
}
left, leftWidth := formatTexts(left, max, leftPrefix, leftSuffix)
if leftWidth == 0 {
return []Suggest{}, 0
}
right, rightWidth := formatTexts(right, max-leftWidth, rightPrefix, rightSuffix)
for i := 0; i < num; i++ {
new[i] = Suggest{Text: left[i], Description: right[i]}
}
return new, leftWidth + rightWidth
}
// NewCompletionManager returns initialized CompletionManager object.
func NewCompletionManager(completer Completer, max uint16) *CompletionManager {
return &CompletionManager{
selected: -1,
max: max,
completer: completer,
verticalScroll: 0,
}
}
go-prompt-0.2.5/completion_test.go 0000664 0000000 0000000 00000015542 13731402157 0017213 0 ustar 00root root 0000000 0000000 package prompt
import (
"reflect"
"testing"
)
func TestFormatShortSuggestion(t *testing.T) {
var scenarioTable = []struct {
in []Suggest
expected []Suggest
max int
exWidth int
}{
{
in: []Suggest{
{Text: "foo"},
{Text: "bar"},
{Text: "fuga"},
},
expected: []Suggest{
{Text: " foo "},
{Text: " bar "},
{Text: " fuga "},
},
max: 100,
exWidth: 6,
},
{
in: []Suggest{
{Text: "apple", Description: "This is apple."},
{Text: "banana", Description: "This is banana."},
{Text: "coconut", Description: "This is coconut."},
},
expected: []Suggest{
{Text: " apple ", Description: " This is apple. "},
{Text: " banana ", Description: " This is banana. "},
{Text: " coconut ", Description: " This is coconut. "},
},
max: 100,
exWidth: len(" apple " + " This is apple. "),
},
{
in: []Suggest{
{Text: "This is apple."},
{Text: "This is banana."},
{Text: "This is coconut."},
},
expected: []Suggest{
{Text: " Thi... "},
{Text: " Thi... "},
{Text: " Thi... "},
},
max: 8,
exWidth: 8,
},
{
in: []Suggest{
{Text: "This is apple."},
{Text: "This is banana."},
{Text: "This is coconut."},
},
expected: []Suggest{},
max: 3,
exWidth: 0,
},
{
in: []Suggest{
{Text: "--all-namespaces", Description: "-------------------------------------------------------------------------------------------------------------------------------------------"},
{Text: "--allow-missing-template-keys", Description: "-----------------------------------------------------------------------------------------------------------------------------------------------"},
{Text: "--export", Description: "----------------------------------------------------------------------------------------------------------"},
{Text: "-f", Description: "-----------------------------------------------------------------------------------"},
{Text: "--filename", Description: "-----------------------------------------------------------------------------------"},
{Text: "--include-extended-apis", Description: "------------------------------------------------------------------------------------"},
},
expected: []Suggest{
{Text: " --all-namespaces ", Description: " --------------... "},
{Text: " --allow-missing-template-keys ", Description: " --------------... "},
{Text: " --export ", Description: " --------------... "},
{Text: " -f ", Description: " --------------... "},
{Text: " --filename ", Description: " --------------... "},
{Text: " --include-extended-apis ", Description: " --------------... "},
},
max: 50,
exWidth: len(" --include-extended-apis " + " ---------------..."),
},
{
in: []Suggest{
{Text: "--all-namespaces", Description: "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace."},
{Text: "--allow-missing-template-keys", Description: "If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to golang and jsonpath output formats."},
{Text: "--export", Description: "If true, use 'export' for the resources. Exported resources are stripped of cluster-specific information."},
{Text: "-f", Description: "Filename, directory, or URL to files identifying the resource to get from a server."},
{Text: "--filename", Description: "Filename, directory, or URL to files identifying the resource to get from a server."},
{Text: "--include-extended-apis", Description: "If true, include definitions of new APIs via calls to the API server. [default true]"},
},
expected: []Suggest{
{Text: " --all-namespaces ", Description: " If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace. "},
{Text: " --allow-missing-template-keys ", Description: " If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to golang and jsonpath output formats. "},
{Text: " --export ", Description: " If true, use 'export' for the resources. Exported resources are stripped of cluster-specific information. "},
{Text: " -f ", Description: " Filename, directory, or URL to files identifying the resource to get from a server. "},
{Text: " --filename ", Description: " Filename, directory, or URL to files identifying the resource to get from a server. "},
{Text: " --include-extended-apis ", Description: " If true, include definitions of new APIs via calls to the API server. [default true] "},
},
max: 500,
exWidth: len(" --include-extended-apis " + " If true, include definitions of new APIs via calls to the API server. [default true] "),
},
}
for i, s := range scenarioTable {
actual, width := formatSuggestions(s.in, s.max)
if width != s.exWidth {
t.Errorf("[scenario %d] Want %d but got %d\n", i, s.exWidth, width)
}
if !reflect.DeepEqual(actual, s.expected) {
t.Errorf("[scenario %d] Want %#v, but got %#v\n", i, s.expected, actual)
}
}
}
func TestFormatText(t *testing.T) {
var scenarioTable = []struct {
in []string
expected []string
max int
exWidth int
}{
{
in: []string{
"",
"",
},
expected: []string{
"",
"",
},
max: 10,
exWidth: 0,
},
{
in: []string{
"apple",
"banana",
"coconut",
},
expected: []string{
"",
"",
"",
},
max: 2,
exWidth: 0,
},
{
in: []string{
"apple",
"banana",
"coconut",
},
expected: []string{
"",
"",
"",
},
max: len(" " + " " + shortenSuffix),
exWidth: 0,
},
{
in: []string{
"apple",
"banana",
"coconut",
},
expected: []string{
" apple ",
" banana ",
" coconut ",
},
max: 100,
exWidth: len(" coconut "),
},
{
in: []string{
"apple",
"banana",
"coconut",
},
expected: []string{
" a... ",
" b... ",
" c... ",
},
max: 6,
exWidth: 6,
},
}
for i, s := range scenarioTable {
actual, width := formatTexts(s.in, s.max, " ", " ")
if width != s.exWidth {
t.Errorf("[scenario %d] Want %d but got %d\n", i, s.exWidth, width)
}
if !reflect.DeepEqual(actual, s.expected) {
t.Errorf("[scenario %d] Want %#v, but got %#v\n", i, s.expected, actual)
}
}
}
go-prompt-0.2.5/document.go 0000664 0000000 0000000 00000031451 13731402157 0015616 0 ustar 00root root 0000000 0000000 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
// 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,
}
}
// 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
cnt := 0
for len(s) > 0 {
cnt++
r, size := utf8.DecodeRuneInString(s)
if cnt == d.cursorPosition+offset {
return r
}
s = s[size:]
}
return 0
}
// TextBeforeCursor returns the text before the cursor.
func (d *Document) TextBeforeCursor() string {
r := []rune(d.Text)
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:])
}
// GetWordBeforeCursor returns the word before the cursor.
// If we have whitespace before the cursor this returns an empty string.
func (d *Document) GetWordBeforeCursor() string {
x := d.TextBeforeCursor()
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 {
x := d.TextBeforeCursor()
return x[d.FindStartOfPreviousWordWithSpace():]
}
// 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()
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
}
// FindStartOfPreviousWordWithSpace is almost the same as FindStartOfPreviousWord.
// The only difference is to ignore contiguous spaces.
func (d *Document) FindStartOfPreviousWordWithSpace() int {
x := d.TextBeforeCursor()
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")
return s[len(s)-1]
}
// CurrentLineAfterCursor returns the text from the cursor until the end of the line.
func (d *Document) CurrentLineAfterCursor() string {
return strings.Split(d.TextAfterCursor(), "\n")[0]
}
// CurrentLine return the text on the line where the cursor is. (when the input
// consists of just one line, it equals `text`.
func (d *Document) CurrentLine() string {
return d.CurrentLineBeforeCursor() + d.CurrentLineAfterCursor()
}
// Array pointing to the start indexes of all the lines.
func (d *Document) lineStartIndexes() []int {
// TODO: Cache, because this is often reused.
// (If it is used, it's often used many times.
// And this has to be fast for editing big documents!)
lc := d.LineCount()
lengths := make([]int, lc)
for i, l := range d.Lines() {
lengths[i] = len(l)
}
// Calculate cumulative sums.
indexes := make([]int, lc+1)
indexes[0] = 0 // https://github.com/jonathanslenders/python-prompt-toolkit/blob/master/prompt_toolkit/document.py#L189
pos := 0
for i, l := range lengths {
pos += l + 1
indexes[i+1] = pos
}
if lc > 1 {
// Pop the last item. (This is not a new line.)
indexes = indexes[:lc]
}
return indexes
}
// For the index of a character at a certain line, calculate the index of
// the first character on that line.
func (d *Document) findLineStartIndex(index int) (pos int, lineStartIndex int) {
indexes := d.lineStartIndexes()
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)
return
}
// CursorPositionCol returns the current column. (0-based.)
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
return
}
// GetCursorLeftPosition returns the relative position for cursor left.
func (d *Document) GetCursorLeftPosition(count int) int {
if count < 0 {
return d.GetCursorRightPosition(-count)
}
if d.CursorPositionCol() > count {
return -count
}
return -d.CursorPositionCol()
}
// GetCursorRightPosition returns relative position for cursor right.
func (d *Document) GetCursorRightPosition(count int) int {
if count < 0 {
return d.GetCursorLeftPosition(-count)
}
if len(d.CurrentLineAfterCursor()) > count {
return count
}
return len(d.CurrentLineAfterCursor())
}
// GetCursorUpPosition return the relative cursor position (character index) where we would be
// if the user pressed the arrow-up button.
func (d *Document) GetCursorUpPosition(count int, preferredColumn int) int {
var col int
if preferredColumn == -1 { // -1 means nil
col = d.CursorPositionCol()
} else {
col = preferredColumn
}
row := d.CursorPositionRow() - count
if row < 0 {
row = 0
}
return d.TranslateRowColToIndex(row, col) - d.cursorPosition
}
// GetCursorDownPosition return the relative cursor position (character index) where we would be if the
// user pressed the arrow-down button.
func (d *Document) GetCursorDownPosition(count int, preferredColumn int) int {
var col int
if preferredColumn == -1 { // -1 means nil
col = d.CursorPositionCol()
} else {
col = preferredColumn
}
row := d.CursorPositionRow() + count
return d.TranslateRowColToIndex(row, col) - d.cursorPosition
}
// Lines returns the array of all the lines.
func (d *Document) Lines() []string {
// TODO: Cache, because this one is reused very often.
return strings.Split(d.Text, "\n")
}
// LineCount return the number of lines in this document. If the document ends
// with a trailing \n, that counts as the beginning of a new line.
func (d *Document) LineCount() int {
return len(d.Lines())
}
// TranslateIndexToPosition given an index for the text, return the corresponding (row, col) tuple.
// (0-based. Returns (0, 0) for index=0.)
func (d *Document) TranslateIndexToPosition(index int) (row int, col int) {
row, rowIndex := d.findLineStartIndex(index)
col = index - rowIndex
return
}
// TranslateRowColToIndex given a (row, col), return the corresponding index.
// (Row and col params are 0-based.)
func (d *Document) TranslateRowColToIndex(row int, column int) (index int) {
indexes := d.lineStartIndexes()
if row < 0 {
row = 0
} else if row > len(indexes) {
row = len(indexes) - 1
}
index = indexes[row]
line := d.Lines()[row]
// python) result += max(0, min(col, len(line)))
if column > 0 || len(line) > 0 {
if column > len(line) {
index += len(line)
} else {
index += column
}
}
// Keep in range. (len(self.text) is included, because the cursor can be
// right after the end of the text as well.)
// python) result = max(0, min(result, len(self.text)))
if index > len(d.Text) {
index = len(d.Text)
}
if index < 0 {
index = 0
}
return index
}
// OnLastLine returns true when we are at the last line.
func (d *Document) OnLastLine() bool {
return d.CursorPositionRow() == (d.LineCount() - 1)
}
// GetEndOfLinePosition returns relative position for the end of this line.
func (d *Document) GetEndOfLinePosition() int {
return len([]rune(d.CurrentLineAfterCursor()))
}
func (d *Document) leadingWhitespaceInCurrentLine() (margin string) {
trimmed := strings.TrimSpace(d.CurrentLine())
margin = d.CurrentLine()[:len(d.CurrentLine())-len(trimmed)]
return
}
go-prompt-0.2.5/document_test.go 0000664 0000000 0000000 00000070412 13731402157 0016655 0 ustar 00root root 0000000 0000000 package prompt
import (
"fmt"
"reflect"
"testing"
"unicode/utf8"
)
func ExampleDocument_CurrentLine() {
d := &Document{
Text: `Hello! my name is c-bata.
This is a example of Document component.
This component has texts displayed in terminal and cursor position.
`,
cursorPosition: len(`Hello! my name is c-bata.
This is a exam`),
}
fmt.Println(d.CurrentLine())
// Output:
// This is a example of Document component.
}
func ExampleDocument_DisplayCursorPosition() {
d := &Document{
Text: `Hello! my name is c-bata.`,
cursorPosition: len(`Hello`),
}
fmt.Println("DisplayCursorPosition", d.DisplayCursorPosition())
// Output:
// DisplayCursorPosition 5
}
func ExampleDocument_CursorPositionRow() {
d := &Document{
Text: `Hello! my name is c-bata.
This is a example of Document component.
This component has texts displayed in terminal and cursor position.
`,
cursorPosition: len(`Hello! my name is c-bata.
This is a exam`),
}
fmt.Println("CursorPositionRow", d.CursorPositionRow())
// Output:
// CursorPositionRow 1
}
func ExampleDocument_CursorPositionCol() {
d := &Document{
Text: `Hello! my name is c-bata.
This is a example of Document component.
This component has texts displayed in terminal and cursor position.
`,
cursorPosition: len(`Hello! my name is c-bata.
This is a exam`),
}
fmt.Println("CursorPositionCol", d.CursorPositionCol())
// Output:
// CursorPositionCol 14
}
func ExampleDocument_TextBeforeCursor() {
d := &Document{
Text: `Hello! my name is c-bata.
This is a example of Document component.
This component has texts displayed in terminal and cursor position.
`,
cursorPosition: len(`Hello! my name is c-bata.
This is a exam`),
}
fmt.Println(d.TextBeforeCursor())
// Output:
// Hello! my name is c-bata.
// This is a exam
}
func ExampleDocument_TextAfterCursor() {
d := &Document{
Text: `Hello! my name is c-bata.
This is a example of Document component.
This component has texts displayed in terminal and cursor position.
`,
cursorPosition: len(`Hello! my name is c-bata.
This is a exam`),
}
fmt.Println(d.TextAfterCursor())
// Output:
// ple of Document component.
// This component has texts displayed in terminal and cursor position.
}
func ExampleDocument_DisplayCursorPosition_withJapanese() {
d := &Document{
Text: `こんにちは、芝田 将です。`,
cursorPosition: 3,
}
fmt.Println("DisplayCursorPosition", d.DisplayCursorPosition())
// Output:
// DisplayCursorPosition 6
}
func ExampleDocument_CurrentLineBeforeCursor() {
d := &Document{
Text: `Hello! my name is c-bata.
This is a example of Document component.
This component has texts displayed in terminal and cursor position.
`,
cursorPosition: len(`Hello! my name is c-bata.
This is a exam`),
}
fmt.Println(d.CurrentLineBeforeCursor())
// Output:
// This is a exam
}
func ExampleDocument_CurrentLineAfterCursor() {
d := &Document{
Text: `Hello! my name is c-bata.
This is a example of Document component.
This component has texts displayed in terminal and cursor position.
`,
cursorPosition: len(`Hello! my name is c-bata.
This is a exam`),
}
fmt.Println(d.CurrentLineAfterCursor())
// Output:
// ple of Document component.
}
func ExampleDocument_GetWordBeforeCursor() {
d := &Document{
Text: `Hello! my name is c-bata.
This is a example of Document component.
`,
cursorPosition: len(`Hello! my name is c-bata.
This is a exam`),
}
fmt.Println(d.GetWordBeforeCursor())
// Output:
// exam
}
func ExampleDocument_GetWordAfterCursor() {
d := &Document{
Text: `Hello! my name is c-bata.
This is a example of Document component.
`,
cursorPosition: len(`Hello! my name is c-bata.
This is a exam`),
}
fmt.Println(d.GetWordAfterCursor())
// Output:
// ple
}
func ExampleDocument_GetWordBeforeCursorWithSpace() {
d := &Document{
Text: `Hello! my name is c-bata.
This is a example of Document component.
`,
cursorPosition: len(`Hello! my name is c-bata.
This is a example `),
}
fmt.Println(d.GetWordBeforeCursorWithSpace())
// Output:
// example
}
func ExampleDocument_GetWordAfterCursorWithSpace() {
d := &Document{
Text: `Hello! my name is c-bata.
This is a example of Document component.
`,
cursorPosition: len(`Hello! my name is c-bata.
This is a`),
}
fmt.Println(d.GetWordAfterCursorWithSpace())
// Output:
// example
}
func ExampleDocument_GetWordBeforeCursorUntilSeparator() {
d := &Document{
Text: `hello,i am c-bata`,
cursorPosition: len(`hello,i am c`),
}
fmt.Println(d.GetWordBeforeCursorUntilSeparator(","))
// Output:
// i am c
}
func ExampleDocument_GetWordAfterCursorUntilSeparator() {
d := &Document{
Text: `hello,i am c-bata,thank you for using go-prompt`,
cursorPosition: len(`hello,i a`),
}
fmt.Println(d.GetWordAfterCursorUntilSeparator(","))
// Output:
// m c-bata
}
func ExampleDocument_GetWordBeforeCursorUntilSeparatorIgnoreNextToCursor() {
d := &Document{
Text: `hello,i am c-bata,thank you for using go-prompt`,
cursorPosition: len(`hello,i am c-bata,`),
}
fmt.Println(d.GetWordBeforeCursorUntilSeparatorIgnoreNextToCursor(","))
// Output:
// i am c-bata,
}
func ExampleDocument_GetWordAfterCursorUntilSeparatorIgnoreNextToCursor() {
d := &Document{
Text: `hello,i am c-bata,thank you for using go-prompt`,
cursorPosition: len(`hello`),
}
fmt.Println(d.GetWordAfterCursorUntilSeparatorIgnoreNextToCursor(","))
// Output:
// ,i am c-bata
}
func TestDocument_DisplayCursorPosition(t *testing.T) {
patterns := []struct {
document *Document
expected int
}{
{
document: &Document{
Text: "hello",
cursorPosition: 2,
},
expected: 2,
},
{
document: &Document{
Text: "こんにちは",
cursorPosition: 2,
},
expected: 4,
},
{
// If you're facing test failure on this test case and your terminal is iTerm2,
// please check 'Profile -> Text' configuration. 'Use Unicode version 9 widths'
// must be checked.
// https://github.com/c-bata/go-prompt/pull/99
document: &Document{
Text: "Добрый день",
cursorPosition: 3,
},
expected: 3,
},
}
for _, p := range patterns {
ac := p.document.DisplayCursorPosition()
if ac != p.expected {
t.Errorf("Should be %#v, got %#v", p.expected, ac)
}
}
}
func TestDocument_GetCharRelativeToCursor(t *testing.T) {
patterns := []struct {
document *Document
expected string
}{
{
document: &Document{
Text: "line 1\nline 2\nline 3\nline 4\n",
cursorPosition: len([]rune("line 1\n" + "lin")),
},
expected: "e",
},
{
document: &Document{
Text: "あいうえお\nかきくけこ\nさしすせそ\nたちつてと\n",
cursorPosition: 8,
},
expected: "く",
},
{
document: &Document{
Text: "Добрый\nдень\nДобрый день",
cursorPosition: 9,
},
expected: "н",
},
}
for i, p := range patterns {
ac := p.document.GetCharRelativeToCursor(1)
ex, _ := utf8.DecodeRuneInString(p.expected)
if ac != ex {
t.Errorf("[%d] Should be %s, got %s", i, string(ex), string(ac))
}
}
}
func TestDocument_TextBeforeCursor(t *testing.T) {
patterns := []struct {
document *Document
expected string
}{
{
document: &Document{
Text: "line 1\nline 2\nline 3\nline 4\n",
cursorPosition: len("line 1\n" + "lin"),
},
expected: "line 1\nlin",
},
{
document: &Document{
Text: "あいうえお\nかきくけこ\nさしすせそ\nたちつてと\n",
cursorPosition: 8,
},
expected: "あいうえお\nかき",
},
{
document: &Document{
Text: "Добрый\nдень\nДобрый день",
cursorPosition: 9,
},
expected: "Добрый\nде",
},
}
for i, p := range patterns {
ac := p.document.TextBeforeCursor()
if ac != p.expected {
t.Errorf("[%d] Should be %s, got %s", i, p.expected, ac)
}
}
}
func TestDocument_TextAfterCursor(t *testing.T) {
pattern := []struct {
document *Document
expected string
}{
{
document: &Document{
Text: "line 1\nline 2\nline 3\nline 4\n",
cursorPosition: len("line 1\n" + "lin"),
},
expected: "e 2\nline 3\nline 4\n",
},
{
document: &Document{
Text: "",
cursorPosition: 0,
},
expected: "",
},
{
document: &Document{
Text: "あいうえお\nかきくけこ\nさしすせそ\nたちつてと\n",
cursorPosition: 8,
},
expected: "くけこ\nさしすせそ\nたちつてと\n",
},
{
document: &Document{
Text: "Добрый\nдень\nДобрый день",
cursorPosition: 9,
},
expected: "нь\nДобрый день",
},
}
for i, p := range pattern {
ac := p.document.TextAfterCursor()
if ac != p.expected {
t.Errorf("[%d] Should be %#v, got %#v", i, p.expected, ac)
}
}
}
func TestDocument_GetWordBeforeCursor(t *testing.T) {
pattern := []struct {
document *Document
expected string
sep string
}{
{
document: &Document{
Text: "apple bana",
cursorPosition: len("apple bana"),
},
expected: "bana",
},
{
document: &Document{
Text: "apply -f ./file/foo.json",
cursorPosition: len("apply -f ./file/foo.json"),
},
expected: "foo.json",
sep: " /",
},
{
document: &Document{
Text: "apple banana orange",
cursorPosition: len("apple ba"),
},
expected: "ba",
},
{
document: &Document{
Text: "apply -f ./file/foo.json",
cursorPosition: len("apply -f ./fi"),
},
expected: "fi",
sep: " /",
},
{
document: &Document{
Text: "apple ",
cursorPosition: len("apple "),
},
expected: "",
},
{
document: &Document{
Text: "あいうえお かきくけこ さしすせそ",
cursorPosition: 8,
},
expected: "かき",
},
{
document: &Document{
Text: "Добрый день Добрый день",
cursorPosition: 9,
},
expected: "де",
},
}
for i, p := range pattern {
if p.sep == "" {
ac := p.document.GetWordBeforeCursor()
if ac != p.expected {
t.Errorf("[%d] Should be %#v, got %#v", i, p.expected, ac)
}
ac = p.document.GetWordBeforeCursorUntilSeparator("")
if ac != p.expected {
t.Errorf("[%d] Should be %#v, got %#v", i, p.expected, ac)
}
} else {
ac := p.document.GetWordBeforeCursorUntilSeparator(p.sep)
if ac != p.expected {
t.Errorf("[%d] Should be %#v, got %#v", i, p.expected, ac)
}
}
}
}
func TestDocument_GetWordBeforeCursorWithSpace(t *testing.T) {
pattern := []struct {
document *Document
expected string
sep string
}{
{
document: &Document{
Text: "apple bana ",
cursorPosition: len("apple bana "),
},
expected: "bana ",
},
{
document: &Document{
Text: "apply -f /path/to/file/",
cursorPosition: len("apply -f /path/to/file/"),
},
expected: "file/",
sep: " /",
},
{
document: &Document{
Text: "apple ",
cursorPosition: len("apple "),
},
expected: "apple ",
},
{
document: &Document{
Text: "path/",
cursorPosition: len("path/"),
},
expected: "path/",
sep: " /",
},
{
document: &Document{
Text: "あいうえお かきくけこ ",
cursorPosition: 12,
},
expected: "かきくけこ ",
},
{
document: &Document{
Text: "Добрый день ",
cursorPosition: 12,
},
expected: "день ",
},
}
for _, p := range pattern {
if p.sep == "" {
ac := p.document.GetWordBeforeCursorWithSpace()
if ac != p.expected {
t.Errorf("Should be %#v, got %#v", p.expected, ac)
}
ac = p.document.GetWordBeforeCursorUntilSeparatorIgnoreNextToCursor("")
if ac != p.expected {
t.Errorf("Should be %#v, got %#v", p.expected, ac)
}
} else {
ac := p.document.GetWordBeforeCursorUntilSeparatorIgnoreNextToCursor(p.sep)
if ac != p.expected {
t.Errorf("Should be %#v, got %#v", p.expected, ac)
}
}
}
}
func TestDocument_FindStartOfPreviousWord(t *testing.T) {
pattern := []struct {
document *Document
expected int
sep string
}{
{
document: &Document{
Text: "apple bana",
cursorPosition: len("apple bana"),
},
expected: len("apple "),
},
{
document: &Document{
Text: "apply -f ./file/foo.json",
cursorPosition: len("apply -f ./file/foo.json"),
},
expected: len("apply -f ./file/"),
sep: " /",
},
{
document: &Document{
Text: "apple ",
cursorPosition: len("apple "),
},
expected: len("apple "),
},
{
document: &Document{
Text: "apply -f ./file/foo.json",
cursorPosition: len("apply -f ./"),
},
expected: len("apply -f ./"),
sep: " /",
},
{
document: &Document{
Text: "あいうえお かきくけこ さしすせそ",
cursorPosition: 8, // between 'き' and 'く'
},
expected: len("あいうえお "), // this function returns index byte in string
},
{
document: &Document{
Text: "Добрый день Добрый день",
cursorPosition: 9,
},
expected: len("Добрый "), // this function returns index byte in string
},
}
for _, p := range pattern {
if p.sep == "" {
ac := p.document.FindStartOfPreviousWord()
if ac != p.expected {
t.Errorf("Should be %#v, got %#v", p.expected, ac)
}
ac = p.document.FindStartOfPreviousWordUntilSeparator("")
if ac != p.expected {
t.Errorf("Should be %#v, got %#v", p.expected, ac)
}
} else {
ac := p.document.FindStartOfPreviousWordUntilSeparator(p.sep)
if ac != p.expected {
t.Errorf("Should be %#v, got %#v", p.expected, ac)
}
}
}
}
func TestDocument_FindStartOfPreviousWordWithSpace(t *testing.T) {
pattern := []struct {
document *Document
expected int
sep string
}{
{
document: &Document{
Text: "apple bana ",
cursorPosition: len("apple bana "),
},
expected: len("apple "),
},
{
document: &Document{
Text: "apply -f /file/foo/",
cursorPosition: len("apply -f /file/foo/"),
},
expected: len("apply -f /file/"),
sep: " /",
},
{
document: &Document{
Text: "apple ",
cursorPosition: len("apple "),
},
expected: len(""),
},
{
document: &Document{
Text: "file/",
cursorPosition: len("file/"),
},
expected: len(""),
sep: " /",
},
{
document: &Document{
Text: "あいうえお かきくけこ ",
cursorPosition: 12, // cursor points to last
},
expected: len("あいうえお "), // this function returns index byte in string
},
{
document: &Document{
Text: "Добрый день ",
cursorPosition: 12,
},
expected: len("Добрый "), // this function returns index byte in string
},
}
for _, p := range pattern {
if p.sep == "" {
ac := p.document.FindStartOfPreviousWordWithSpace()
if ac != p.expected {
t.Errorf("Should be %#v, got %#v", p.expected, ac)
}
ac = p.document.FindStartOfPreviousWordUntilSeparatorIgnoreNextToCursor("")
if ac != p.expected {
t.Errorf("Should be %#v, got %#v", p.expected, ac)
}
} else {
ac := p.document.FindStartOfPreviousWordUntilSeparatorIgnoreNextToCursor(p.sep)
if ac != p.expected {
t.Errorf("Should be %#v, got %#v", p.expected, ac)
}
}
}
}
func TestDocument_GetWordAfterCursor(t *testing.T) {
pattern := []struct {
document *Document
expected string
sep string
}{
{
document: &Document{
Text: "apple bana",
cursorPosition: len("apple bana"),
},
expected: "",
},
{
document: &Document{
Text: "apply -f ./file/foo.json",
cursorPosition: len("apply -f ./fi"),
},
expected: "le",
sep: " /",
},
{
document: &Document{
Text: "apple bana",
cursorPosition: len("apple "),
},
expected: "bana",
},
{
document: &Document{
Text: "apple bana",
cursorPosition: len("apple"),
},
expected: "",
},
{
document: &Document{
Text: "apply -f ./file/foo.json",
cursorPosition: len("apply -f ."),
},
expected: "",
sep: " /",
},
{
document: &Document{
Text: "apple bana",
cursorPosition: len("ap"),
},
expected: "ple",
},
{
document: &Document{
Text: "あいうえお かきくけこ さしすせそ",
cursorPosition: 8,
},
expected: "くけこ",
},
{
document: &Document{
Text: "Добрый день Добрый день",
cursorPosition: 9,
},
expected: "нь",
},
}
for k, p := range pattern {
if p.sep == "" {
ac := p.document.GetWordAfterCursor()
if ac != p.expected {
t.Errorf("[%d] Should be %#v, got %#v", k, p.expected, ac)
}
ac = p.document.GetWordAfterCursorUntilSeparator("")
if ac != p.expected {
t.Errorf("[%d] Should be %#v, got %#v", k, p.expected, ac)
}
} else {
ac := p.document.GetWordAfterCursorUntilSeparator(p.sep)
if ac != p.expected {
t.Errorf("[%d] Should be %#v, got %#v", k, p.expected, ac)
}
}
}
}
func TestDocument_GetWordAfterCursorWithSpace(t *testing.T) {
pattern := []struct {
document *Document
expected string
sep string
}{
{
document: &Document{
Text: "apple bana",
cursorPosition: len("apple bana"),
},
expected: "",
},
{
document: &Document{
Text: "apple bana",
cursorPosition: len("apple "),
},
expected: "bana",
},
{
document: &Document{
Text: "/path/to",
cursorPosition: len("/path/"),
},
expected: "to",
sep: " /",
},
{
document: &Document{
Text: "/path/to/file",
cursorPosition: len("/path/"),
},
expected: "to",
sep: " /",
},
{
document: &Document{
Text: "apple bana",
cursorPosition: len("apple"),
},
expected: " bana",
},
{
document: &Document{
Text: "path/to",
cursorPosition: len("path"),
},
expected: "/to",
sep: " /",
},
{
document: &Document{
Text: "apple bana",
cursorPosition: len("ap"),
},
expected: "ple",
},
{
document: &Document{
Text: "あいうえお かきくけこ さしすせそ",
cursorPosition: 5,
},
expected: " かきくけこ",
},
{
document: &Document{
Text: "Добрый день Добрый день",
cursorPosition: 6,
},
expected: " день",
},
}
for k, p := range pattern {
if p.sep == "" {
ac := p.document.GetWordAfterCursorWithSpace()
if ac != p.expected {
t.Errorf("[%d] Should be %#v, got %#v", k, p.expected, ac)
}
ac = p.document.GetWordAfterCursorUntilSeparatorIgnoreNextToCursor("")
if ac != p.expected {
t.Errorf("[%d] Should be %#v, got %#v", k, p.expected, ac)
}
} else {
ac := p.document.GetWordAfterCursorUntilSeparatorIgnoreNextToCursor(p.sep)
if ac != p.expected {
t.Errorf("[%d] Should be %#v, got %#v", k, p.expected, ac)
}
}
}
}
func TestDocument_FindEndOfCurrentWord(t *testing.T) {
pattern := []struct {
document *Document
expected int
sep string
}{
{
document: &Document{
Text: "apple bana",
cursorPosition: len("apple bana"),
},
expected: len(""),
},
{
document: &Document{
Text: "apple bana",
cursorPosition: len("apple "),
},
expected: len("bana"),
},
{
document: &Document{
Text: "apply -f ./file/foo.json",
cursorPosition: len("apply -f ./"),
},
expected: len("file"),
sep: " /",
},
{
document: &Document{
Text: "apple bana",
cursorPosition: len("apple"),
},
expected: len(""),
},
{
document: &Document{
Text: "apply -f ./file/foo.json",
cursorPosition: len("apply -f ."),
},
expected: len(""),
sep: " /",
},
{
document: &Document{
Text: "apple bana",
cursorPosition: len("ap"),
},
expected: len("ple"),
},
{
// りん(cursor)ご ばなな
document: &Document{
Text: "りんご ばなな",
cursorPosition: 2,
},
expected: len("ご"),
},
{
document: &Document{
Text: "りんご ばなな",
cursorPosition: 3,
},
expected: 0,
},
{
// Доб(cursor)рый день
document: &Document{
Text: "Добрый день",
cursorPosition: 3,
},
expected: len("рый"),
},
}
for k, p := range pattern {
if p.sep == "" {
ac := p.document.FindEndOfCurrentWord()
if ac != p.expected {
t.Errorf("[%d] Should be %#v, got %#v", k, p.expected, ac)
}
ac = p.document.FindEndOfCurrentWordUntilSeparator("")
if ac != p.expected {
t.Errorf("[%d] Should be %#v, got %#v", k, p.expected, ac)
}
} else {
ac := p.document.FindEndOfCurrentWordUntilSeparator(p.sep)
if ac != p.expected {
t.Errorf("[%d] Should be %#v, got %#v", k, p.expected, ac)
}
}
}
}
func TestDocument_FindEndOfCurrentWordWithSpace(t *testing.T) {
pattern := []struct {
document *Document
expected int
sep string
}{
{
document: &Document{
Text: "apple bana",
cursorPosition: len("apple bana"),
},
expected: len(""),
},
{
document: &Document{
Text: "apple bana",
cursorPosition: len("apple "),
},
expected: len("bana"),
},
{
document: &Document{
Text: "apply -f /file/foo.json",
cursorPosition: len("apply -f /"),
},
expected: len("file"),
sep: " /",
},
{
document: &Document{
Text: "apple bana",
cursorPosition: len("apple"),
},
expected: len(" bana"),
},
{
document: &Document{
Text: "apply -f /path/to",
cursorPosition: len("apply -f /path"),
},
expected: len("/to"),
sep: " /",
},
{
document: &Document{
Text: "apple bana",
cursorPosition: len("ap"),
},
expected: len("ple"),
},
{
document: &Document{
Text: "あいうえお かきくけこ",
cursorPosition: 6,
},
expected: len("かきくけこ"),
},
{
document: &Document{
Text: "あいうえお かきくけこ",
cursorPosition: 5,
},
expected: len(" かきくけこ"),
},
{
document: &Document{
Text: "Добрый день",
cursorPosition: 6,
},
expected: len(" день"),
},
}
for k, p := range pattern {
if p.sep == "" {
ac := p.document.FindEndOfCurrentWordWithSpace()
if ac != p.expected {
t.Errorf("[%d] Should be %#v, got %#v", k, p.expected, ac)
}
ac = p.document.FindEndOfCurrentWordUntilSeparatorIgnoreNextToCursor("")
if ac != p.expected {
t.Errorf("[%d] Should be %#v, got %#v", k, p.expected, ac)
}
} else {
ac := p.document.FindEndOfCurrentWordUntilSeparatorIgnoreNextToCursor(p.sep)
if ac != p.expected {
t.Errorf("[%d] Should be %#v, got %#v", k, p.expected, ac)
}
}
}
}
func TestDocument_CurrentLineBeforeCursor(t *testing.T) {
d := &Document{
Text: "line 1\nline 2\nline 3\nline 4\n",
cursorPosition: len("line 1\n" + "lin"),
}
ac := d.CurrentLineBeforeCursor()
ex := "lin"
if ac != ex {
t.Errorf("Should be %#v, got %#v", ex, ac)
}
}
func TestDocument_CurrentLineAfterCursor(t *testing.T) {
d := &Document{
Text: "line 1\nline 2\nline 3\nline 4\n",
cursorPosition: len("line 1\n" + "lin"),
}
ac := d.CurrentLineAfterCursor()
ex := "e 2"
if ac != ex {
t.Errorf("Should be %#v, got %#v", ex, ac)
}
}
func TestDocument_CurrentLine(t *testing.T) {
d := &Document{
Text: "line 1\nline 2\nline 3\nline 4\n",
cursorPosition: len("line 1\n" + "lin"),
}
ac := d.CurrentLine()
ex := "line 2"
if ac != ex {
t.Errorf("Should be %#v, got %#v", ex, ac)
}
}
func TestDocument_CursorPositionRowAndCol(t *testing.T) {
var cursorPositionTests = []struct {
document *Document
expectedRow int
expectedCol int
}{
{
document: &Document{Text: "line 1\nline 2\nline 3\n", cursorPosition: len("line 1\n" + "lin")},
expectedRow: 1,
expectedCol: 3,
},
{
document: &Document{Text: "", cursorPosition: 0},
expectedRow: 0,
expectedCol: 0,
},
}
for _, test := range cursorPositionTests {
ac := test.document.CursorPositionRow()
if ac != test.expectedRow {
t.Errorf("Should be %#v, got %#v", test.expectedRow, ac)
}
ac = test.document.CursorPositionCol()
if ac != test.expectedCol {
t.Errorf("Should be %#v, got %#v", test.expectedCol, ac)
}
}
}
func TestDocument_GetCursorLeftPosition(t *testing.T) {
d := &Document{
Text: "line 1\nline 2\nline 3\nline 4\n",
cursorPosition: len("line 1\n" + "line 2\n" + "lin"),
}
ac := d.GetCursorLeftPosition(2)
ex := -2
if ac != ex {
t.Errorf("Should be %#v, got %#v", ex, ac)
}
ac = d.GetCursorLeftPosition(10)
ex = -3
if ac != ex {
t.Errorf("Should be %#v, got %#v", ex, ac)
}
}
func TestDocument_GetCursorUpPosition(t *testing.T) {
d := &Document{
Text: "line 1\nline 2\nline 3\nline 4\n",
cursorPosition: len("line 1\n" + "line 2\n" + "lin"),
}
ac := d.GetCursorUpPosition(2, -1)
ex := len("lin") - len("line 1\n"+"line 2\n"+"lin")
if ac != ex {
t.Errorf("Should be %#v, got %#v", ex, ac)
}
ac = d.GetCursorUpPosition(100, -1)
ex = len("lin") - len("line 1\n"+"line 2\n"+"lin")
if ac != ex {
t.Errorf("Should be %#v, got %#v", ex, ac)
}
}
func TestDocument_GetCursorDownPosition(t *testing.T) {
d := &Document{
Text: "line 1\nline 2\nline 3\nline 4\n",
cursorPosition: len("lin"),
}
ac := d.GetCursorDownPosition(2, -1)
ex := len("line 1\n"+"line 2\n"+"lin") - len("lin")
if ac != ex {
t.Errorf("Should be %#v, got %#v", ex, ac)
}
ac = d.GetCursorDownPosition(100, -1)
ex = len("line 1\n"+"line 2\n"+"line 3\n"+"line 4\n") - len("lin")
if ac != ex {
t.Errorf("Should be %#v, got %#v", ex, ac)
}
}
func TestDocument_GetCursorRightPosition(t *testing.T) {
d := &Document{
Text: "line 1\nline 2\nline 3\nline 4\n",
cursorPosition: len("line 1\n" + "line 2\n" + "lin"),
}
ac := d.GetCursorRightPosition(2)
ex := 2
if ac != ex {
t.Errorf("Should be %#v, got %#v", ex, ac)
}
ac = d.GetCursorRightPosition(10)
ex = 3
if ac != ex {
t.Errorf("Should be %#v, got %#v", ex, ac)
}
}
func TestDocument_Lines(t *testing.T) {
d := &Document{
Text: "line 1\nline 2\nline 3\nline 4\n",
cursorPosition: len("line 1\n" + "lin"),
}
ac := d.Lines()
ex := []string{"line 1", "line 2", "line 3", "line 4", ""}
if !reflect.DeepEqual(ac, ex) {
t.Errorf("Should be %#v, got %#v", ex, ac)
}
}
func TestDocument_LineCount(t *testing.T) {
d := &Document{
Text: "line 1\nline 2\nline 3\nline 4\n",
cursorPosition: len("line 1\n" + "lin"),
}
ac := d.LineCount()
ex := 5
if ac != ex {
t.Errorf("Should be %#v, got %#v", ex, ac)
}
}
func TestDocument_TranslateIndexToPosition(t *testing.T) {
d := &Document{
Text: "line 1\nline 2\nline 3\nline 4\n",
cursorPosition: len("line 1\n" + "lin"),
}
row, col := d.TranslateIndexToPosition(len("line 1\nline 2\nlin"))
if row != 2 {
t.Errorf("Should be %#v, got %#v", 2, row)
}
if col != 3 {
t.Errorf("Should be %#v, got %#v", 3, col)
}
row, col = d.TranslateIndexToPosition(0)
if row != 0 {
t.Errorf("Should be %#v, got %#v", 0, row)
}
if col != 0 {
t.Errorf("Should be %#v, got %#v", 0, col)
}
}
func TestDocument_TranslateRowColToIndex(t *testing.T) {
d := &Document{
Text: "line 1\nline 2\nline 3\nline 4\n",
cursorPosition: len("line 1\n" + "lin"),
}
ac := d.TranslateRowColToIndex(2, 3)
ex := len("line 1\nline 2\nlin")
if ac != ex {
t.Errorf("Should be %#v, got %#v", ex, ac)
}
ac = d.TranslateRowColToIndex(0, 0)
ex = 0
if ac != ex {
t.Errorf("Should be %#v, got %#v", ex, ac)
}
}
func TestDocument_OnLastLine(t *testing.T) {
d := &Document{
Text: "line 1\nline 2\nline 3",
cursorPosition: len("line 1\nline"),
}
ac := d.OnLastLine()
if ac {
t.Errorf("Should be %#v, got %#v", false, ac)
}
d.cursorPosition = len("line 1\nline 2\nline")
ac = d.OnLastLine()
if !ac {
t.Errorf("Should be %#v, got %#v", true, ac)
}
}
func TestDocument_GetEndOfLinePosition(t *testing.T) {
d := &Document{
Text: "line 1\nline 2\nline 3",
cursorPosition: len("line 1\nli"),
}
ac := d.GetEndOfLinePosition()
ex := len("ne 2")
if ac != ex {
t.Errorf("Should be %#v, got %#v", ex, ac)
}
}
go-prompt-0.2.5/emacs.go 0000664 0000000 0000000 00000005163 13731402157 0015071 0 ustar 00root root 0000000 0000000 package prompt
import "github.com/c-bata/go-prompt/internal/debug"
/*
========
PROGRESS
========
Moving the cursor
-----------------
* [x] Ctrl + a Go to the beginning of the line (Home)
* [x] Ctrl + e Go to the End of the line (End)
* [x] Ctrl + p Previous command (Up arrow)
* [x] Ctrl + n Next command (Down arrow)
* [x] Ctrl + f Forward one character
* [x] Ctrl + b Backward one character
* [x] Ctrl + xx Toggle between the start of line and current cursor position
Editing
-------
* [x] Ctrl + L Clear the Screen, similar to the clear command
* [x] Ctrl + d Delete character under the cursor
* [x] Ctrl + h Delete character before the cursor (Backspace)
* [x] Ctrl + w Cut the Word before the cursor to the clipboard.
* [x] Ctrl + k Cut the Line after the cursor to the clipboard.
* [x] Ctrl + u Cut/delete the Line before the cursor to the clipboard.
* [ ] Ctrl + t Swap the last two characters before the cursor (typo).
* [ ] Esc + t Swap the last two words before the cursor.
* [ ] ctrl + y Paste the last thing to be cut (yank)
* [ ] ctrl + _ Undo
*/
var emacsKeyBindings = []KeyBind{
// Go to the End of the line
{
Key: ControlE,
Fn: func(buf *Buffer) {
x := []rune(buf.Document().TextAfterCursor())
buf.CursorRight(len(x))
},
},
// Go to the beginning of the line
{
Key: ControlA,
Fn: func(buf *Buffer) {
x := []rune(buf.Document().TextBeforeCursor())
buf.CursorLeft(len(x))
},
},
// Cut the Line after the cursor
{
Key: ControlK,
Fn: func(buf *Buffer) {
x := []rune(buf.Document().TextAfterCursor())
buf.Delete(len(x))
},
},
// Cut/delete the Line before the cursor
{
Key: ControlU,
Fn: func(buf *Buffer) {
x := []rune(buf.Document().TextBeforeCursor())
buf.DeleteBeforeCursor(len(x))
},
},
// Delete character under the cursor
{
Key: ControlD,
Fn: func(buf *Buffer) {
if buf.Text() != "" {
buf.Delete(1)
}
},
},
// Backspace
{
Key: ControlH,
Fn: func(buf *Buffer) {
buf.DeleteBeforeCursor(1)
},
},
// Right allow: Forward one character
{
Key: ControlF,
Fn: func(buf *Buffer) {
buf.CursorRight(1)
},
},
// Left allow: Backward one character
{
Key: ControlB,
Fn: func(buf *Buffer) {
buf.CursorLeft(1)
},
},
// Cut the Word before the cursor.
{
Key: ControlW,
Fn: func(buf *Buffer) {
buf.DeleteBeforeCursor(len([]rune(buf.Document().GetWordBeforeCursorWithSpace())))
},
},
// Clear the Screen, similar to the clear command
{
Key: ControlL,
Fn: func(buf *Buffer) {
consoleWriter.EraseScreen()
consoleWriter.CursorGoTo(0, 0)
debug.AssertNoError(consoleWriter.Flush())
},
},
}
go-prompt-0.2.5/emacs_test.go 0000664 0000000 0000000 00000001345 13731402157 0016126 0 ustar 00root root 0000000 0000000 package prompt
import "testing"
func TestEmacsKeyBindings(t *testing.T) {
buf := NewBuffer()
buf.InsertText("abcde", false, true)
if buf.cursorPosition != len("abcde") {
t.Errorf("Want %d, but got %d", len("abcde"), buf.cursorPosition)
}
// Go to the beginning of the line
applyEmacsKeyBind(buf, ControlA)
if buf.cursorPosition != 0 {
t.Errorf("Want %d, but got %d", 0, buf.cursorPosition)
}
// Go to the end of the line
applyEmacsKeyBind(buf, ControlE)
if buf.cursorPosition != len("abcde") {
t.Errorf("Want %d, but got %d", len("abcde"), buf.cursorPosition)
}
}
func applyEmacsKeyBind(buf *Buffer, key Key) {
for i := range emacsKeyBindings {
kb := emacsKeyBindings[i]
if kb.Key == key {
kb.Fn(buf)
}
}
}
go-prompt-0.2.5/filter.go 0000664 0000000 0000000 00000003725 13731402157 0015270 0 ustar 00root root 0000000 0000000 package prompt
import "strings"
// Filter is the type to filter the prompt.Suggestion array.
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 {
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 {
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 suggestions
}
if ignoreCase {
sub = strings.ToUpper(sub)
}
ret := make([]Suggest, 0, len(suggestions))
for i := range suggestions {
c := suggestions[i].Text
if ignoreCase {
c = strings.ToUpper(c)
}
if function(c, sub) {
ret = append(ret, suggestions[i])
}
}
return ret
}
go-prompt-0.2.5/filter_test.go 0000664 0000000 0000000 00000006413 13731402157 0016324 0 ustar 00root root 0000000 0000000 package prompt
import (
"reflect"
"testing"
)
func TestFilter(t *testing.T) {
var scenarioTable = []struct {
scenario string
filter Filter
list []Suggest
substr string
ignoreCase bool
expected []Suggest
}{
{
scenario: "Contains don't ignore case",
filter: FilterContains,
list: []Suggest{
{Text: "abcde"},
{Text: "fghij"},
{Text: "ABCDE"},
},
substr: "cd",
ignoreCase: false,
expected: []Suggest{
{Text: "abcde"},
},
},
{
scenario: "Contains ignore case",
filter: FilterContains,
list: []Suggest{
{Text: "abcde"},
{Text: "fghij"},
{Text: "ABCDE"},
},
substr: "cd",
ignoreCase: true,
expected: []Suggest{
{Text: "abcde"},
{Text: "ABCDE"},
},
},
{
scenario: "HasPrefix don't ignore case",
filter: FilterHasPrefix,
list: []Suggest{
{Text: "abcde"},
{Text: "fghij"},
{Text: "ABCDE"},
},
substr: "abc",
ignoreCase: false,
expected: []Suggest{
{Text: "abcde"},
},
},
{
scenario: "HasPrefix ignore case",
filter: FilterHasPrefix,
list: []Suggest{
{Text: "abcde"},
{Text: "fabcj"},
{Text: "ABCDE"},
},
substr: "abc",
ignoreCase: true,
expected: []Suggest{
{Text: "abcde"},
{Text: "ABCDE"},
},
},
{
scenario: "HasSuffix don't ignore case",
filter: FilterHasSuffix,
list: []Suggest{
{Text: "abcde"},
{Text: "fcdej"},
{Text: "ABCDE"},
},
substr: "cde",
ignoreCase: false,
expected: []Suggest{
{Text: "abcde"},
},
},
{
scenario: "HasSuffix ignore case",
filter: FilterHasSuffix,
list: []Suggest{
{Text: "abcde"},
{Text: "fcdej"},
{Text: "ABCDE"},
},
substr: "cde",
ignoreCase: true,
expected: []Suggest{
{Text: "abcde"},
{Text: "ABCDE"},
},
},
{
scenario: "Fuzzy don't ignore case",
filter: FilterFuzzy,
list: []Suggest{
{Text: "abcde"},
{Text: "fcdej"},
{Text: "ABCDE"},
},
substr: "ae",
ignoreCase: false,
expected: []Suggest{
{Text: "abcde"},
},
},
{
scenario: "Fuzzy ignore case",
filter: FilterFuzzy,
list: []Suggest{
{Text: "abcde"},
{Text: "fcdej"},
{Text: "ABCDE"},
},
substr: "ae",
ignoreCase: true,
expected: []Suggest{
{Text: "abcde"},
{Text: "ABCDE"},
},
},
}
for _, s := range scenarioTable {
if actual := s.filter(s.list, s.substr, s.ignoreCase); !reflect.DeepEqual(actual, s.expected) {
t.Errorf("%s: Should be %#v, but got %#v", s.scenario, s.expected, actual)
}
}
}
func TestFuzzyMatch(t *testing.T) {
tests := []struct {
s string
sub string
match bool
}{
{"dog house", "dog", true},
{"dog house", "", true},
{"", "", true},
{"this is much longer", "hhg", true},
{"this is much longer", "hhhg", false},
{"long", "longer", false},
{"can we do unicode 文字 with this 今日", "文字今日", true},
{"can we do unicode 文字 with this 今日", "d文字tt今日", true},
{"can we do unicode 文字 with this 今日", "d文字ttt今日", false},
}
for _, test := range tests {
if fuzzyMatch(test.s, test.sub) != test.match {
t.Errorf("fuzzymatch, %s in %s: expected %v, got %v", test.sub, test.s, test.match, !test.match)
}
}
}
go-prompt-0.2.5/go.mod 0000664 0000000 0000000 00000000366 13731402157 0014560 0 ustar 00root root 0000000 0000000 module github.com/c-bata/go-prompt
go 1.14
require (
github.com/mattn/go-colorable v0.1.7
github.com/mattn/go-runewidth v0.0.9
github.com/mattn/go-tty v0.0.3
github.com/pkg/term v1.1.0
golang.org/x/sys v0.0.0-20200918174421-af09f7315aff
)
go-prompt-0.2.5/go.sum 0000664 0000000 0000000 00000007502 13731402157 0014604 0 ustar 00root root 0000000 0000000 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-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw=
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
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-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
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-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
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/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI=
github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0=
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=
github.com/pkg/term v1.1.0 h1:xIAAdCMh3QIAy+5FrE8Ad8XoDhEU4ufwbaSozViP9kk=
github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20 h1:4X356008q5SA3YXu8PiRap39KFmy4Lf6sGlceJKZQsU=
golang.org/x/sys v0.0.0-20200917073148-efd3b9a0ff20/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200918174421-af09f7315aff h1:1CPUrky56AcgSpxz/KfgzQWzfG09u5YOL8MvPYBlrL8=
golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
go-prompt-0.2.5/history.go 0000664 0000000 0000000 00000002720 13731402157 0015476 0 ustar 00root root 0000000 0000000 package prompt
// History stores the texts that are entered.
type History struct {
histories []string
tmp []string
selected int
}
// Add to add text in history.
func (h *History) Add(input string) {
h.histories = append(h.histories, input)
h.Clear()
}
// Clear to clear the history.
func (h *History) Clear() {
h.tmp = make([]string, len(h.histories))
for i := range h.histories {
h.tmp[i] = h.histories[i]
}
h.tmp = append(h.tmp, "")
h.selected = len(h.tmp) - 1
}
// Older saves a buffer of current line and get a buffer of previous line by up-arrow.
// The changes of line buffers are stored until new history is created.
func (h *History) Older(buf *Buffer) (new *Buffer, changed bool) {
if len(h.tmp) == 1 || h.selected == 0 {
return buf, false
}
h.tmp[h.selected] = buf.Text()
h.selected--
new = NewBuffer()
new.InsertText(h.tmp[h.selected], false, true)
return new, true
}
// Newer saves a buffer of current line and get a buffer of next line by up-arrow.
// The changes of line buffers are stored until new history is created.
func (h *History) Newer(buf *Buffer) (new *Buffer, changed bool) {
if h.selected >= len(h.tmp)-1 {
return buf, false
}
h.tmp[h.selected] = buf.Text()
h.selected++
new = NewBuffer()
new.InsertText(h.tmp[h.selected], false, true)
return new, true
}
// NewHistory returns new history object.
func NewHistory() *History {
return &History{
histories: []string{},
tmp: []string{""},
selected: 0,
}
}
go-prompt-0.2.5/history_test.go 0000664 0000000 0000000 00000002447 13731402157 0016543 0 ustar 00root root 0000000 0000000 package prompt
import (
"reflect"
"testing"
)
func TestHistoryClear(t *testing.T) {
h := NewHistory()
h.Add("foo")
h.Clear()
expected := &History{
histories: []string{"foo"},
tmp: []string{"foo", ""},
selected: 1,
}
if !reflect.DeepEqual(expected, h) {
t.Errorf("Should be %#v, but got %#v", expected, h)
}
}
func TestHistoryAdd(t *testing.T) {
h := NewHistory()
h.Add("echo 1")
expected := &History{
histories: []string{"echo 1"},
tmp: []string{"echo 1", ""},
selected: 1,
}
if !reflect.DeepEqual(h, expected) {
t.Errorf("Should be %v, but got %v", expected, h)
}
}
func TestHistoryOlder(t *testing.T) {
h := NewHistory()
h.Add("echo 1")
// Prepare buffer
buf := NewBuffer()
buf.InsertText("echo 2", false, true)
// [1 time] Call Older function
buf1, changed := h.Older(buf)
if !changed {
t.Error("Should be changed history but not changed.")
}
if buf1.Text() != "echo 1" {
t.Errorf("Should be %#v, but got %#v", "echo 1", buf1.Text())
}
// [2 times] Call Older function
buf = NewBuffer()
buf.InsertText("echo 1", false, true)
buf2, changed := h.Older(buf)
if changed {
t.Error("Should be not changed history but changed.")
}
if !reflect.DeepEqual("echo 1", buf2.Text()) {
t.Errorf("Should be %#v, but got %#v", "echo 1", buf2.Text())
}
}
go-prompt-0.2.5/input.go 0000664 0000000 0000000 00000016716 13731402157 0015146 0 ustar 00root root 0000000 0000000 package prompt
import "bytes"
// WinSize represents the width and height of terminal.
type WinSize struct {
Row uint16
Col uint16
}
// 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 NotDefined
}
// ASCIISequences holds mappings of the key and byte array.
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
}
go-prompt-0.2.5/input_posix.go 0000664 0000000 0000000 00000003160 13731402157 0016355 0 ustar 00root root 0000000 0000000 // +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,
}
}
go-prompt-0.2.5/input_test.go 0000664 0000000 0000000 00000001025 13731402157 0016170 0 ustar 00root root 0000000 0000000 package prompt
import (
"testing"
)
func TestPosixParserGetKey(t *testing.T) {
scenarioTable := []struct {
name string
input []byte
expected Key
}{
{
name: "escape",
input: []byte{0x1b},
expected: Escape,
},
{
name: "undefined",
input: []byte{'a'},
expected: NotDefined,
},
}
for _, s := range scenarioTable {
t.Run(s.name, func(t *testing.T) {
key := GetKey(s.input)
if key != s.expected {
t.Errorf("Should be %s, but got %s", key, s.expected)
}
})
}
}
go-prompt-0.2.5/input_windows.go 0000664 0000000 0000000 00000003217 13731402157 0016710 0 ustar 00root root 0000000 0000000 // +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{}
}
go-prompt-0.2.5/internal/ 0000775 0000000 0000000 00000000000 13731402157 0015261 5 ustar 00root root 0000000 0000000 go-prompt-0.2.5/internal/bisect/ 0000775 0000000 0000000 00000000000 13731402157 0016532 5 ustar 00root root 0000000 0000000 go-prompt-0.2.5/internal/bisect/bisect.go 0000664 0000000 0000000 00000000507 13731402157 0020334 0 ustar 00root root 0000000 0000000 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
})
}
go-prompt-0.2.5/internal/bisect/bisect_test.go 0000664 0000000 0000000 00000002063 13731402157 0021372 0 ustar 00root root 0000000 0000000 package bisect_test
import (
"fmt"
"math/rand"
"testing"
"github.com/c-bata/go-prompt/internal/bisect"
)
func Example() {
in := []int{1, 2, 3, 3, 3, 6, 7}
fmt.Println("Insertion position for 0 in the slice is", bisect.Right(in, 0))
fmt.Println("Insertion position for 4 in the slice is", bisect.Right(in, 4))
// Output:
// Insertion position for 0 in the slice is 0
// Insertion position for 4 in the slice is 5
}
func TestBisectRight(t *testing.T) {
// Thanks!! https://play.golang.org/p/y9NRj_XVIW
in := []int{1, 2, 3, 3, 3, 6, 7}
r := bisect.Right(in, 0)
if r != 0 {
t.Errorf("number 0 should inserted at 0 position, but got %d", r)
}
r = bisect.Right(in, 4)
if r != 5 {
t.Errorf("number 4 should inserted at 5 position, but got %d", r)
}
}
func BenchmarkRight(b *testing.B) {
rand.Seed(0)
for _, l := range []int{10, 1e2, 1e3, 1e4} {
x := rand.Perm(l)
insertion := rand.Int()
b.Run(fmt.Sprintf("arrayLength=%d", l), func(b *testing.B) {
b.ResetTimer()
for n := 0; n < b.N; n++ {
bisect.Right(x, insertion)
}
})
}
}
go-prompt-0.2.5/internal/debug/ 0000775 0000000 0000000 00000000000 13731402157 0016347 5 ustar 00root root 0000000 0000000 go-prompt-0.2.5/internal/debug/assert.go 0000664 0000000 0000000 00000001457 13731402157 0020206 0 ustar 00root root 0000000 0000000 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())
}
go-prompt-0.2.5/internal/debug/log.go 0000664 0000000 0000000 00000001553 13731402157 0017463 0 ustar 00root root 0000000 0000000 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)
}
go-prompt-0.2.5/internal/strings/ 0000775 0000000 0000000 00000000000 13731402157 0016752 5 ustar 00root root 0000000 0000000 go-prompt-0.2.5/internal/strings/strings.go 0000664 0000000 0000000 00000003742 13731402157 0021000 0 ustar 00root root 0000000 0000000 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
}
go-prompt-0.2.5/internal/strings/strings_test.go 0000664 0000000 0000000 00000001725 13731402157 0022036 0 ustar 00root root 0000000 0000000 package strings_test
import (
"fmt"
"github.com/c-bata/go-prompt/internal/strings"
)
func ExampleIndexNotByte() {
fmt.Println(strings.IndexNotByte("golang", 'g'))
fmt.Println(strings.IndexNotByte("golang", 'x'))
fmt.Println(strings.IndexNotByte("gggggg", 'g'))
// Output:
// 1
// 0
// -1
}
func ExampleLastIndexNotByte() {
fmt.Println(strings.LastIndexNotByte("golang", 'g'))
fmt.Println(strings.LastIndexNotByte("golang", 'x'))
fmt.Println(strings.LastIndexNotByte("gggggg", 'g'))
// Output:
// 4
// 5
// -1
}
func ExampleIndexNotAny() {
fmt.Println(strings.IndexNotAny("golang", "glo"))
fmt.Println(strings.IndexNotAny("golang", "gl"))
fmt.Println(strings.IndexNotAny("golang", "golang"))
// Output:
// 3
// 1
// -1
}
func ExampleLastIndexNotAny() {
fmt.Println(strings.LastIndexNotAny("golang", "agn"))
fmt.Println(strings.LastIndexNotAny("golang", "an"))
fmt.Println(strings.LastIndexNotAny("golang", "golang"))
// Output:
// 2
// 5
// -1
}
go-prompt-0.2.5/internal/term/ 0000775 0000000 0000000 00000000000 13731402157 0016230 5 ustar 00root root 0000000 0000000 go-prompt-0.2.5/internal/term/raw.go 0000664 0000000 0000000 00000001354 13731402157 0017353 0 ustar 00root root 0000000 0000000 // +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))
}
go-prompt-0.2.5/internal/term/term.go 0000664 0000000 0000000 00000001136 13731402157 0017527 0 ustar 00root root 0000000 0000000 // +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)
}
go-prompt-0.2.5/key.go 0000664 0000000 0000000 00000003322 13731402157 0014564 0 ustar 00root root 0000000 0000000 // 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.
type ASCIICode struct {
Key Key
ASCIICode []byte
}
const (
Escape Key = iota
ControlA
ControlB
ControlC
ControlD
ControlE
ControlF
ControlG
ControlH
ControlI
ControlJ
ControlK
ControlL
ControlM
ControlN
ControlO
ControlP
ControlQ
ControlR
ControlS
ControlT
ControlU
ControlV
ControlW
ControlX
ControlY
ControlZ
ControlSpace
ControlBackslash
ControlSquareClose
ControlCircumflex
ControlUnderscore
ControlLeft
ControlRight
ControlUp
ControlDown
Up
Down
Right
Left
ShiftLeft
ShiftUp
ShiftDown
ShiftRight
Home
End
Delete
ShiftDelete
ControlDelete
PageUp
PageDown
BackTab
Insert
Backspace
// Aliases.
Tab
Enter
// Actually Enter equals ControlM, not ControlJ,
// However, in prompt_toolkit, we made the mistake of translating
// \r into \n during the input, so everyone is now handling the
// enter key by binding ControlJ.
// From now on, it's better to bind `ASCII_SEQUENCES.Enter` everywhere,
// because that's future compatible, and will still work when we
// stop replacing \r by \n.
F1
F2
F3
F4
F5
F6
F7
F8
F9
F10
F11
F12
F13
F14
F15
F16
F17
F18
F19
F20
F21
F22
F23
F24
// Matches any key.
Any
// Special
CPRResponse
Vt100MouseEvent
WindowsMouseEvent
BracketedPaste
// Key which is ignored. (The key binding for this key should not do anything.)
Ignore
// Key is not defined
NotDefined
)
go-prompt-0.2.5/key_bind.go 0000664 0000000 0000000 00000002125 13731402157 0015560 0 ustar 00root root 0000000 0000000 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 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: GoLineEnd,
},
// Go to the beginning of the line
{
Key: Home,
Fn: GoLineBeginning,
},
// Delete character under the cursor
{
Key: Delete,
Fn: DeleteChar,
},
// Backspace
{
Key: Backspace,
Fn: DeleteBeforeChar,
},
// Right allow: Forward one character
{
Key: Right,
Fn: GoRightChar,
},
// Left allow: Backward one character
{
Key: Left,
Fn: GoLeftChar,
},
}
go-prompt-0.2.5/key_bind_func.go 0000664 0000000 0000000 00000002303 13731402157 0016571 0 ustar 00root root 0000000 0000000 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())
}
go-prompt-0.2.5/key_string.go 0000664 0000000 0000000 00000002517 13731402157 0016157 0 ustar 00root root 0000000 0000000 // Code generated by "stringer -type=Key"; DO NOT EDIT.
package prompt
import "strconv"
const _Key_name = "EscapeControlAControlBControlCControlDControlEControlFControlGControlHControlIControlJControlKControlLControlMControlNControlOControlPControlQControlRControlSControlTControlUControlVControlWControlXControlYControlZControlSpaceControlBackslashControlSquareCloseControlCircumflexControlUnderscoreControlLeftControlRightControlUpControlDownUpDownRightLeftShiftLeftShiftUpShiftDownShiftRightHomeEndDeleteShiftDeleteControlDeletePageUpPageDownBackTabInsertBackspaceTabEnterF1F2F3F4F5F6F7F8F9F10F11F12F13F14F15F16F17F18F19F20F21F22F23F24AnyCPRResponseVt100MouseEventWindowsMouseEventBracketedPasteIgnoreNotDefined"
var _Key_index = [...]uint16{0, 6, 14, 22, 30, 38, 46, 54, 62, 70, 78, 86, 94, 102, 110, 118, 126, 134, 142, 150, 158, 166, 174, 182, 190, 198, 206, 214, 226, 242, 260, 277, 294, 305, 317, 326, 337, 339, 343, 348, 352, 361, 368, 377, 387, 391, 394, 400, 411, 424, 430, 438, 445, 451, 460, 463, 468, 470, 472, 474, 476, 478, 480, 482, 484, 486, 489, 492, 495, 498, 501, 504, 507, 510, 513, 516, 519, 522, 525, 528, 531, 534, 545, 560, 577, 591, 597, 607}
func (i Key) String() string {
if i < 0 || i >= Key(len(_Key_index)-1) {
return "Key(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Key_name[_Key_index[i]:_Key_index[i+1]]
}
go-prompt-0.2.5/option.go 0000664 0000000 0000000 00000021041 13731402157 0015302 0 ustar 00root root 0000000 0000000 package prompt
// Option is the type to replace default parameters.
// prompt.New accepts any number of options (this is functional option pattern).
type Option func(prompt *Prompt) error
// OptionParser to set a custom ConsoleParser object. An argument should implement ConsoleParser interface.
func OptionParser(x ConsoleParser) Option {
return func(p *Prompt) error {
p.in = x
return nil
}
}
// 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
}
}
// OptionTitle to set title displayed at the header bar of terminal.
func OptionTitle(x string) Option {
return func(p *Prompt) error {
p.renderer.title = x
return nil
}
}
// OptionPrefix to set prefix string.
func OptionPrefix(x string) Option {
return func(p *Prompt) error {
p.renderer.prefix = x
return nil
}
}
// 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 {
p.renderer.livePrefixCallback = f
return nil
}
}
// OptionPrefixTextColor change a text color of prefix string
func OptionPrefixTextColor(x Color) Option {
return func(p *Prompt) error {
p.renderer.prefixTextColor = x
return nil
}
}
// OptionPrefixBackgroundColor to change a background color of prefix string
func OptionPrefixBackgroundColor(x Color) Option {
return func(p *Prompt) error {
p.renderer.prefixBGColor = x
return nil
}
}
// 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
return nil
}
}
// 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
return nil
}
}
// OptionPreviewSuggestionTextColor to change a text color which is completed
func OptionPreviewSuggestionTextColor(x Color) Option {
return func(p *Prompt) error {
p.renderer.previewSuggestionTextColor = x
return nil
}
}
// OptionPreviewSuggestionBGColor to change a background color which is completed
func OptionPreviewSuggestionBGColor(x Color) Option {
return func(p *Prompt) error {
p.renderer.previewSuggestionBGColor = x
return nil
}
}
// OptionSuggestionTextColor to change a text color in drop down suggestions.
func OptionSuggestionTextColor(x Color) Option {
return func(p *Prompt) error {
p.renderer.suggestionTextColor = x
return nil
}
}
// OptionSuggestionBGColor change a background color in drop down suggestions.
func OptionSuggestionBGColor(x Color) Option {
return func(p *Prompt) error {
p.renderer.suggestionBGColor = x
return nil
}
}
// 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
return nil
}
}
// 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
return nil
}
}
// 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
return nil
}
}
// 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
return nil
}
}
// 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
return nil
}
}
// 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
return nil
}
}
// OptionScrollbarThumbColor to change a thumb color on scrollbar.
func OptionScrollbarThumbColor(x Color) Option {
return func(p *Prompt) error {
p.renderer.scrollbarThumbColor = x
return nil
}
}
// OptionScrollbarBGColor to change a background color of scrollbar.
func OptionScrollbarBGColor(x Color) Option {
return func(p *Prompt) error {
p.renderer.scrollbarBGColor = x
return nil
}
}
// OptionMaxSuggestion specify the max number of displayed suggestions.
func OptionMaxSuggestion(x uint16) Option {
return func(p *Prompt) error {
p.completion.max = x
return nil
}
}
// OptionHistory to set history expressed by string array.
func OptionHistory(x []string) Option {
return func(p *Prompt) error {
p.history.histories = x
p.history.Clear()
return nil
}
}
// OptionSwitchKeyBindMode set a key bind mode.
func OptionSwitchKeyBindMode(m KeyBindMode) Option {
return func(p *Prompt) error {
p.keyBindMode = m
return nil
}
}
// 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
// OptionAddKeyBind to set a custom key bind.
func OptionAddKeyBind(b ...KeyBind) Option {
return func(p *Prompt) error {
p.keyBindings = append(p.keyBindings, b...)
return nil
}
}
// 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: defaultWriter,
livePrefixCallback: func() (string, bool) { return "", false },
prefixTextColor: Blue,
prefixBGColor: DefaultColor,
inputTextColor: DefaultColor,
inputBGColor: DefaultColor,
previewSuggestionTextColor: Green,
previewSuggestionBGColor: DefaultColor,
suggestionTextColor: White,
suggestionBGColor: Cyan,
selectedSuggestionTextColor: Black,
selectedSuggestionBGColor: Turquoise,
descriptionTextColor: Black,
descriptionBGColor: Turquoise,
selectedDescriptionTextColor: White,
selectedDescriptionBGColor: Cyan,
scrollbarThumbColor: DarkGray,
scrollbarBGColor: Cyan,
},
buf: NewBuffer(),
executor: executor,
history: NewHistory(),
completion: NewCompletionManager(completer, 6),
keyBindMode: EmacsKeyBind, // All the above assume that bash is running in the default Emacs setting
}
for _, opt := range opts {
if err := opt(pt); err != nil {
panic(err)
}
}
return pt
}
go-prompt-0.2.5/output.go 0000664 0000000 0000000 00000010340 13731402157 0015332 0 ustar 00root root 0000000 0000000 package prompt
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
)
// ConsoleWriter is an interface to abstract output layer.
type ConsoleWriter interface {
/* Write */
// WriteRaw to write raw byte array.
WriteRaw(data []byte)
// Write to write safety byte array by removing control sequences.
Write(data []byte)
// WriteStr to write raw string.
WriteRawStr(data string)
// WriteStr to write safety string by removing control sequences.
WriteStr(data string)
// Flush to flush buffer.
Flush() error
/* Erasing */
// EraseScreen erases the screen with the background colour and moves the cursor to home.
EraseScreen()
// EraseUp erases the screen from the current line up to the top of the screen.
EraseUp()
// EraseDown erases the screen from the current line down to the bottom of the screen.
EraseDown()
// EraseStartOfLine erases from the current cursor position to the start of the current line.
EraseStartOfLine()
// EraseEndOfLine erases from the current cursor position to the end of the current line.
EraseEndOfLine()
// EraseLine erases the entire current line.
EraseLine()
/* Cursor */
// ShowCursor stops blinking cursor and show.
ShowCursor()
// HideCursor hides cursor.
HideCursor()
// CursorGoTo sets the cursor position where subsequent text will begin.
CursorGoTo(row, col int)
// CursorUp moves the cursor up by 'n' rows; the default count is 1.
CursorUp(n int)
// CursorDown moves the cursor down by 'n' rows; the default count is 1.
CursorDown(n int)
// CursorForward moves the cursor forward by 'n' columns; the default count is 1.
CursorForward(n int)
// CursorBackward moves the cursor backward by 'n' columns; the default count is 1.
CursorBackward(n int)
// AskForCPR asks for a cursor position report (CPR).
AskForCPR()
// SaveCursor saves current cursor position.
SaveCursor()
// UnSaveCursor restores cursor position after a Save Cursor.
UnSaveCursor()
/* Scrolling */
// ScrollDown scrolls display down one line.
ScrollDown()
// ScrollUp scroll display up one line.
ScrollUp()
/* Title */
// SetTitle sets a title of terminal window.
SetTitle(title string)
// ClearTitle clears a title of terminal window.
ClearTitle()
/* Font */
// SetColor sets text and background colors. and specify whether text is bold.
SetColor(fg, bg Color, bold bool)
}
go-prompt-0.2.5/output_posix.go 0000664 0000000 0000000 00000003011 13731402157 0016551 0 ustar 00root root 0000000 0000000 // +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,
}
}
go-prompt-0.2.5/output_vt100.go 0000664 0000000 0000000 00000016435 13731402157 0016277 0 ustar 00root root 0000000 0000000 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. [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'},
}
go-prompt-0.2.5/output_vt100_test.go 0000664 0000000 0000000 00000002505 13731402157 0017327 0 ustar 00root root 0000000 0000000 package prompt
import (
"bytes"
"testing"
)
func TestVT100WriterWrite(t *testing.T) {
scenarioTable := []struct {
input []byte
expected []byte
}{
{
input: []byte{0x1b},
expected: []byte{'?'},
},
{
input: []byte{'a'},
expected: []byte{'a'},
},
}
for _, s := range scenarioTable {
pw := &VT100Writer{}
pw.Write(s.input)
if !bytes.Equal(pw.buffer, s.expected) {
t.Errorf("Should be %+#v, but got %+#v", pw.buffer, s.expected)
}
}
}
func TestVT100WriterWriteStr(t *testing.T) {
scenarioTable := []struct {
input string
expected []byte
}{
{
input: "\x1b",
expected: []byte{'?'},
},
{
input: "a",
expected: []byte{'a'},
},
}
for _, s := range scenarioTable {
pw := &VT100Writer{}
pw.WriteStr(s.input)
if !bytes.Equal(pw.buffer, s.expected) {
t.Errorf("Should be %+#v, but got %+#v", pw.buffer, s.expected)
}
}
}
func TestVT100WriterWriteRawStr(t *testing.T) {
scenarioTable := []struct {
input string
expected []byte
}{
{
input: "\x1b",
expected: []byte{0x1b},
},
{
input: "a",
expected: []byte{'a'},
},
}
for _, s := range scenarioTable {
pw := &VT100Writer{}
pw.WriteRawStr(s.input)
if !bytes.Equal(pw.buffer, s.expected) {
t.Errorf("Should be %+#v, but got %+#v", pw.buffer, s.expected)
}
}
}
go-prompt-0.2.5/output_windows.go 0000664 0000000 0000000 00000002052 13731402157 0017105 0 ustar 00root root 0000000 0000000 // +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(),
}
}
go-prompt-0.2.5/prompt.go 0000664 0000000 0000000 00000015626 13731402157 0015327 0 ustar 00root root 0000000 0000000 package prompt
import (
"bytes"
"os"
"time"
"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 (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
ASCIICodeBindings []ASCIICodeBind
keyBindMode KeyBindMode
completionOnDown bool
exitChecker ExitChecker
skipTearDown bool
}
// Exec is the struct contains user input context.
type Exec struct {
input string
}
// Run starts prompt.
func (p *Prompt) Run() {
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)
stopReadBufCh := make(chan struct{})
go p.readBuffer(bufCh, stopReadBufCh)
exitCh := make(chan int)
winSizeCh := make(chan *WinSize)
stopHandleSignalCh := make(chan struct{})
go p.handleSignals(exitCh, winSizeCh, stopHandleSignalCh)
for {
select {
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
stopReadBufCh <- struct{}{}
stopHandleSignalCh <- struct{}{}
// Unset raw mode
// Reset to Blocking mode because returned EAGAIN when still set non-blocking mode.
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
debug.AssertNoError(p.in.Setup())
go p.readBuffer(bufCh, stopReadBufCh)
go p.handleSignals(exitCh, winSizeCh, stopHandleSignalCh)
} else {
p.completion.Update(*p.buf.Document())
p.renderer.Render(p.buf, p.completion)
}
case w := <-winSizeCh:
p.renderer.UpdateWinSize(w)
p.renderer.Render(p.buf, p.completion)
case code := <-exitCh:
p.renderer.BreakLine(p.buf)
p.tearDown()
os.Exit(code)
default:
time.Sleep(10 * time.Millisecond)
}
}
}
func (p *Prompt) feed(b []byte) (shouldExit bool, exec *Exec) {
key := GetKey(b)
p.buf.lastKeyStroke = key
// completion
completing := p.completion.Completing()
p.handleCompletionKeyBinding(key, completing)
switch key {
case Enter, ControlJ, ControlM:
p.renderer.BreakLine(p.buf)
exec = &Exec{input: p.buf.Text()}
p.buf = NewBuffer()
if exec.input != "" {
p.history.Add(exec.input)
}
case ControlC:
p.renderer.BreakLine(p.buf)
p.buf = NewBuffer()
p.history.Clear()
case Up, ControlP:
if !completing { // Don't use p.completion.Completing() because it takes double operation when switch to selected=-1.
if newBuf, changed := p.history.Older(p.buf); changed {
p.buf = newBuf
}
}
case Down, ControlN:
if !completing { // Don't use p.completion.Completing() because it takes double operation when switch to selected=-1.
if newBuf, changed := p.history.Newer(p.buf); changed {
p.buf = newBuf
}
return
}
case ControlD:
if p.buf.Text() == "" {
shouldExit = true
return
}
case NotDefined:
if p.handleASCIICodeBinding(b) {
return
}
p.buf.InsertText(string(b), false, true)
}
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 {
kb.Fn(p.buf)
}
}
if p.keyBindMode == EmacsKeyBind {
for i := range emacsKeyBindings {
kb := emacsKeyBindings[i]
if kb.Key == key {
kb.Fn(p.buf)
}
}
}
// Custom key bindings
for i := range p.keyBindings {
kb := p.keyBindings[i]
if kb.Key == key {
kb.Fn(p.buf)
}
}
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 {
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{})
go p.readBuffer(bufCh, stopReadBufCh)
for {
select {
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
stopReadBufCh <- struct{}{}
return e.input
} else {
p.completion.Update(*p.buf.Document())
p.renderer.Render(p.buf, p.completion)
}
default:
time.Sleep(10 * time.Millisecond)
}
}
}
func (p *Prompt) readBuffer(bufCh chan []byte, stopCh chan struct{}) {
debug.Log("start reading buffer")
for {
select {
case <-stopCh:
debug.Log("stop reading buffer")
return
default:
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() {
debug.AssertNoError(p.in.Setup())
p.renderer.Setup()
p.renderer.UpdateWinSize(p.in.GetWinSize())
}
func (p *Prompt) tearDown() {
if !p.skipTearDown {
debug.AssertNoError(p.in.TearDown())
}
p.renderer.TearDown()
}
go-prompt-0.2.5/render.go 0000664 0000000 0000000 00000017524 13731402157 0015264 0 ustar 00root root 0000000 0000000 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
previousCursor int
// colors,
prefixTextColor Color
prefixBGColor Color
inputTextColor Color
inputBGColor Color
previewSuggestionTextColor Color
previewSuggestionBGColor Color
suggestionTextColor Color
suggestionBGColor Color
selectedSuggestionTextColor Color
selectedSuggestionBGColor Color
descriptionTextColor Color
descriptionBGColor Color
selectedDescriptionTextColor Color
selectedDescriptionBGColor Color
scrollbarThumbColor Color
scrollbarBGColor Color
}
// Setup to initialize console output.
func (r *Render) Setup() {
if r.title != "" {
r.out.SetTitle(r.title)
debug.AssertNoError(r.out.Flush())
}
}
// getCurrentPrefix to get current prefix.
// If live-prefix is enabled, return live-prefix.
func (r *Render) getCurrentPrefix() string {
if prefix, ok := r.livePrefixCallback(); ok {
return prefix
}
return r.prefix
}
func (r *Render) renderPrefix() {
r.out.SetColor(r.prefixTextColor, r.prefixBGColor, false)
r.out.WriteStr(r.getCurrentPrefix())
r.out.SetColor(DefaultColor, DefaultColor, false)
}
// TearDown to clear title and erasing.
func (r *Render) TearDown() {
r.out.ClearTitle()
r.out.EraseDown()
debug.AssertNoError(r.out.Flush())
}
func (r *Render) prepareArea(lines int) {
for i := 0; i < lines; i++ {
r.out.ScrollDown()
}
for i := 0; i < lines; i++ {
r.out.ScrollUp()
}
}
// UpdateWinSize called when window size is changed.
func (r *Render) UpdateWinSize(ws *WinSize) {
r.row = ws.Row
r.col = ws.Col
}
func (r *Render) renderWindowTooSmall() {
r.out.CursorGoTo(0, 0)
r.out.EraseScreen()
r.out.SetColor(DarkRed, White, false)
r.out.WriteStr("Your console window is too small...")
}
func (r *Render) renderCompletion(buf *Buffer, completions *CompletionManager) {
suggestions := completions.GetSuggestions()
if len(completions.GetSuggestions()) == 0 {
return
}
prefix := r.getCurrentPrefix()
formatted, width := formatSuggestions(
suggestions,
int(r.col)-runewidth.StringWidth(prefix)-1, // -1 means a width of scrollbar
)
// +1 means a width of scrollbar.
width++
windowHeight := len(formatted)
if windowHeight > int(completions.max) {
windowHeight = int(completions.max)
}
formatted = formatted[completions.verticalScroll : completions.verticalScroll+windowHeight]
r.prepareArea(windowHeight)
cursor := runewidth.StringWidth(prefix) + runewidth.StringWidth(buf.Document().TextBeforeCursor())
x, _ := r.toPos(cursor)
if x+width >= int(r.col) {
cursor = r.backward(cursor, x+width-int(r.col))
}
contentHeight := len(completions.tmp)
fractionVisible := float64(windowHeight) / float64(contentHeight)
fractionAbove := float64(completions.verticalScroll) / float64(contentHeight)
scrollbarHeight := int(clamp(float64(windowHeight), 1, float64(windowHeight)*fractionVisible))
scrollbarTop := int(float64(windowHeight) * fractionAbove)
isScrollThumb := func(row int) bool {
return scrollbarTop <= row && row <= scrollbarTop+scrollbarHeight
}
selected := completions.selected - completions.verticalScroll
r.out.SetColor(White, Cyan, false)
for i := 0; i < windowHeight; i++ {
r.out.CursorDown(1)
if i == selected {
r.out.SetColor(r.selectedSuggestionTextColor, r.selectedSuggestionBGColor, true)
} else {
r.out.SetColor(r.suggestionTextColor, r.suggestionBGColor, false)
}
r.out.WriteStr(formatted[i].Text)
if i == selected {
r.out.SetColor(r.selectedDescriptionTextColor, r.selectedDescriptionBGColor, false)
} else {
r.out.SetColor(r.descriptionTextColor, r.descriptionBGColor, false)
}
r.out.WriteStr(formatted[i].Description)
if isScrollThumb(i) {
r.out.SetColor(DefaultColor, r.scrollbarThumbColor, false)
} else {
r.out.SetColor(DefaultColor, r.scrollbarBGColor, false)
}
r.out.WriteStr(" ")
r.out.SetColor(DefaultColor, DefaultColor, false)
r.lineWrap(cursor + width)
r.backward(cursor+width, width)
}
if x+width >= int(r.col) {
r.out.CursorForward(x + width - int(r.col))
}
r.out.CursorUp(windowHeight)
r.out.SetColor(DefaultColor, DefaultColor, false)
}
// 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 := runewidth.StringWidth(prefix) + runewidth.StringWidth(line)
// prepare area
_, y := r.toPos(cursor)
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)
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, 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)
rest := buffer.Document().TextAfterCursor()
r.out.WriteStr(rest)
cursor += runewidth.StringWidth(rest)
r.lineWrap(cursor)
cursor = r.backward(cursor, runewidth.StringWidth(rest))
}
r.previousCursor = cursor
}
// BreakLine to break line.
func (r *Render) BreakLine(buffer *Buffer) {
// Erasing and Render
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)
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.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 {
fromX, fromY := r.toPos(from)
toX, toY := r.toPos(to)
r.out.CursorUp(fromY - toY)
r.out.CursorBackward(fromX - toX)
return to
}
// toPos returns the relative position from the beginning of the string.
func (r *Render) toPos(cursor int) (x, y int) {
col := int(r.col)
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:
return high
case x < low:
return low
default:
return x
}
}
go-prompt-0.2.5/render_test.go 0000664 0000000 0000000 00000005512 13731402157 0016315 0 ustar 00root root 0000000 0000000 // +build !windows
package prompt
import (
"reflect"
"syscall"
"testing"
)
func TestFormatCompletion(t *testing.T) {
scenarioTable := []struct {
scenario string
completions []Suggest
prefix string
suffix string
expected []Suggest
maxWidth int
expectedWidth int
}{
{
scenario: "",
completions: []Suggest{
{Text: "select"},
{Text: "from"},
{Text: "insert"},
{Text: "where"},
},
prefix: " ",
suffix: " ",
expected: []Suggest{
{Text: " select "},
{Text: " from "},
{Text: " insert "},
{Text: " where "},
},
maxWidth: 20,
expectedWidth: 8,
},
{
scenario: "",
completions: []Suggest{
{Text: "select", Description: "select description"},
{Text: "from", Description: "from description"},
{Text: "insert", Description: "insert description"},
{Text: "where", Description: "where description"},
},
prefix: " ",
suffix: " ",
expected: []Suggest{
{Text: " select ", Description: " select description "},
{Text: " from ", Description: " from description "},
{Text: " insert ", Description: " insert description "},
{Text: " where ", Description: " where description "},
},
maxWidth: 40,
expectedWidth: 28,
},
}
for _, s := range scenarioTable {
ac, width := formatSuggestions(s.completions, s.maxWidth)
if !reflect.DeepEqual(ac, s.expected) {
t.Errorf("Should be %#v, but got %#v", s.expected, ac)
}
if width != s.expectedWidth {
t.Errorf("Should be %#v, but got %#v", s.expectedWidth, width)
}
}
}
func TestBreakLineCallback(t *testing.T) {
var i int
r := &Render{
prefix: "> ",
out: &PosixWriter{
fd: syscall.Stdin, // "write" to stdin just so we don't mess with the output of the tests
},
livePrefixCallback: func() (string, bool) { return "", false },
prefixTextColor: Blue,
prefixBGColor: DefaultColor,
inputTextColor: DefaultColor,
inputBGColor: DefaultColor,
previewSuggestionTextColor: Green,
previewSuggestionBGColor: DefaultColor,
suggestionTextColor: White,
suggestionBGColor: Cyan,
selectedSuggestionTextColor: Black,
selectedSuggestionBGColor: Turquoise,
descriptionTextColor: Black,
descriptionBGColor: Turquoise,
selectedDescriptionTextColor: White,
selectedDescriptionBGColor: Cyan,
scrollbarThumbColor: DarkGray,
scrollbarBGColor: Cyan,
col: 1,
}
b := NewBuffer()
r.BreakLine(b)
if i != 0 {
t.Errorf("i should initially be 0, before applying a break line callback")
}
r.breakLineCallback = func(doc *Document) {
i++
}
r.BreakLine(b)
r.BreakLine(b)
r.BreakLine(b)
if i != 3 {
t.Errorf("BreakLine callback not called, i should be 3")
}
}
go-prompt-0.2.5/shortcut.go 0000664 0000000 0000000 00000002140 13731402157 0015644 0 ustar 00root root 0000000 0000000 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)
}
}
go-prompt-0.2.5/signal_posix.go 0000664 0000000 0000000 00000001561 13731402157 0016476 0 ustar 00root root 0000000 0000000 // +build !windows
package prompt
import (
"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{}) {
in := p.in
sigCh := make(chan os.Signal, 1)
signal.Notify(
sigCh,
syscall.SIGINT,
syscall.SIGTERM,
syscall.SIGQUIT,
syscall.SIGWINCH,
)
for {
select {
case <-stop:
debug.Log("stop handleSignals")
return
case s := <-sigCh:
switch s {
case syscall.SIGINT: // kill -SIGINT XXXX or Ctrl+c
debug.Log("Catch SIGINT")
exitCh <- 0
case syscall.SIGTERM: // kill -SIGTERM XXXX
debug.Log("Catch SIGTERM")
exitCh <- 1
case syscall.SIGQUIT: // kill -SIGQUIT XXXX
debug.Log("Catch SIGQUIT")
exitCh <- 0
case syscall.SIGWINCH:
debug.Log("Catch SIGWINCH")
winSizeCh <- in.GetWinSize()
}
}
}
}
go-prompt-0.2.5/signal_windows.go 0000664 0000000 0000000 00000001365 13731402157 0017030 0 ustar 00root root 0000000 0000000 // +build windows
package prompt
import (
"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{}) {
sigCh := make(chan os.Signal, 1)
signal.Notify(
sigCh,
syscall.SIGINT,
syscall.SIGTERM,
syscall.SIGQUIT,
)
for {
select {
case <-stop:
debug.Log("stop handleSignals")
return
case s := <-sigCh:
switch s {
case syscall.SIGINT: // kill -SIGINT XXXX or Ctrl+c
debug.Log("Catch SIGINT")
exitCh <- 0
case syscall.SIGTERM: // kill -SIGTERM XXXX
debug.Log("Catch SIGTERM")
exitCh <- 1
case syscall.SIGQUIT: // kill -SIGQUIT XXXX
debug.Log("Catch SIGQUIT")
exitCh <- 0
}
}
}
}