Merge 6e05092055
into 255a3ec82c
commit
d0b3885bed
@ -0,0 +1,26 @@
|
||||
# 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/
|
@ -0,0 +1,79 @@
|
||||
# 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
|
@ -0,0 +1,21 @@
|
||||
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.
|
@ -0,0 +1,46 @@
|
||||
.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}'
|
@ -0,0 +1,125 @@
|
||||
# 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)
|
||||
* [segmentio/topicctl: Tool for declarative management of Kafka topics](https://github.com/segmentio/topicctl)
|
||||
* [chriswalz/bit: Bit is a modern Git CLI](https://github.com/chriswalz/bit)
|
||||
* (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
|
||||
---------------------|---------------------------------------------------------
|
||||
<kbd>Ctrl + A</kbd> | Go to the beginning of the line (Home)
|
||||
<kbd>Ctrl + E</kbd> | Go to the end of the line (End)
|
||||
<kbd>Ctrl + P</kbd> | Previous command (Up arrow)
|
||||
<kbd>Ctrl + N</kbd> | Next command (Down arrow)
|
||||
<kbd>Ctrl + F</kbd> | Forward one character
|
||||
<kbd>Ctrl + B</kbd> | Backward one character
|
||||
<kbd>Ctrl + D</kbd> | Delete character under the cursor
|
||||
<kbd>Ctrl + H</kbd> | Delete character before the cursor (Backspace)
|
||||
<kbd>Ctrl + W</kbd> | Cut the word before the cursor to the clipboard
|
||||
<kbd>Ctrl + K</kbd> | Cut the line after the cursor to the clipboard
|
||||
<kbd>Ctrl + U</kbd> | Cut the line before the cursor to the clipboard
|
||||
<kbd>Ctrl + L</kbd> | Clear the screen
|
||||
|
||||
### History
|
||||
|
||||
You can use <kbd>Up arrow</kbd> and <kbd>Down arrow</kbd> 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.
|
||||
|
@ -0,0 +1,191 @@
|
||||
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
|
||||
}
|
@ -0,0 +1,190 @@
|
||||
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,
|
||||
}
|
||||
}
|
@ -0,0 +1,441 @@
|
||||
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
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
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())
|
||||
},
|
||||
},
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
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
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
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,
|
||||
}
|
||||
}
|
@ -0,0 +1,169 @@
|
||||
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
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
// +build !windows
|
||||
|
||||
package prompt
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/c-bata/go-prompt/internal/term"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const maxReadBytes = 1024
|
||||
|
||||
// PosixParser is a ConsoleParser implementation for POSIX environment.
|
||||
type PosixParser struct {
|
||||
fd int
|
||||
origTermios syscall.Termios
|
||||
}
|
||||
|
||||
// Setup should be called before starting input
|
||||
func (t *PosixParser) Setup() error {
|
||||
// Set NonBlocking mode because if syscall.Read block this goroutine, it cannot receive data from stopCh.
|
||||
if err := syscall.SetNonblock(t.fd, true); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := term.SetRaw(t.fd); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TearDown should be called after stopping input
|
||||
func (t *PosixParser) TearDown() error {
|
||||
if err := syscall.SetNonblock(t.fd, false); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := term.Restore(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read returns byte array.
|
||||
func (t *PosixParser) Read() ([]byte, error) {
|
||||
buf := make([]byte, maxReadBytes)
|
||||
n, err := syscall.Read(t.fd, buf)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
return buf[:n], nil
|
||||
}
|
||||
|
||||
// GetWinSize returns WinSize object to represent width and height of terminal.
|
||||
func (t *PosixParser) GetWinSize() *WinSize {
|
||||
ws, err := unix.IoctlGetWinsize(t.fd, unix.TIOCGWINSZ)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &WinSize{
|
||||
Row: ws.Row,
|
||||
Col: ws.Col,
|
||||
}
|
||||
}
|
||||
|
||||
var _ ConsoleParser = &PosixParser{}
|
||||
|
||||
// NewStandardInputParser returns ConsoleParser object to read from stdin.
|
||||
func NewStandardInputParser() *PosixParser {
|
||||
in, err := syscall.Open("/dev/tty", syscall.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &PosixParser{
|
||||
fd: in,
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
// +build windows
|
||||
|
||||
package prompt
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
"unicode/utf8"
|
||||
"unsafe"
|
||||
|
||||
tty "github.com/mattn/go-tty"
|
||||
)
|
||||
|
||||
const maxReadBytes = 1024
|
||||
|
||||
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
|
||||
var procGetNumberOfConsoleInputEvents = kernel32.NewProc("GetNumberOfConsoleInputEvents")
|
||||
|
||||
// WindowsParser is a ConsoleParser implementation for Win32 console.
|
||||
type WindowsParser struct {
|
||||
tty *tty.TTY
|
||||
}
|
||||
|
||||
// Setup should be called before starting input
|
||||
func (p *WindowsParser) Setup() error {
|
||||
t, err := tty.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.tty = t
|
||||
return nil
|
||||
}
|
||||
|
||||
// TearDown should be called after stopping input
|
||||
func (p *WindowsParser) TearDown() error {
|
||||
return p.tty.Close()
|
||||
}
|
||||
|
||||
// Read returns byte array.
|
||||
func (p *WindowsParser) Read() ([]byte, error) {
|
||||
var ev uint32
|
||||
r0, _, err := procGetNumberOfConsoleInputEvents.Call(p.tty.Input().Fd(), uintptr(unsafe.Pointer(&ev)))
|
||||
if r0 == 0 {
|
||||
return nil, err
|
||||
}
|
||||
if ev == 0 {
|
||||
return nil, errors.New("EAGAIN")
|
||||
}
|
||||
|
||||
r, err := p.tty.ReadRune()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := make([]byte, maxReadBytes)
|
||||
n := utf8.EncodeRune(buf[:], r)
|
||||
for p.tty.Buffered() && n < maxReadBytes {
|
||||
r, err := p.tty.ReadRune()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
n += utf8.EncodeRune(buf[n:], r)
|
||||
}
|
||||
return buf[:n], nil
|
||||
}
|
||||
|
||||
// GetWinSize returns WinSize object to represent width and height of terminal.
|
||||
func (p *WindowsParser) GetWinSize() *WinSize {
|
||||
w, h, err := p.tty.Size()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &WinSize{
|
||||
Row: uint16(h),
|
||||
Col: uint16(w),
|
||||
}
|
||||
}
|
||||
|
||||
// NewStandardInputParser returns ConsoleParser object to read from stdin.
|
||||
func NewStandardInputParser() *WindowsParser {
|
||||
return &WindowsParser{}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package bisect
|
||||
|
||||
import "sort"
|
||||
|
||||
// Right to locate the insertion point for v in a to maintain sorted order.
|
||||
func Right(a []int, v int) int {
|
||||
return bisectRightRange(a, v, 0, len(a))
|
||||
}
|
||||
|
||||
func bisectRightRange(a []int, v int, lo, hi int) int {
|
||||
s := a[lo:hi]
|
||||
return sort.Search(len(s), func(i int) bool {
|
||||
return s[i] > v
|
||||
})
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package debug
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
envAssertPanic = "GO_PROMPT_ENABLE_ASSERT"
|
||||
)
|
||||
|
||||
var (
|
||||
enableAssert bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
if e := os.Getenv(envAssertPanic); e == "true" || e == "1" {
|
||||
enableAssert = true
|
||||
}
|
||||
}
|
||||
|
||||
// Assert ensures expected condition.
|
||||
func Assert(cond bool, msg interface{}) {
|
||||
if cond {
|
||||
return
|
||||
}
|
||||
if enableAssert {
|
||||
panic(msg)
|
||||
}
|
||||
writeWithSync(2, "[ASSERT] "+toString(msg))
|
||||
}
|
||||
|
||||
func toString(v interface{}) string {
|
||||
switch a := v.(type) {
|
||||
case func() string:
|
||||
return a()
|
||||
case string:
|
||||
return a
|
||||
case fmt.Stringer:
|
||||
return a.String()
|
||||
default:
|
||||
return fmt.Sprintf("unexpected type, %t", v)
|
||||
}
|
||||
}
|
||||
|
||||
// AssertNoError ensures err is nil.
|
||||
func AssertNoError(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if enableAssert {
|
||||
panic(err)
|
||||
}
|
||||
writeWithSync(2, "[ASSERT] "+err.Error())
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package debug
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
envEnableLog = "GO_PROMPT_ENABLE_LOG"
|
||||
logFileName = "go-prompt.log"
|
||||
)
|
||||
|
||||
var (
|
||||
logfile *os.File
|
||||
logger *log.Logger
|
||||
)
|
||||
|
||||
func init() {
|
||||
if e := os.Getenv(envEnableLog); e == "true" || e == "1" {
|
||||
var err error
|
||||
logfile, err = os.OpenFile(logFileName, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
|
||||
if err == nil {
|
||||
logger = log.New(logfile, "", log.Llongfile)
|
||||
return
|
||||
}
|
||||
}
|
||||
logger = log.New(ioutil.Discard, "", log.Llongfile)
|
||||
}
|
||||
|
||||
// Teardown to close logfile
|
||||
func Teardown() {
|
||||
if logfile == nil {
|
||||
return
|
||||
}
|
||||
_ = logfile.Close()
|
||||
}
|
||||
|
||||
func writeWithSync(calldepth int, msg string) {
|
||||
calldepth++
|
||||
if logfile == nil {
|
||||
return
|
||||
}
|
||||
_ = logger.Output(calldepth, msg)
|
||||
_ = logfile.Sync() // immediately write msg
|
||||
}
|
||||
|
||||
// Log to output message
|
||||
func Log(msg string) {
|
||||
calldepth := 2
|
||||
writeWithSync(calldepth, msg)
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
package strings
|
||||
|
||||
import "unicode/utf8"
|
||||
|
||||
// IndexNotByte is similar with strings.IndexByte but showing the opposite behavior.
|
||||
func IndexNotByte(s string, c byte) int {
|
||||
n := len(s)
|
||||
for i := 0; i < n; i++ {
|
||||
if s[i] != c {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// LastIndexNotByte is similar with strings.LastIndexByte but showing the opposite behavior.
|
||||
func LastIndexNotByte(s string, c byte) int {
|
||||
for i := len(s) - 1; i >= 0; i-- {
|
||||
if s[i] != c {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
type asciiSet [8]uint32
|
||||
|
||||
func (as *asciiSet) notContains(c byte) bool {
|
||||
return (as[c>>5] & (1 << uint(c&31))) == 0
|
||||
}
|
||||
|
||||
func makeASCIISet(chars string) (as asciiSet, ok bool) {
|
||||
for i := 0; i < len(chars); i++ {
|
||||
c := chars[i]
|
||||
if c >= utf8.RuneSelf {
|
||||
return as, false
|
||||
}
|
||||
as[c>>5] |= 1 << uint(c&31)
|
||||
}
|
||||
return as, true
|
||||
}
|
||||
|
||||
// IndexNotAny is similar with strings.IndexAny but showing the opposite behavior.
|
||||
func IndexNotAny(s, chars string) int {
|
||||
if len(chars) > 0 {
|
||||
if len(s) > 8 {
|
||||
if as, isASCII := makeASCIISet(chars); isASCII {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if as.notContains(s[i]) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
LabelFirstLoop:
|
||||
for i, c := range s {
|
||||
for j, m := range chars {
|
||||
if c != m && j == len(chars)-1 {
|
||||
return i
|
||||
} else if c != m {
|
||||
continue
|
||||
} else {
|
||||
continue LabelFirstLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// LastIndexNotAny is similar with strings.LastIndexAny but showing the opposite behavior.
|
||||
func LastIndexNotAny(s, chars string) int {
|
||||
if len(chars) > 0 {
|
||||
if len(s) > 8 {
|
||||
if as, isASCII := makeASCIISet(chars); isASCII {
|
||||
for i := len(s) - 1; i >= 0; i-- {
|
||||
if as.notContains(s[i]) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
}
|
||||
LabelFirstLoop:
|
||||
for i := len(s); i > 0; {
|
||||
r, size := utf8.DecodeLastRuneInString(s[:i])
|
||||
i -= size
|
||||
for j, m := range chars {
|
||||
if r != m && j == len(chars)-1 {
|
||||
return i
|
||||
} else if r != m {
|
||||
continue
|
||||
} else {
|
||||
continue LabelFirstLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
// +build !windows
|
||||
|
||||
package term
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/pkg/term/termios"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// SetRaw put terminal into a raw mode
|
||||
func SetRaw(fd int) error {
|
||||
n, err := getOriginalTermios(fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n.Iflag &^= syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK |
|
||||
syscall.ISTRIP | syscall.INLCR | syscall.IGNCR |
|
||||
syscall.ICRNL | syscall.IXON
|
||||
n.Lflag &^= syscall.ECHO | syscall.ICANON | syscall.IEXTEN | syscall.ISIG | syscall.ECHONL
|
||||
n.Cflag &^= syscall.CSIZE | syscall.PARENB
|
||||
n.Cflag |= syscall.CS8 // Set to 8-bit wide. Typical value for displaying characters.
|
||||
n.Cc[syscall.VMIN] = 1
|
||||
n.Cc[syscall.VTIME] = 0
|
||||
|
||||
return termios.Tcsetattr(uintptr(fd), termios.TCSANOW, (*unix.Termios)(n))
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
// +build !windows
|
||||
|
||||
package term
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/term/termios"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var (
|
||||
saveTermios *unix.Termios
|
||||
saveTermiosFD int
|
||||
saveTermiosOnce sync.Once
|
||||
)
|
||||
|
||||
func getOriginalTermios(fd int) (*unix.Termios, error) {
|
||||
var err error
|
||||
saveTermiosOnce.Do(func() {
|
||||
saveTermiosFD = fd
|
||||
saveTermios, err = termios.Tcgetattr(uintptr(fd))
|
||||
})
|
||||
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)
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
// 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
|
||||
)
|
@ -0,0 +1,59 @@
|
||||
package prompt
|
||||
|
||||
// KeyBindFunc receives buffer and processed it.
|
||||
type KeyBindFunc func(*Buffer)
|
||||
|
||||
// KeyBind represents which key should do what operation.
|
||||
type KeyBind struct {
|
||||
Key Key
|
||||
Fn KeyBindFunc
|
||||
}
|
||||
|
||||
// ASCIICodeBind represents which []byte should do what operation
|
||||
type ASCIICodeBind struct {
|
||||
ASCIICode []byte
|
||||
Fn KeyBindFunc
|
||||
}
|
||||
|
||||
// KeyBindMode to switch a key binding flexibly.
|
||||
type KeyBindMode string
|
||||
|
||||
const (
|
||||
// CommonKeyBind is a mode without any keyboard shortcut
|
||||
CommonKeyBind KeyBindMode = "common"
|
||||
// EmacsKeyBind 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,
|
||||
},
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package prompt
|
||||
|
||||
// GoLineEnd Go to the End of the line
|
||||
func GoLineEnd(buf *Buffer) {
|
||||
x := []rune(buf.Document().TextAfterCursor())
|
||||
buf.CursorRight(len(x))
|
||||
}
|
||||
|
||||
// GoLineBeginning Go to the beginning of the line
|
||||
func GoLineBeginning(buf *Buffer) {
|
||||
x := []rune(buf.Document().TextBeforeCursor())
|
||||
buf.CursorLeft(len(x))
|
||||
}
|
||||
|
||||
// DeleteChar Delete character under the cursor
|
||||
func DeleteChar(buf *Buffer) {
|
||||
buf.Delete(1)
|
||||
}
|
||||
|
||||
// DeleteWord Delete word before the cursor
|
||||
func DeleteWord(buf *Buffer) {
|
||||
buf.DeleteBeforeCursor(len([]rune(buf.Document().TextBeforeCursor())) - buf.Document().FindStartOfPreviousWordWithSpace())
|
||||
}
|
||||
|
||||
// DeleteBeforeChar Go to Backspace
|
||||
func DeleteBeforeChar(buf *Buffer) {
|
||||
buf.DeleteBeforeCursor(1)
|
||||
}
|
||||
|
||||
// GoRightChar Forward one character
|
||||
func GoRightChar(buf *Buffer) {
|
||||
buf.CursorRight(1)
|
||||
}
|
||||
|
||||
// GoLeftChar Backward one character
|
||||
func GoLeftChar(buf *Buffer) {
|
||||
buf.CursorLeft(1)
|
||||
}
|
||||
|
||||
// GoRightWord Forward one word
|
||||
func GoRightWord(buf *Buffer) {
|
||||
buf.CursorRight(buf.Document().FindEndOfCurrentWordWithSpace())
|
||||
}
|
||||
|
||||
// GoLeftWord Backward one word
|
||||
func GoLeftWord(buf *Buffer) {
|
||||
buf.CursorLeft(len([]rune(buf.Document().TextBeforeCursor())) - buf.Document().FindStartOfPreviousWordWithSpace())
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
// 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]]
|
||||
}
|
@ -0,0 +1,310 @@
|
||||
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
|
||||
}
|
@ -0,0 +1,161 @@
|
||||
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)
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
// +build !windows
|
||||
|
||||
package prompt
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const flushMaxRetryCount = 3
|
||||
|
||||
// PosixWriter is a ConsoleWriter implementation for POSIX environment.
|
||||
// To control terminal emulator, this outputs VT100 escape sequences.
|
||||
type PosixWriter struct {
|
||||
VT100Writer
|
||||
fd int
|
||||
}
|
||||
|
||||
// Flush to flush buffer
|
||||
func (w *PosixWriter) Flush() error {
|
||||
l := len(w.buffer)
|
||||
offset := 0
|
||||
retry := 0
|
||||
for {
|
||||
n, err := syscall.Write(w.fd, w.buffer[offset:])
|
||||
if err != nil {
|
||||
if retry < flushMaxRetryCount {
|
||||
retry++
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
offset += n
|
||||
if offset == l {
|
||||
break
|
||||
}
|
||||
}
|
||||
w.buffer = []byte{}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ ConsoleWriter = &PosixWriter{}
|
||||
|
||||
var (
|
||||
// NewStandardOutputWriter returns ConsoleWriter object to write to stdout.
|
||||
// This generates VT100 escape sequences because almost terminal emulators
|
||||
// in POSIX OS built on top of a VT100 specification.
|
||||
// Deprecated: Please use NewStdoutWriter
|
||||
NewStandardOutputWriter = NewStdoutWriter
|
||||
)
|
||||
|
||||
// NewStdoutWriter returns ConsoleWriter object to write to stdout.
|
||||
// This generates VT100 escape sequences because almost terminal emulators
|
||||
// in POSIX OS built on top of a VT100 specification.
|
||||
func NewStdoutWriter() ConsoleWriter {
|
||||
return &PosixWriter{
|
||||
fd: syscall.Stdout,
|
||||
}
|
||||
}
|
||||
|
||||
// NewStderrWriter returns ConsoleWriter object to write to stderr.
|
||||
// This generates VT100 escape sequences because almost terminal emulators
|
||||
// in POSIX OS built on top of a VT100 specification.
|
||||
func NewStderrWriter() ConsoleWriter {
|
||||
return &PosixWriter{
|
||||
fd: syscall.Stderr,
|
||||
}
|
||||
}
|
@ -0,0 +1,318 @@
|
||||
package prompt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// VT100Writer generates VT100 escape sequences.
|
||||
type VT100Writer struct {
|
||||
buffer []byte
|
||||
}
|
||||
|
||||
func (w *VT100Writer) Buffer() []byte {
|
||||
return w.buffer
|
||||
}
|
||||
|
||||
func (w *VT100Writer) SetBuffer(buffer []byte) {
|
||||
w.buffer = buffer
|
||||
}
|
||||
|
||||
// WriteRaw to write raw byte array
|
||||
func (w *VT100Writer) WriteRaw(data []byte) {
|
||||
w.buffer = append(w.buffer, data...)
|
||||
}
|
||||
|
||||
// Write to write safety byte array by removing control sequences.
|
||||
func (w *VT100Writer) Write(data []byte) {
|
||||
w.WriteRaw(bytes.Replace(data, []byte{0x1b}, []byte{'?'}, -1))
|
||||
}
|
||||
|
||||
// WriteRawStr to write raw string
|
||||
func (w *VT100Writer) WriteRawStr(data string) {
|
||||
w.WriteRaw([]byte(data))
|
||||
}
|
||||
|
||||
// WriteStr to write safety string by removing control sequences.
|
||||
func (w *VT100Writer) WriteStr(data string) {
|
||||
w.Write([]byte(data))
|
||||
}
|
||||
|
||||
/* Erase */
|
||||
|
||||
// EraseScreen erases the screen with the background colour and moves the cursor to home.
|
||||
func (w *VT100Writer) EraseScreen() {
|
||||
w.WriteRaw([]byte{0x1b, '[', '2', 'J'})
|
||||
}
|
||||
|
||||
// EraseUp erases the screen from the current line up to the top of the screen.
|
||||
func (w *VT100Writer) EraseUp() {
|
||||
w.WriteRaw([]byte{0x1b, '[', '1', 'J'})
|
||||
}
|
||||
|
||||
// EraseDown erases the screen from the current line down to the bottom of the screen.
|
||||
func (w *VT100Writer) EraseDown() {
|
||||
w.WriteRaw([]byte{0x1b, '[', 'J'})
|
||||
}
|
||||
|
||||
// EraseStartOfLine erases from the current cursor position to the start of the current line.
|
||||
func (w *VT100Writer) EraseStartOfLine() {
|
||||
w.WriteRaw([]byte{0x1b, '[', '1', 'K'})
|
||||
}
|
||||
|
||||
// EraseEndOfLine erases from the current cursor position to the end of the current line.
|
||||
func (w *VT100Writer) EraseEndOfLine() {
|
||||
w.WriteRaw([]byte{0x1b, '[', 'K'})
|
||||
}
|
||||
|
||||
// EraseLine erases the entire current line.
|
||||
func (w *VT100Writer) EraseLine() {
|
||||
w.WriteRaw([]byte{0x1b, '[', '2', 'K'})
|
||||
}
|
||||
|
||||
/* Cursor */
|
||||
|
||||
// ShowCursor stops blinking cursor and show.
|
||||
func (w *VT100Writer) ShowCursor() {
|
||||
w.WriteRaw([]byte{0x1b, '[', '?', '1', '2', 'l', 0x1b, '[', '?', '2', '5', 'h'})
|
||||
}
|
||||
|
||||
// HideCursor hides cursor.
|
||||
func (w *VT100Writer) HideCursor() {
|
||||
w.WriteRaw([]byte{0x1b, '[', '?', '2', '5', 'l'})
|
||||
}
|
||||
|
||||
// CursorGoTo sets the cursor position where subsequent text will begin.
|
||||
func (w *VT100Writer) CursorGoTo(row, col int) {
|
||||
if row == 0 && col == 0 {
|
||||
// If no row/column parameters are provided (ie. <ESC>[H), the cursor will move to the home position.
|
||||
w.WriteRaw([]byte{0x1b, '[', 'H'})
|
||||
return
|
||||
}
|
||||
r := strconv.Itoa(row)
|
||||
c := strconv.Itoa(col)
|
||||
w.WriteRaw([]byte{0x1b, '['})
|
||||
w.WriteRaw([]byte(r))
|
||||
w.WriteRaw([]byte{';'})
|
||||
w.WriteRaw([]byte(c))
|
||||
w.WriteRaw([]byte{'H'})
|
||||
}
|
||||
|
||||
// CursorUp moves the cursor up by 'n' rows; the default count is 1.
|
||||
func (w *VT100Writer) CursorUp(n int) {
|
||||
if n == 0 {
|
||||
return
|
||||
} else if n < 0 {
|
||||
w.CursorDown(-n)
|
||||
return
|
||||
}
|
||||
s := strconv.Itoa(n)
|
||||
w.WriteRaw([]byte{0x1b, '['})
|
||||
w.WriteRaw([]byte(s))
|
||||
w.WriteRaw([]byte{'A'})
|
||||
}
|
||||
|
||||
// CursorDown moves the cursor down by 'n' rows; the default count is 1.
|
||||
func (w *VT100Writer) CursorDown(n int) {
|
||||
if n == 0 {
|
||||
return
|
||||
} else if n < 0 {
|
||||
w.CursorUp(-n)
|
||||
return
|
||||
}
|
||||
s := strconv.Itoa(n)
|
||||
w.WriteRaw([]byte{0x1b, '['})
|
||||
w.WriteRaw([]byte(s))
|
||||
w.WriteRaw([]byte{'B'})
|
||||
}
|
||||
|
||||
// CursorForward moves the cursor forward by 'n' columns; the default count is 1.
|
||||
func (w *VT100Writer) CursorForward(n int) {
|
||||
if n == 0 {
|
||||
return
|
||||
} else if n < 0 {
|
||||
w.CursorBackward(-n)
|
||||
return
|
||||
}
|
||||
s := strconv.Itoa(n)
|
||||
w.WriteRaw([]byte{0x1b, '['})
|
||||
w.WriteRaw([]byte(s))
|
||||
w.WriteRaw([]byte{'C'})
|
||||
}
|
||||
|
||||
// CursorBackward moves the cursor backward by 'n' columns; the default count is 1.
|
||||
func (w *VT100Writer) CursorBackward(n int) {
|
||||
if n == 0 {
|
||||
return
|
||||
} else if n < 0 {
|
||||
w.CursorForward(-n)
|
||||
return
|
||||
}
|
||||
s := strconv.Itoa(n)
|
||||
w.WriteRaw([]byte{0x1b, '['})
|
||||
w.WriteRaw([]byte(s))
|
||||
w.WriteRaw([]byte{'D'})
|
||||
}
|
||||
|
||||
// AskForCPR asks for a cursor position report (CPR).
|
||||
func (w *VT100Writer) AskForCPR() {
|
||||
// CPR: Cursor Position Request.
|
||||
w.WriteRaw([]byte{0x1b, '[', '6', 'n'})
|
||||
}
|
||||
|
||||
// SaveCursor saves current cursor position.
|
||||
func (w *VT100Writer) SaveCursor() {
|
||||
w.WriteRaw([]byte{0x1b, '[', 's'})
|
||||
}
|
||||
|
||||
// UnSaveCursor restores cursor position after a Save Cursor.
|
||||
func (w *VT100Writer) UnSaveCursor() {
|
||||
w.WriteRaw([]byte{0x1b, '[', 'u'})
|
||||
}
|
||||
|
||||
/* Scrolling */
|
||||
|
||||
// ScrollDown scrolls display down one line.
|
||||
func (w *VT100Writer) ScrollDown() {
|
||||
w.WriteRaw([]byte{0x1b, 'D'})
|
||||
}
|
||||
|
||||
// ScrollUp scroll display up one line.
|
||||
func (w *VT100Writer) ScrollUp() {
|
||||
w.WriteRaw([]byte{0x1b, 'M'})
|
||||
}
|
||||
|
||||
/* Title */
|
||||
|
||||
// SetTitle sets a title of terminal window.
|
||||
func (w *VT100Writer) SetTitle(title string) {
|
||||
titleBytes := []byte(title)
|
||||
patterns := []struct {
|
||||
from []byte
|
||||
to []byte
|
||||
}{
|
||||
{
|
||||
from: []byte{0x13},
|
||||
to: []byte{},
|
||||
},
|
||||
{
|
||||
from: []byte{0x07},
|
||||
to: []byte{},
|
||||
},
|
||||
}
|
||||
for i := range patterns {
|
||||
titleBytes = bytes.Replace(titleBytes, patterns[i].from, patterns[i].to, -1)
|
||||
}
|
||||
|
||||
w.WriteRaw([]byte{0x1b, ']', '2', ';'})
|
||||
w.WriteRaw(titleBytes)
|
||||
w.WriteRaw([]byte{0x07})
|
||||
}
|
||||
|
||||
// ClearTitle clears a title of terminal window.
|
||||
func (w *VT100Writer) ClearTitle() {
|
||||
w.WriteRaw([]byte{0x1b, ']', '2', ';', 0x07})
|
||||
}
|
||||
|
||||
/* Font */
|
||||
|
||||
// SetColor sets text and background colors. and specify whether text is bold.
|
||||
func (w *VT100Writer) SetColor(fg, bg Color, bold bool) {
|
||||
if bold {
|
||||
w.SetDisplayAttributes(fg, bg, DisplayBold)
|
||||
} else {
|
||||
// If using `DisplayDefualt`, it will be broken in some environment.
|
||||
// Details are https://github.com/c-bata/go-prompt/pull/85
|
||||
w.SetDisplayAttributes(fg, bg, DisplayReset)
|
||||
}
|
||||
}
|
||||
|
||||
// SetDisplayAttributes to set VT100 display attributes.
|
||||
func (w *VT100Writer) SetDisplayAttributes(fg, bg Color, attrs ...DisplayAttribute) {
|
||||
w.WriteRaw([]byte{0x1b, '['}) // control sequence introducer
|
||||
defer w.WriteRaw([]byte{'m'}) // final character
|
||||
|
||||
var separator byte = ';'
|
||||
for i := range attrs {
|
||||
p, ok := displayAttributeParameters[attrs[i]]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
w.WriteRaw(p)
|
||||
w.WriteRaw([]byte{separator})
|
||||
}
|
||||
|
||||
f, ok := foregroundANSIColors[fg]
|
||||
if !ok {
|
||||
f = foregroundANSIColors[DefaultColor]
|
||||
}
|
||||
w.WriteRaw(f)
|
||||
w.WriteRaw([]byte{separator})
|
||||
b, ok := backgroundANSIColors[bg]
|
||||
if !ok {
|
||||
b = backgroundANSIColors[DefaultColor]
|
||||
}
|
||||
w.WriteRaw(b)
|
||||
}
|
||||
|
||||
var displayAttributeParameters = map[DisplayAttribute][]byte{
|
||||
DisplayReset: {'0'},
|
||||
DisplayBold: {'1'},
|
||||
DisplayLowIntensity: {'2'},
|
||||
DisplayItalic: {'3'},
|
||||
DisplayUnderline: {'4'},
|
||||
DisplayBlink: {'5'},
|
||||
DisplayRapidBlink: {'6'},
|
||||
DisplayReverse: {'7'},
|
||||
DisplayInvisible: {'8'},
|
||||
DisplayCrossedOut: {'9'},
|
||||
DisplayDefaultFont: {'1', '0'},
|
||||
}
|
||||
|
||||
var foregroundANSIColors = map[Color][]byte{
|
||||
DefaultColor: {'3', '9'},
|
||||
|
||||
// Low intensity.
|
||||
Black: {'3', '0'},
|
||||
DarkRed: {'3', '1'},
|
||||
DarkGreen: {'3', '2'},
|
||||
Brown: {'3', '3'},
|
||||
DarkBlue: {'3', '4'},
|
||||
Purple: {'3', '5'},
|
||||
Cyan: {'3', '6'},
|
||||
LightGray: {'3', '7'},
|
||||
|
||||
// High intensity.
|
||||
DarkGray: {'9', '0'},
|
||||
Red: {'9', '1'},
|
||||
Green: {'9', '2'},
|
||||
Yellow: {'9', '3'},
|
||||
Blue: {'9', '4'},
|
||||
Fuchsia: {'9', '5'},
|
||||
Turquoise: {'9', '6'},
|
||||
White: {'9', '7'},
|
||||
}
|
||||
|
||||
var backgroundANSIColors = map[Color][]byte{
|
||||
DefaultColor: {'4', '9'},
|
||||
|
||||
// Low intensity.
|
||||
Black: {'4', '0'},
|
||||
DarkRed: {'4', '1'},
|
||||
DarkGreen: {'4', '2'},
|
||||
Brown: {'4', '3'},
|
||||
DarkBlue: {'4', '4'},
|
||||
Purple: {'4', '5'},
|
||||
Cyan: {'4', '6'},
|
||||
LightGray: {'4', '7'},
|
||||
|
||||
// High intensity
|
||||
DarkGray: {'1', '0', '0'},
|
||||
Red: {'1', '0', '1'},
|
||||
Green: {'1', '0', '2'},
|
||||
Yellow: {'1', '0', '3'},
|
||||
Blue: {'1', '0', '4'},
|
||||
Fuchsia: {'1', '0', '5'},
|
||||
Turquoise: {'1', '0', '6'},
|
||||
White: {'1', '0', '7'},
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
// +build windows
|
||||
|
||||
package prompt
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
colorable "github.com/mattn/go-colorable"
|
||||
)
|
||||
|
||||
// WindowsWriter is a ConsoleWriter implementation for Win32 console.
|
||||
// Output is converted from VT100 escape sequences by mattn/go-colorable.
|
||||
type WindowsWriter struct {
|
||||
VT100Writer
|
||||
out io.Writer
|
||||
}
|
||||
|
||||
// Flush to flush buffer
|
||||
func (w *WindowsWriter) Flush() error {
|
||||
_, err := w.out.Write(w.buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.buffer = []byte{}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ ConsoleWriter = &WindowsWriter{}
|
||||
|
||||
var (
|
||||
// NewStandardOutputWriter is Deprecated: Please use NewStdoutWriter
|
||||
NewStandardOutputWriter = NewStdoutWriter
|
||||
)
|
||||
|
||||
// NewStdoutWriter returns ConsoleWriter object to write to stdout.
|
||||
// This generates win32 control sequences.
|
||||
func NewStdoutWriter() ConsoleWriter {
|
||||
return &WindowsWriter{
|
||||
out: colorable.NewColorableStdout(),
|
||||
}
|
||||
}
|
||||
|
||||
// NewStderrWriter returns ConsoleWriter object to write to stderr.
|
||||
// This generates win32 control sequences.
|
||||
func NewStderrWriter() ConsoleWriter {
|
||||
return &WindowsWriter{
|
||||
out: colorable.NewColorableStderr(),
|
||||
}
|
||||
}
|
@ -0,0 +1,296 @@
|
||||
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 <return> (meaning breakline is true), and the executor is called first, before exit.
|
||||
// Exit means exit go-prompt (not the overall Go program)
|
||||
type ExitChecker func(in string, breakline bool) bool
|
||||
|
||||
// Completer should return the suggest item from Document.
|
||||
type Completer func(Document) []Suggest
|
||||
|
||||
// Prompt is core struct of go-prompt.
|
||||
type Prompt struct {
|
||||
in ConsoleParser
|
||||
buf *Buffer
|
||||
renderer *Render
|
||||
executor Executor
|
||||
history *History
|
||||
completion *CompletionManager
|
||||
keyBindings []KeyBind
|
||||
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()
|
||||
}
|
@ -0,0 +1,287 @@
|
||||
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
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package prompt
|
||||
|
||||
func dummyExecutor(in string) {}
|
||||
|
||||
// Input get the input data from the user and return it.
|
||||
func Input(prefix string, completer Completer, opts ...Option) string {
|
||||
pt := New(dummyExecutor, completer)
|
||||
pt.renderer.prefixTextColor = DefaultColor
|
||||
pt.renderer.prefix = prefix
|
||||
|
||||
for _, opt := range opts {
|
||||
if err := opt(pt); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return pt.Input()
|
||||
}
|
||||
|
||||
// Choose to the shortcut of input function to select from string array.
|
||||
// Deprecated: Maybe anyone want to use this.
|
||||
func Choose(prefix string, choices []string, opts ...Option) string {
|
||||
completer := newChoiceCompleter(choices, FilterHasPrefix)
|
||||
pt := New(dummyExecutor, completer)
|
||||
pt.renderer.prefixTextColor = DefaultColor
|
||||
pt.renderer.prefix = prefix
|
||||
|
||||
for _, opt := range opts {
|
||||
if err := opt(pt); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return pt.Input()
|
||||
}
|
||||
|
||||
func newChoiceCompleter(choices []string, filter Filter) Completer {
|
||||
s := make([]Suggest, len(choices))
|
||||
for i := range choices {
|
||||
s[i] = Suggest{Text: choices[i]}
|
||||
}
|
||||
return func(x Document) []Suggest {
|
||||
return filter(s, x.GetWordBeforeCursor(), true)
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
// +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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
// +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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
language: go
|
||||
sudo: false
|
||||
go:
|
||||
- 1.13.x
|
||||
- tip
|
||||
|
||||
before_install:
|
||||
- go get -t -v ./...
|
||||
|
||||
script:
|
||||
- ./go.test.sh
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Yasuhiro Matsumoto
|
||||
|
||||
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.
|
@ -0,0 +1,48 @@
|
||||
# go-colorable
|
||||
|
||||
[](https://travis-ci.org/mattn/go-colorable)
|
||||
[](https://codecov.io/gh/mattn/go-colorable)
|
||||
[](http://godoc.org/github.com/mattn/go-colorable)
|
||||
[](https://goreportcard.com/report/mattn/go-colorable)
|
||||
|
||||
Colorable writer for windows.
|
||||
|
||||
For example, most of logger packages doesn't show colors on windows. (I know we can do it with ansicon. But I don't want.)
|
||||
This package is possible to handle escape sequence for ansi color on windows.
|
||||
|
||||
## Too Bad!
|
||||
|
||||

|
||||
|
||||
|
||||
## So Good!
|
||||
|
||||

|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true})
|
||||
logrus.SetOutput(colorable.NewColorableStdout())
|
||||
|
||||
logrus.Info("succeeded")
|
||||
logrus.Warn("not correct")
|
||||
logrus.Error("something error")
|
||||
logrus.Fatal("panic")
|
||||
```
|
||||
|
||||
You can compile above code on non-windows OSs.
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
$ go get github.com/mattn/go-colorable
|
||||
```
|
||||
|
||||
# License
|
||||
|
||||
MIT
|
||||
|
||||
# Author
|
||||
|
||||
Yasuhiro Matsumoto (a.k.a mattn)
|
@ -0,0 +1,37 @@
|
||||
// +build appengine
|
||||
|
||||
package colorable
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
_ "github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
// NewColorable returns new instance of Writer which handles escape sequence.
|
||||
func NewColorable(file *os.File) io.Writer {
|
||||
if file == nil {
|
||||
panic("nil passed instead of *os.File to NewColorable()")
|
||||
}
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
// NewColorableStdout returns new instance of Writer which handles escape sequence for stdout.
|
||||
func NewColorableStdout() io.Writer {
|
||||
return os.Stdout
|
||||
}
|
||||
|
||||
// NewColorableStderr returns new instance of Writer which handles escape sequence for stderr.
|
||||
func NewColorableStderr() io.Writer {
|
||||
return os.Stderr
|
||||
}
|
||||
|
||||
// EnableColorsStdout enable colors if possible.
|
||||
func EnableColorsStdout(enabled *bool) func() {
|
||||
if enabled != nil {
|
||||
*enabled = true
|
||||
}
|
||||
return func() {}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
// +build !windows
|
||||
// +build !appengine
|
||||
|
||||
package colorable
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
_ "github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
// NewColorable returns new instance of Writer which handles escape sequence.
|
||||
func NewColorable(file *os.File) io.Writer {
|
||||
if file == nil {
|
||||
panic("nil passed instead of *os.File to NewColorable()")
|
||||
}
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
// NewColorableStdout returns new instance of Writer which handles escape sequence for stdout.
|
||||
func NewColorableStdout() io.Writer {
|
||||
return os.Stdout
|
||||
}
|
||||
|
||||
// NewColorableStderr returns new instance of Writer which handles escape sequence for stderr.
|
||||
func NewColorableStderr() io.Writer {
|
||||
return os.Stderr
|
||||
}
|
||||
|
||||
// EnableColorsStdout enable colors if possible.
|
||||
func EnableColorsStdout(enabled *bool) func() {
|
||||
if enabled != nil {
|
||||
*enabled = true
|
||||
}
|
||||
return func() {}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
echo "" > coverage.txt
|
||||
|
||||
for d in $(go list ./... | grep -v vendor); do
|
||||
go test -race -coverprofile=profile.out -covermode=atomic "$d"
|
||||
if [ -f profile.out ]; then
|
||||
cat profile.out >> coverage.txt
|
||||
rm profile.out
|
||||
fi
|
||||
done
|
@ -0,0 +1,55 @@
|
||||
package colorable
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
// NonColorable holds writer but removes escape sequence.
|
||||
type NonColorable struct {
|
||||
out io.Writer
|
||||
}
|
||||
|
||||
// NewNonColorable returns new instance of Writer which removes escape sequence from Writer.
|
||||
func NewNonColorable(w io.Writer) io.Writer {
|
||||
return &NonColorable{out: w}
|
||||
}
|
||||
|
||||
// Write writes data on console
|
||||
func (w *NonColorable) Write(data []byte) (n int, err error) {
|
||||
er := bytes.NewReader(data)
|
||||
var bw [1]byte
|
||||
loop:
|
||||
for {
|
||||
c1, err := er.ReadByte()
|
||||
if err != nil {
|
||||
break loop
|
||||
}
|
||||
if c1 != 0x1b {
|
||||
bw[0] = c1
|
||||
w.out.Write(bw[:])
|
||||
continue
|
||||
}
|
||||
c2, err := er.ReadByte()
|
||||
if err != nil {
|
||||
break loop
|
||||
}
|
||||
if c2 != 0x5b {
|
||||
continue
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
for {
|
||||
c, err := er.ReadByte()
|
||||
if err != nil {
|
||||
break loop
|
||||
}
|
||||
if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' {
|
||||
break
|
||||
}
|
||||
buf.Write([]byte(string(c)))
|
||||
}
|
||||
}
|
||||
|
||||
return len(data), nil
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
language: go
|
||||
sudo: false
|
||||
go:
|
||||
- 1.13.x
|
||||
- tip
|
||||
|
||||
before_install:
|
||||
- go get -t -v ./...
|
||||
|
||||
script:
|
||||
- ./go.test.sh
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
@ -0,0 +1,9 @@
|
||||
Copyright (c) Yasuhiro MATSUMOTO <mattn.jp@gmail.com>
|
||||
|
||||
MIT License (Expat)
|
||||
|
||||
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.
|
@ -0,0 +1,50 @@
|
||||
# go-isatty
|
||||
|
||||
[](http://godoc.org/github.com/mattn/go-isatty)
|
||||
[](https://codecov.io/gh/mattn/go-isatty)
|
||||
[](https://coveralls.io/github/mattn/go-isatty?branch=master)
|
||||
[](https://goreportcard.com/report/mattn/go-isatty)
|
||||
|
||||
isatty for golang
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/mattn/go-isatty"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if isatty.IsTerminal(os.Stdout.Fd()) {
|
||||
fmt.Println("Is Terminal")
|
||||
} else if isatty.IsCygwinTerminal(os.Stdout.Fd()) {
|
||||
fmt.Println("Is Cygwin/MSYS2 Terminal")
|
||||
} else {
|
||||
fmt.Println("Is Not Terminal")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
$ go get github.com/mattn/go-isatty
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
## Author
|
||||
|
||||
Yasuhiro Matsumoto (a.k.a mattn)
|
||||
|
||||
## Thanks
|
||||
|
||||
* k-takata: base idea for IsCygwinTerminal
|
||||
|
||||
https://github.com/k-takata/go-iscygpty
|
@ -0,0 +1,2 @@
|
||||
// Package isatty implements interface to isatty
|
||||
package isatty
|
@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
echo "" > coverage.txt
|
||||
|
||||
for d in $(go list ./... | grep -v vendor); do
|
||||
go test -race -coverprofile=profile.out -covermode=atomic "$d"
|
||||
if [ -f profile.out ]; then
|
||||
cat profile.out >> coverage.txt
|
||||
rm profile.out
|
||||
fi
|
||||
done
|
@ -0,0 +1,18 @@
|
||||
// +build darwin freebsd openbsd netbsd dragonfly
|
||||
// +build !appengine
|
||||
|
||||
package isatty
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
// IsTerminal return true if the file descriptor is terminal.
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
_, err := unix.IoctlGetTermios(int(fd), unix.TIOCGETA)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
|
||||
// terminal. This is also always false on this environment.
|
||||
func IsCygwinTerminal(fd uintptr) bool {
|
||||
return false
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
// +build appengine js nacl
|
||||
|
||||
package isatty
|
||||
|
||||
// IsTerminal returns true if the file descriptor is terminal which
|
||||
// is always false on js and appengine classic which is a sandboxed PaaS.
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2
|
||||
// terminal. This is also always false on this environment.
|
||||
func IsCygwinTerminal(fd uintptr) bool {
|
||||
return false
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
// +build plan9
|
||||
|
||||
package isatty
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
path, err := syscall.Fd2path(int(fd))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return path == "/dev/cons" || path == "/mnt/term/dev/cons"
|
||||
}
|
||||
|
||||
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
|
||||
// terminal. This is also always false on this environment.
|
||||
func IsCygwinTerminal(fd uintptr) bool {
|
||||
return false
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
// +build solaris
|
||||
// +build !appengine
|
||||
|
||||
package isatty
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
// see: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libbc/libc/gen/common/isatty.c
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
var termio unix.Termio
|
||||
err := unix.IoctlSetTermio(int(fd), unix.TCGETA, &termio)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
|
||||
// terminal. This is also always false on this environment.
|
||||
func IsCygwinTerminal(fd uintptr) bool {
|
||||
return false
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
// +build linux aix
|
||||
// +build !appengine
|
||||
|
||||
package isatty
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
// IsTerminal return true if the file descriptor is terminal.
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
_, err := unix.IoctlGetTermios(int(fd), unix.TCGETS)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
|
||||
// terminal. This is also always false on this environment.
|
||||
func IsCygwinTerminal(fd uintptr) bool {
|
||||
return false
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
// +build windows
|
||||
// +build !appengine
|
||||
|
||||
package isatty
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
objectNameInfo uintptr = 1
|
||||
fileNameInfo = 2
|
||||
fileTypePipe = 3
|
||||
)
|
||||
|
||||
var (
|
||||
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
ntdll = syscall.NewLazyDLL("ntdll.dll")
|
||||
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
||||
procGetFileInformationByHandleEx = kernel32.NewProc("GetFileInformationByHandleEx")
|
||||
procGetFileType = kernel32.NewProc("GetFileType")
|
||||
procNtQueryObject = ntdll.NewProc("NtQueryObject")
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Check if GetFileInformationByHandleEx is available.
|
||||
if procGetFileInformationByHandleEx.Find() != nil {
|
||||
procGetFileInformationByHandleEx = nil
|
||||
}
|
||||
}
|
||||
|
||||
// IsTerminal return true if the file descriptor is terminal.
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
var st uint32
|
||||
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0)
|
||||
return r != 0 && e == 0
|
||||
}
|
||||
|
||||
// Check pipe name is used for cygwin/msys2 pty.
|
||||
// Cygwin/MSYS2 PTY has a name like:
|
||||
// \{cygwin,msys}-XXXXXXXXXXXXXXXX-ptyN-{from,to}-master
|
||||
func isCygwinPipeName(name string) bool {
|
||||
token := strings.Split(name, "-")
|
||||
if len(token) < 5 {
|
||||
return false
|
||||
}
|
||||
|
||||
if token[0] != `\msys` &&
|
||||
token[0] != `\cygwin` &&
|
||||
token[0] != `\Device\NamedPipe\msys` &&
|
||||
token[0] != `\Device\NamedPipe\cygwin` {
|
||||
return false
|
||||
}
|
||||
|
||||
if token[1] == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(token[2], "pty") {
|
||||
return false
|
||||
}
|
||||
|
||||
if token[3] != `from` && token[3] != `to` {
|
||||
return false
|
||||
}
|
||||
|
||||
if token[4] != "master" {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// getFileNameByHandle use the undocomented ntdll NtQueryObject to get file full name from file handler
|
||||
// since GetFileInformationByHandleEx is not avilable under windows Vista and still some old fashion
|
||||
// guys are using Windows XP, this is a workaround for those guys, it will also work on system from
|
||||
// Windows vista to 10
|
||||
// see https://stackoverflow.com/a/18792477 for details
|
||||
func getFileNameByHandle(fd uintptr) (string, error) {
|
||||
if procNtQueryObject == nil {
|
||||
return "", errors.New("ntdll.dll: NtQueryObject not supported")
|
||||
}
|
||||
|
||||
var buf [4 + syscall.MAX_PATH]uint16
|
||||
var result int
|
||||
r, _, e := syscall.Syscall6(procNtQueryObject.Addr(), 5,
|
||||
fd, objectNameInfo, uintptr(unsafe.Pointer(&buf)), uintptr(2*len(buf)), uintptr(unsafe.Pointer(&result)), 0)
|
||||
if r != 0 {
|
||||
return "", e
|
||||
}
|
||||
return string(utf16.Decode(buf[4 : 4+buf[0]/2])), nil
|
||||
}
|
||||
|
||||
// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2
|
||||
// terminal.
|
||||
func IsCygwinTerminal(fd uintptr) bool {
|
||||
if procGetFileInformationByHandleEx == nil {
|
||||
name, err := getFileNameByHandle(fd)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return isCygwinPipeName(name)
|
||||
}
|
||||
|
||||
// Cygwin/msys's pty is a pipe.
|
||||
ft, _, e := syscall.Syscall(procGetFileType.Addr(), 1, fd, 0, 0)
|
||||
if ft != fileTypePipe || e != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
var buf [2 + syscall.MAX_PATH]uint16
|
||||
r, _, e := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(),
|
||||
4, fd, fileNameInfo, uintptr(unsafe.Pointer(&buf)),
|
||||
uintptr(len(buf)*2), 0, 0)
|
||||
if r == 0 || e != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
l := *(*uint32)(unsafe.Pointer(&buf))
|
||||
return isCygwinPipeName(string(utf16.Decode(buf[2 : 2+l/2])))
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": [
|
||||
"config:base"
|
||||
],
|
||||
"postUpdateOptions": [
|
||||
"gomodTidy"
|
||||
]
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
language: go
|
||||
sudo: false
|
||||
go:
|
||||
- 1.13.x
|
||||
- tip
|
||||
|
||||
before_install:
|
||||
- go get -t -v ./...
|
||||
|
||||
script:
|
||||
- go generate
|
||||
- git diff --cached --exit-code
|
||||
- ./go.test.sh
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Yasuhiro Matsumoto
|
||||
|
||||
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.
|
@ -0,0 +1,27 @@
|
||||
go-runewidth
|
||||
============
|
||||
|
||||
[](https://travis-ci.org/mattn/go-runewidth)
|
||||
[](https://codecov.io/gh/mattn/go-runewidth)
|
||||
[](http://godoc.org/github.com/mattn/go-runewidth)
|
||||
[](https://goreportcard.com/report/github.com/mattn/go-runewidth)
|
||||
|
||||
Provides functions to get fixed width of the character or string.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
```go
|
||||
runewidth.StringWidth("つのだ☆HIRO") == 12
|
||||
```
|
||||
|
||||
|
||||
Author
|
||||
------
|
||||
|
||||
Yasuhiro Matsumoto
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
under the MIT License: http://mattn.mit-license.org/2013
|
@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
echo "" > coverage.txt
|
||||
|
||||
for d in $(go list ./... | grep -v vendor); do
|
||||
go test -race -coverprofile=profile.out -covermode=atomic "$d"
|
||||
if [ -f profile.out ]; then
|
||||
cat profile.out >> coverage.txt
|
||||
rm profile.out
|
||||
fi
|
||||
done
|
@ -0,0 +1,257 @@
|
||||
package runewidth
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
//go:generate go run script/generate.go
|
||||
|
||||
var (
|
||||
// EastAsianWidth will be set true if the current locale is CJK
|
||||
EastAsianWidth bool
|
||||
|
||||
// ZeroWidthJoiner is flag to set to use UTR#51 ZWJ
|
||||
ZeroWidthJoiner bool
|
||||
|
||||
// DefaultCondition is a condition in current locale
|
||||
DefaultCondition = &Condition{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
handleEnv()
|
||||
}
|
||||
|
||||
func handleEnv() {
|
||||
env := os.Getenv("RUNEWIDTH_EASTASIAN")
|
||||
if env == "" {
|
||||
EastAsianWidth = IsEastAsian()
|
||||
} else {
|
||||
EastAsianWidth = env == "1"
|
||||
}
|
||||
// update DefaultCondition
|
||||
DefaultCondition.EastAsianWidth = EastAsianWidth
|
||||
DefaultCondition.ZeroWidthJoiner = ZeroWidthJoiner
|
||||
}
|
||||
|
||||
type interval struct {
|
||||
first rune
|
||||
last rune
|
||||
}
|
||||
|
||||
type table []interval
|
||||
|
||||
func inTables(r rune, ts ...table) bool {
|
||||
for _, t := range ts {
|
||||
if inTable(r, t) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func inTable(r rune, t table) bool {
|
||||
if r < t[0].first {
|
||||
return false
|
||||
}
|
||||
|
||||
bot := 0
|
||||
top := len(t) - 1
|
||||
for top >= bot {
|
||||
mid := (bot + top) >> 1
|
||||
|
||||
switch {
|
||||
case t[mid].last < r:
|
||||
bot = mid + 1
|
||||
case t[mid].first > r:
|
||||
top = mid - 1
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
var private = table{
|
||||
{0x00E000, 0x00F8FF}, {0x0F0000, 0x0FFFFD}, {0x100000, 0x10FFFD},
|
||||
}
|
||||
|
||||
var nonprint = table{
|
||||
{0x0000, 0x001F}, {0x007F, 0x009F}, {0x00AD, 0x00AD},
|
||||
{0x070F, 0x070F}, {0x180B, 0x180E}, {0x200B, 0x200F},
|
||||
{0x2028, 0x202E}, {0x206A, 0x206F}, {0xD800, 0xDFFF},
|
||||
{0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFB}, {0xFFFE, 0xFFFF},
|
||||
}
|
||||
|
||||
// Condition have flag EastAsianWidth whether the current locale is CJK or not.
|
||||
type Condition struct {
|
||||
EastAsianWidth bool
|
||||
ZeroWidthJoiner bool
|
||||
}
|
||||
|
||||
// NewCondition return new instance of Condition which is current locale.
|
||||
func NewCondition() *Condition {
|
||||
return &Condition{
|
||||
EastAsianWidth: EastAsianWidth,
|
||||
ZeroWidthJoiner: ZeroWidthJoiner,
|
||||
}
|
||||
}
|
||||
|
||||
// RuneWidth returns the number of cells in r.
|
||||
// See http://www.unicode.org/reports/tr11/
|
||||
func (c *Condition) RuneWidth(r rune) int {
|
||||
switch {
|
||||
case r < 0 || r > 0x10FFFF || inTables(r, nonprint, combining, notassigned):
|
||||
return 0
|
||||
case (c.EastAsianWidth && IsAmbiguousWidth(r)) || inTables(r, doublewidth):
|
||||
return 2
|
||||
default:
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Condition) stringWidth(s string) (width int) {
|
||||
for _, r := range []rune(s) {
|
||||
width += c.RuneWidth(r)
|
||||
}
|
||||
return width
|
||||
}
|
||||
|
||||
func (c *Condition) stringWidthZeroJoiner(s string) (width int) {
|
||||
r1, r2 := rune(0), rune(0)
|
||||
for _, r := range []rune(s) {
|
||||
if r == 0xFE0E || r == 0xFE0F {
|
||||
continue
|
||||
}
|
||||
w := c.RuneWidth(r)
|
||||
if r2 == 0x200D && inTables(r, emoji) && inTables(r1, emoji) {
|
||||
if width < w {
|
||||
width = w
|
||||
}
|
||||
} else {
|
||||
width += w
|
||||
}
|
||||
r1, r2 = r2, r
|
||||
}
|
||||
return width
|
||||
}
|
||||
|
||||
// StringWidth return width as you can see
|
||||
func (c *Condition) StringWidth(s string) (width int) {
|
||||
if c.ZeroWidthJoiner {
|
||||
return c.stringWidthZeroJoiner(s)
|
||||
}
|
||||
return c.stringWidth(s)
|
||||
}
|
||||
|
||||
// Truncate return string truncated with w cells
|
||||
func (c *Condition) Truncate(s string, w int, tail string) string {
|
||||
if c.StringWidth(s) <= w {
|
||||
return s
|
||||
}
|
||||
r := []rune(s)
|
||||
tw := c.StringWidth(tail)
|
||||
w -= tw
|
||||
width := 0
|
||||
i := 0
|
||||
for ; i < len(r); i++ {
|
||||
cw := c.RuneWidth(r[i])
|
||||
if width+cw > w {
|
||||
break
|
||||
}
|
||||
width += cw
|
||||
}
|
||||
return string(r[0:i]) + tail
|
||||
}
|
||||
|
||||
// Wrap return string wrapped with w cells
|
||||
func (c *Condition) Wrap(s string, w int) string {
|
||||
width := 0
|
||||
out := ""
|
||||
for _, r := range []rune(s) {
|
||||
cw := RuneWidth(r)
|
||||
if r == '\n' {
|
||||
out += string(r)
|
||||
width = 0
|
||||
continue
|
||||
} else if width+cw > w {
|
||||
out += "\n"
|
||||
width = 0
|
||||
out += string(r)
|
||||
width += cw
|
||||
continue
|
||||
}
|
||||
out += string(r)
|
||||
width += cw
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// FillLeft return string filled in left by spaces in w cells
|
||||
func (c *Condition) FillLeft(s string, w int) string {
|
||||
width := c.StringWidth(s)
|
||||
count := w - width
|
||||
if count > 0 {
|
||||
b := make([]byte, count)
|
||||
for i := range b {
|
||||
b[i] = ' '
|
||||
}
|
||||
return string(b) + s
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// FillRight return string filled in left by spaces in w cells
|
||||
func (c *Condition) FillRight(s string, w int) string {
|
||||
width := c.StringWidth(s)
|
||||
count := w - width
|
||||
if count > 0 {
|
||||
b := make([]byte, count)
|
||||
for i := range b {
|
||||
b[i] = ' '
|
||||
}
|
||||
return s + string(b)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// RuneWidth returns the number of cells in r.
|
||||
// See http://www.unicode.org/reports/tr11/
|
||||
func RuneWidth(r rune) int {
|
||||
return DefaultCondition.RuneWidth(r)
|
||||
}
|
||||
|
||||
// IsAmbiguousWidth returns whether is ambiguous width or not.
|
||||
func IsAmbiguousWidth(r rune) bool {
|
||||
return inTables(r, private, ambiguous)
|
||||
}
|
||||
|
||||
// IsNeutralWidth returns whether is neutral width or not.
|
||||
func IsNeutralWidth(r rune) bool {
|
||||
return inTable(r, neutral)
|
||||
}
|
||||
|
||||
// StringWidth return width as you can see
|
||||
func StringWidth(s string) (width int) {
|
||||
return DefaultCondition.StringWidth(s)
|
||||
}
|
||||
|
||||
// Truncate return string truncated with w cells
|
||||
func Truncate(s string, w int, tail string) string {
|
||||
return DefaultCondition.Truncate(s, w, tail)
|
||||
}
|
||||
|
||||
// Wrap return string wrapped with w cells
|
||||
func Wrap(s string, w int) string {
|
||||
return DefaultCondition.Wrap(s, w)
|
||||
}
|
||||
|
||||
// FillLeft return string filled in left by spaces in w cells
|
||||
func FillLeft(s string, w int) string {
|
||||
return DefaultCondition.FillLeft(s, w)
|
||||
}
|
||||
|
||||
// FillRight return string filled in left by spaces in w cells
|
||||
func FillRight(s string, w int) string {
|
||||
return DefaultCondition.FillRight(s, w)
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
// +build appengine
|
||||
|
||||
package runewidth
|
||||
|
||||
// IsEastAsian return true if the current locale is CJK
|
||||
func IsEastAsian() bool {
|
||||
return false
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
// +build js
|
||||
// +build !appengine
|
||||
|
||||
package runewidth
|
||||
|
||||
func IsEastAsian() bool {
|
||||
// TODO: Implement this for the web. Detect east asian in a compatible way, and return true.
|
||||
return false
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
// +build !windows
|
||||
// +build !js
|
||||
// +build !appengine
|
||||
|
||||
package runewidth
|
||||
|
||||
import (
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var reLoc = regexp.MustCompile(`^[a-z][a-z][a-z]?(?:_[A-Z][A-Z])?\.(.+)`)
|
||||
|
||||
var mblenTable = map[string]int{
|
||||
"utf-8": 6,
|
||||
"utf8": 6,
|
||||
"jis": 8,
|
||||
"eucjp": 3,
|
||||
"euckr": 2,
|
||||
"euccn": 2,
|
||||
"sjis": 2,
|
||||
"cp932": 2,
|
||||
"cp51932": 2,
|
||||
"cp936": 2,
|
||||
"cp949": 2,
|
||||
"cp950": 2,
|
||||
"big5": 2,
|
||||
"gbk": 2,
|
||||
"gb2312": 2,
|
||||
}
|
||||
|
||||
func isEastAsian(locale string) bool {
|
||||
charset := strings.ToLower(locale)
|
||||
r := reLoc.FindStringSubmatch(locale)
|
||||
if len(r) == 2 {
|
||||
charset = strings.ToLower(r[1])
|
||||
}
|
||||
|
||||
if strings.HasSuffix(charset, "@cjk_narrow") {
|
||||
return false
|
||||
}
|
||||
|
||||
for pos, b := range []byte(charset) {
|
||||
if b == '@' {
|
||||
charset = charset[:pos]
|
||||
break
|
||||
}
|
||||
}
|
||||
max := 1
|
||||
if m, ok := mblenTable[charset]; ok {
|
||||
max = m
|
||||
}
|
||||
if max > 1 && (charset[0] != 'u' ||
|
||||
strings.HasPrefix(locale, "ja") ||
|
||||
strings.HasPrefix(locale, "ko") ||
|
||||
strings.HasPrefix(locale, "zh")) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsEastAsian return true if the current locale is CJK
|
||||
func IsEastAsian() bool {
|
||||
locale := os.Getenv("LC_ALL")
|
||||
if locale == "" {
|
||||
locale = os.Getenv("LC_CTYPE")
|
||||
}
|
||||
if locale == "" {
|
||||
locale = os.Getenv("LANG")
|
||||
}
|
||||
|
||||
// ignore C locale
|
||||
if locale == "POSIX" || locale == "C" {
|
||||
return false
|
||||
}
|
||||
if len(locale) > 1 && locale[0] == 'C' && (locale[1] == '.' || locale[1] == '-') {
|
||||
return false
|
||||
}
|
||||
|
||||
return isEastAsian(locale)
|
||||
}
|
@ -0,0 +1,437 @@
|
||||
// Code generated by script/generate.go. DO NOT EDIT.
|
||||
|
||||
package runewidth
|
||||
|
||||
var combining = table{
|
||||
{0x0300, 0x036F}, {0x0483, 0x0489}, {0x07EB, 0x07F3},
|
||||
{0x0C00, 0x0C00}, {0x0C04, 0x0C04}, {0x0D00, 0x0D01},
|
||||
{0x135D, 0x135F}, {0x1A7F, 0x1A7F}, {0x1AB0, 0x1AC0},
|
||||
{0x1B6B, 0x1B73}, {0x1DC0, 0x1DF9}, {0x1DFB, 0x1DFF},
|
||||
{0x20D0, 0x20F0}, {0x2CEF, 0x2CF1}, {0x2DE0, 0x2DFF},
|
||||
{0x3099, 0x309A}, {0xA66F, 0xA672}, {0xA674, 0xA67D},
|
||||
{0xA69E, 0xA69F}, {0xA6F0, 0xA6F1}, {0xA8E0, 0xA8F1},
|
||||
{0xFE20, 0xFE2F}, {0x101FD, 0x101FD}, {0x10376, 0x1037A},
|
||||
{0x10EAB, 0x10EAC}, {0x10F46, 0x10F50}, {0x11300, 0x11301},
|
||||
{0x1133B, 0x1133C}, {0x11366, 0x1136C}, {0x11370, 0x11374},
|
||||
{0x16AF0, 0x16AF4}, {0x1D165, 0x1D169}, {0x1D16D, 0x1D172},
|
||||
{0x1D17B, 0x1D182}, {0x1D185, 0x1D18B}, {0x1D1AA, 0x1D1AD},
|
||||
{0x1D242, 0x1D244}, {0x1E000, 0x1E006}, {0x1E008, 0x1E018},
|
||||
{0x1E01B, 0x1E021}, {0x1E023, 0x1E024}, {0x1E026, 0x1E02A},
|
||||
{0x1E8D0, 0x1E8D6},
|
||||
}
|
||||
|
||||
var doublewidth = table{
|
||||
{0x1100, 0x115F}, {0x231A, 0x231B}, {0x2329, 0x232A},
|
||||
{0x23E9, 0x23EC}, {0x23F0, 0x23F0}, {0x23F3, 0x23F3},
|
||||
{0x25FD, 0x25FE}, {0x2614, 0x2615}, {0x2648, 0x2653},
|
||||
{0x267F, 0x267F}, {0x2693, 0x2693}, {0x26A1, 0x26A1},
|
||||
{0x26AA, 0x26AB}, {0x26BD, 0x26BE}, {0x26C4, 0x26C5},
|
||||
{0x26CE, 0x26CE}, {0x26D4, 0x26D4}, {0x26EA, 0x26EA},
|
||||
{0x26F2, 0x26F3}, {0x26F5, 0x26F5}, {0x26FA, 0x26FA},
|
||||
{0x26FD, 0x26FD}, {0x2705, 0x2705}, {0x270A, 0x270B},
|
||||
{0x2728, 0x2728}, {0x274C, 0x274C}, {0x274E, 0x274E},
|
||||
{0x2753, 0x2755}, {0x2757, 0x2757}, {0x2795, 0x2797},
|
||||
{0x27B0, 0x27B0}, {0x27BF, 0x27BF}, {0x2B1B, 0x2B1C},
|
||||
{0x2B50, 0x2B50}, {0x2B55, 0x2B55}, {0x2E80, 0x2E99},
|
||||
{0x2E9B, 0x2EF3}, {0x2F00, 0x2FD5}, {0x2FF0, 0x2FFB},
|
||||
{0x3000, 0x303E}, {0x3041, 0x3096}, {0x3099, 0x30FF},
|
||||
{0x3105, 0x312F}, {0x3131, 0x318E}, {0x3190, 0x31E3},
|
||||
{0x31F0, 0x321E}, {0x3220, 0x3247}, {0x3250, 0x4DBF},
|
||||
{0x4E00, 0xA48C}, {0xA490, 0xA4C6}, {0xA960, 0xA97C},
|
||||
{0xAC00, 0xD7A3}, {0xF900, 0xFAFF}, {0xFE10, 0xFE19},
|
||||
{0xFE30, 0xFE52}, {0xFE54, 0xFE66}, {0xFE68, 0xFE6B},
|
||||
{0xFF01, 0xFF60}, {0xFFE0, 0xFFE6}, {0x16FE0, 0x16FE4},
|
||||
{0x16FF0, 0x16FF1}, {0x17000, 0x187F7}, {0x18800, 0x18CD5},
|
||||
{0x18D00, 0x18D08}, {0x1B000, 0x1B11E}, {0x1B150, 0x1B152},
|
||||
{0x1B164, 0x1B167}, {0x1B170, 0x1B2FB}, {0x1F004, 0x1F004},
|
||||
{0x1F0CF, 0x1F0CF}, {0x1F18E, 0x1F18E}, {0x1F191, 0x1F19A},
|
||||
{0x1F200, 0x1F202}, {0x1F210, 0x1F23B}, {0x1F240, 0x1F248},
|
||||
{0x1F250, 0x1F251}, {0x1F260, 0x1F265}, {0x1F300, 0x1F320},
|
||||
{0x1F32D, 0x1F335}, {0x1F337, 0x1F37C}, {0x1F37E, 0x1F393},
|
||||
{0x1F3A0, 0x1F3CA}, {0x1F3CF, 0x1F3D3}, {0x1F3E0, 0x1F3F0},
|
||||
{0x1F3F4, 0x1F3F4}, {0x1F3F8, 0x1F43E}, {0x1F440, 0x1F440},
|
||||
{0x1F442, 0x1F4FC}, {0x1F4FF, 0x1F53D}, {0x1F54B, 0x1F54E},
|
||||
{0x1F550, 0x1F567}, {0x1F57A, 0x1F57A}, {0x1F595, 0x1F596},
|
||||
{0x1F5A4, 0x1F5A4}, {0x1F5FB, 0x1F64F}, {0x1F680, 0x1F6C5},
|
||||
{0x1F6CC, 0x1F6CC}, {0x1F6D0, 0x1F6D2}, {0x1F6D5, 0x1F6D7},
|
||||
{0x1F6EB, 0x1F6EC}, {0x1F6F4, 0x1F6FC}, {0x1F7E0, 0x1F7EB},
|
||||
{0x1F90C, 0x1F93A}, {0x1F93C, 0x1F945}, {0x1F947, 0x1F978},
|
||||
{0x1F97A, 0x1F9CB}, {0x1F9CD, 0x1F9FF}, {0x1FA70, 0x1FA74},
|
||||
{0x1FA78, 0x1FA7A}, {0x1FA80, 0x1FA86}, {0x1FA90, 0x1FAA8},
|
||||
{0x1FAB0, 0x1FAB6}, {0x1FAC0, 0x1FAC2}, {0x1FAD0, 0x1FAD6},
|
||||
{0x20000, 0x2FFFD}, {0x30000, 0x3FFFD},
|
||||
}
|
||||
|
||||
var ambiguous = table{
|
||||
{0x00A1, 0x00A1}, {0x00A4, 0x00A4}, {0x00A7, 0x00A8},
|
||||
{0x00AA, 0x00AA}, {0x00AD, 0x00AE}, {0x00B0, 0x00B4},
|
||||
{0x00B6, 0x00BA}, {0x00BC, 0x00BF}, {0x00C6, 0x00C6},
|
||||
{0x00D0, 0x00D0}, {0x00D7, 0x00D8}, {0x00DE, 0x00E1},
|
||||
{0x00E6, 0x00E6}, {0x00E8, 0x00EA}, {0x00EC, 0x00ED},
|
||||
{0x00F0, 0x00F0}, {0x00F2, 0x00F3}, {0x00F7, 0x00FA},
|
||||
{0x00FC, 0x00FC}, {0x00FE, 0x00FE}, {0x0101, 0x0101},
|
||||
{0x0111, 0x0111}, {0x0113, 0x0113}, {0x011B, 0x011B},
|
||||
{0x0126, 0x0127}, {0x012B, 0x012B}, {0x0131, 0x0133},
|
||||
{0x0138, 0x0138}, {0x013F, 0x0142}, {0x0144, 0x0144},
|
||||
{0x0148, 0x014B}, {0x014D, 0x014D}, {0x0152, 0x0153},
|
||||
{0x0166, 0x0167}, {0x016B, 0x016B}, {0x01CE, 0x01CE},
|
||||
{0x01D0, 0x01D0}, {0x01D2, 0x01D2}, {0x01D4, 0x01D4},
|
||||
{0x01D6, 0x01D6}, {0x01D8, 0x01D8}, {0x01DA, 0x01DA},
|
||||
{0x01DC, 0x01DC}, {0x0251, 0x0251}, {0x0261, 0x0261},
|
||||
{0x02C4, 0x02C4}, {0x02C7, 0x02C7}, {0x02C9, 0x02CB},
|
||||
{0x02CD, 0x02CD}, {0x02D0, 0x02D0}, {0x02D8, 0x02DB},
|
||||
{0x02DD, 0x02DD}, {0x02DF, 0x02DF}, {0x0300, 0x036F},
|
||||
{0x0391, 0x03A1}, {0x03A3, 0x03A9}, {0x03B1, 0x03C1},
|
||||
{0x03C3, 0x03C9}, {0x0401, 0x0401}, {0x0410, 0x044F},
|
||||
{0x0451, 0x0451}, {0x2010, 0x2010}, {0x2013, 0x2016},
|
||||
{0x2018, 0x2019}, {0x201C, 0x201D}, {0x2020, 0x2022},
|
||||
{0x2024, 0x2027}, {0x2030, 0x2030}, {0x2032, 0x2033},
|
||||
{0x2035, 0x2035}, {0x203B, 0x203B}, {0x203E, 0x203E},
|
||||
{0x2074, 0x2074}, {0x207F, 0x207F}, {0x2081, 0x2084},
|
||||
{0x20AC, 0x20AC}, {0x2103, 0x2103}, {0x2105, 0x2105},
|
||||
{0x2109, 0x2109}, {0x2113, 0x2113}, {0x2116, 0x2116},
|
||||
{0x2121, 0x2122}, {0x2126, 0x2126}, {0x212B, 0x212B},
|
||||
{0x2153, 0x2154}, {0x215B, 0x215E}, {0x2160, 0x216B},
|
||||
{0x2170, 0x2179}, {0x2189, 0x2189}, {0x2190, 0x2199},
|
||||
{0x21B8, 0x21B9}, {0x21D2, 0x21D2}, {0x21D4, 0x21D4},
|
||||
{0x21E7, 0x21E7}, {0x2200, 0x2200}, {0x2202, 0x2203},
|
||||
{0x2207, 0x2208}, {0x220B, 0x220B}, {0x220F, 0x220F},
|
||||
{0x2211, 0x2211}, {0x2215, 0x2215}, {0x221A, 0x221A},
|
||||
{0x221D, 0x2220}, {0x2223, 0x2223}, {0x2225, 0x2225},
|
||||
{0x2227, 0x222C}, {0x222E, 0x222E}, {0x2234, 0x2237},
|
||||
{0x223C, 0x223D}, {0x2248, 0x2248}, {0x224C, 0x224C},
|
||||
{0x2252, 0x2252}, {0x2260, 0x2261}, {0x2264, 0x2267},
|
||||
{0x226A, 0x226B}, {0x226E, 0x226F}, {0x2282, 0x2283},
|
||||
{0x2286, 0x2287}, {0x2295, 0x2295}, {0x2299, 0x2299},
|
||||
{0x22A5, 0x22A5}, {0x22BF, 0x22BF}, {0x2312, 0x2312},
|
||||
{0x2460, 0x24E9}, {0x24EB, 0x254B}, {0x2550, 0x2573},
|
||||
{0x2580, 0x258F}, {0x2592, 0x2595}, {0x25A0, 0x25A1},
|
||||
{0x25A3, 0x25A9}, {0x25B2, 0x25B3}, {0x25B6, 0x25B7},
|
||||
{0x25BC, 0x25BD}, {0x25C0, 0x25C1}, {0x25C6, 0x25C8},
|
||||
{0x25CB, 0x25CB}, {0x25CE, 0x25D1}, {0x25E2, 0x25E5},
|
||||
{0x25EF, 0x25EF}, {0x2605, 0x2606}, {0x2609, 0x2609},
|
||||
{0x260E, 0x260F}, {0x261C, 0x261C}, {0x261E, 0x261E},
|
||||
{0x2640, 0x2640}, {0x2642, 0x2642}, {0x2660, 0x2661},
|
||||
{0x2663, 0x2665}, {0x2667, 0x266A}, {0x266C, 0x266D},
|
||||
{0x266F, 0x266F}, {0x269E, 0x269F}, {0x26BF, 0x26BF},
|
||||
{0x26C6, 0x26CD}, {0x26CF, 0x26D3}, {0x26D5, 0x26E1},
|
||||
{0x26E3, 0x26E3}, {0x26E8, 0x26E9}, {0x26EB, 0x26F1},
|
||||
{0x26F4, 0x26F4}, {0x26F6, 0x26F9}, {0x26FB, 0x26FC},
|
||||
{0x26FE, 0x26FF}, {0x273D, 0x273D}, {0x2776, 0x277F},
|
||||
{0x2B56, 0x2B59}, {0x3248, 0x324F}, {0xE000, 0xF8FF},
|
||||
{0xFE00, 0xFE0F}, {0xFFFD, 0xFFFD}, {0x1F100, 0x1F10A},
|
||||
{0x1F110, 0x1F12D}, {0x1F130, 0x1F169}, {0x1F170, 0x1F18D},
|
||||
{0x1F18F, 0x1F190}, {0x1F19B, 0x1F1AC}, {0xE0100, 0xE01EF},
|
||||
{0xF0000, 0xFFFFD}, {0x100000, 0x10FFFD},
|
||||
}
|
||||
var notassigned = table{
|
||||
{0x27E6, 0x27ED}, {0x2985, 0x2986},
|
||||
}
|
||||
|
||||
var neutral = table{
|
||||
{0x0000, 0x001F}, {0x007F, 0x00A0}, {0x00A9, 0x00A9},
|
||||
{0x00AB, 0x00AB}, {0x00B5, 0x00B5}, {0x00BB, 0x00BB},
|
||||
{0x00C0, 0x00C5}, {0x00C7, 0x00CF}, {0x00D1, 0x00D6},
|
||||
{0x00D9, 0x00DD}, {0x00E2, 0x00E5}, {0x00E7, 0x00E7},
|
||||
{0x00EB, 0x00EB}, {0x00EE, 0x00EF}, {0x00F1, 0x00F1},
|
||||
{0x00F4, 0x00F6}, {0x00FB, 0x00FB}, {0x00FD, 0x00FD},
|
||||
{0x00FF, 0x0100}, {0x0102, 0x0110}, {0x0112, 0x0112},
|
||||
{0x0114, 0x011A}, {0x011C, 0x0125}, {0x0128, 0x012A},
|
||||
{0x012C, 0x0130}, {0x0134, 0x0137}, {0x0139, 0x013E},
|
||||
{0x0143, 0x0143}, {0x0145, 0x0147}, {0x014C, 0x014C},
|
||||
{0x014E, 0x0151}, {0x0154, 0x0165}, {0x0168, 0x016A},
|
||||
{0x016C, 0x01CD}, {0x01CF, 0x01CF}, {0x01D1, 0x01D1},
|
||||
{0x01D3, 0x01D3}, {0x01D5, 0x01D5}, {0x01D7, 0x01D7},
|
||||
{0x01D9, 0x01D9}, {0x01DB, 0x01DB}, {0x01DD, 0x0250},
|
||||
{0x0252, 0x0260}, {0x0262, 0x02C3}, {0x02C5, 0x02C6},
|
||||
{0x02C8, 0x02C8}, {0x02CC, 0x02CC}, {0x02CE, 0x02CF},
|
||||
{0x02D1, 0x02D7}, {0x02DC, 0x02DC}, {0x02DE, 0x02DE},
|
||||
{0x02E0, 0x02FF}, {0x0370, 0x0377}, {0x037A, 0x037F},
|
||||
{0x0384, 0x038A}, {0x038C, 0x038C}, {0x038E, 0x0390},
|
||||
{0x03AA, 0x03B0}, {0x03C2, 0x03C2}, {0x03CA, 0x0400},
|
||||
{0x0402, 0x040F}, {0x0450, 0x0450}, {0x0452, 0x052F},
|
||||
{0x0531, 0x0556}, {0x0559, 0x058A}, {0x058D, 0x058F},
|
||||
{0x0591, 0x05C7}, {0x05D0, 0x05EA}, {0x05EF, 0x05F4},
|
||||
{0x0600, 0x061C}, {0x061E, 0x070D}, {0x070F, 0x074A},
|
||||
{0x074D, 0x07B1}, {0x07C0, 0x07FA}, {0x07FD, 0x082D},
|
||||
{0x0830, 0x083E}, {0x0840, 0x085B}, {0x085E, 0x085E},
|
||||
{0x0860, 0x086A}, {0x08A0, 0x08B4}, {0x08B6, 0x08C7},
|
||||
{0x08D3, 0x0983}, {0x0985, 0x098C}, {0x098F, 0x0990},
|
||||
{0x0993, 0x09A8}, {0x09AA, 0x09B0}, {0x09B2, 0x09B2},
|
||||
{0x09B6, 0x09B9}, {0x09BC, 0x09C4}, {0x09C7, 0x09C8},
|
||||
{0x09CB, 0x09CE}, {0x09D7, 0x09D7}, {0x09DC, 0x09DD},
|
||||
{0x09DF, 0x09E3}, {0x09E6, 0x09FE}, {0x0A01, 0x0A03},
|
||||
{0x0A05, 0x0A0A}, {0x0A0F, 0x0A10}, {0x0A13, 0x0A28},
|
||||
{0x0A2A, 0x0A30}, {0x0A32, 0x0A33}, {0x0A35, 0x0A36},
|
||||
{0x0A38, 0x0A39}, {0x0A3C, 0x0A3C}, {0x0A3E, 0x0A42},
|
||||
{0x0A47, 0x0A48}, {0x0A4B, 0x0A4D}, {0x0A51, 0x0A51},
|
||||
{0x0A59, 0x0A5C}, {0x0A5E, 0x0A5E}, {0x0A66, 0x0A76},
|
||||
{0x0A81, 0x0A83}, {0x0A85, 0x0A8D}, {0x0A8F, 0x0A91},
|
||||
{0x0A93, 0x0AA8}, {0x0AAA, 0x0AB0}, {0x0AB2, 0x0AB3},
|
||||
{0x0AB5, 0x0AB9}, {0x0ABC, 0x0AC5}, {0x0AC7, 0x0AC9},
|
||||
{0x0ACB, 0x0ACD}, {0x0AD0, 0x0AD0}, {0x0AE0, 0x0AE3},
|
||||
{0x0AE6, 0x0AF1}, {0x0AF9, 0x0AFF}, {0x0B01, 0x0B03},
|
||||
{0x0B05, 0x0B0C}, {0x0B0F, 0x0B10}, {0x0B13, 0x0B28},
|
||||
{0x0B2A, 0x0B30}, {0x0B32, 0x0B33}, {0x0B35, 0x0B39},
|
||||
{0x0B3C, 0x0B44}, {0x0B47, 0x0B48}, {0x0B4B, 0x0B4D},
|
||||
{0x0B55, 0x0B57}, {0x0B5C, 0x0B5D}, {0x0B5F, 0x0B63},
|
||||
{0x0B66, 0x0B77}, {0x0B82, 0x0B83}, {0x0B85, 0x0B8A},
|
||||
{0x0B8E, 0x0B90}, {0x0B92, 0x0B95}, {0x0B99, 0x0B9A},
|
||||
{0x0B9C, 0x0B9C}, {0x0B9E, 0x0B9F}, {0x0BA3, 0x0BA4},
|
||||
{0x0BA8, 0x0BAA}, {0x0BAE, 0x0BB9}, {0x0BBE, 0x0BC2},
|
||||
{0x0BC6, 0x0BC8}, {0x0BCA, 0x0BCD}, {0x0BD0, 0x0BD0},
|
||||
{0x0BD7, 0x0BD7}, {0x0BE6, 0x0BFA}, {0x0C00, 0x0C0C},
|
||||
{0x0C0E, 0x0C10}, {0x0C12, 0x0C28}, {0x0C2A, 0x0C39},
|
||||
{0x0C3D, 0x0C44}, {0x0C46, 0x0C48}, {0x0C4A, 0x0C4D},
|
||||
{0x0C55, 0x0C56}, {0x0C58, 0x0C5A}, {0x0C60, 0x0C63},
|
||||
{0x0C66, 0x0C6F}, {0x0C77, 0x0C8C}, {0x0C8E, 0x0C90},
|
||||
{0x0C92, 0x0CA8}, {0x0CAA, 0x0CB3}, {0x0CB5, 0x0CB9},
|
||||
{0x0CBC, 0x0CC4}, {0x0CC6, 0x0CC8}, {0x0CCA, 0x0CCD},
|
||||
{0x0CD5, 0x0CD6}, {0x0CDE, 0x0CDE}, {0x0CE0, 0x0CE3},
|
||||
{0x0CE6, 0x0CEF}, {0x0CF1, 0x0CF2}, {0x0D00, 0x0D0C},
|
||||
{0x0D0E, 0x0D10}, {0x0D12, 0x0D44}, {0x0D46, 0x0D48},
|
||||
{0x0D4A, 0x0D4F}, {0x0D54, 0x0D63}, {0x0D66, 0x0D7F},
|
||||
{0x0D81, 0x0D83}, {0x0D85, 0x0D96}, {0x0D9A, 0x0DB1},
|
||||
{0x0DB3, 0x0DBB}, {0x0DBD, 0x0DBD}, {0x0DC0, 0x0DC6},
|
||||
{0x0DCA, 0x0DCA}, {0x0DCF, 0x0DD4}, {0x0DD6, 0x0DD6},
|
||||
{0x0DD8, 0x0DDF}, {0x0DE6, 0x0DEF}, {0x0DF2, 0x0DF4},
|
||||
{0x0E01, 0x0E3A}, {0x0E3F, 0x0E5B}, {0x0E81, 0x0E82},
|
||||
{0x0E84, 0x0E84}, {0x0E86, 0x0E8A}, {0x0E8C, 0x0EA3},
|
||||
{0x0EA5, 0x0EA5}, {0x0EA7, 0x0EBD}, {0x0EC0, 0x0EC4},
|
||||
{0x0EC6, 0x0EC6}, {0x0EC8, 0x0ECD}, {0x0ED0, 0x0ED9},
|
||||
{0x0EDC, 0x0EDF}, {0x0F00, 0x0F47}, {0x0F49, 0x0F6C},
|
||||
{0x0F71, 0x0F97}, {0x0F99, 0x0FBC}, {0x0FBE, 0x0FCC},
|
||||
{0x0FCE, 0x0FDA}, {0x1000, 0x10C5}, {0x10C7, 0x10C7},
|
||||
{0x10CD, 0x10CD}, {0x10D0, 0x10FF}, {0x1160, 0x1248},
|
||||
{0x124A, 0x124D}, {0x1250, 0x1256}, {0x1258, 0x1258},
|
||||
{0x125A, 0x125D}, {0x1260, 0x1288}, {0x128A, 0x128D},
|
||||
{0x1290, 0x12B0}, {0x12B2, 0x12B5}, {0x12B8, 0x12BE},
|
||||
{0x12C0, 0x12C0}, {0x12C2, 0x12C5}, {0x12C8, 0x12D6},
|
||||
{0x12D8, 0x1310}, {0x1312, 0x1315}, {0x1318, 0x135A},
|
||||
{0x135D, 0x137C}, {0x1380, 0x1399}, {0x13A0, 0x13F5},
|
||||
{0x13F8, 0x13FD}, {0x1400, 0x169C}, {0x16A0, 0x16F8},
|
||||
{0x1700, 0x170C}, {0x170E, 0x1714}, {0x1720, 0x1736},
|
||||
{0x1740, 0x1753}, {0x1760, 0x176C}, {0x176E, 0x1770},
|
||||
{0x1772, 0x1773}, {0x1780, 0x17DD}, {0x17E0, 0x17E9},
|
||||
{0x17F0, 0x17F9}, {0x1800, 0x180E}, {0x1810, 0x1819},
|
||||
{0x1820, 0x1878}, {0x1880, 0x18AA}, {0x18B0, 0x18F5},
|
||||
{0x1900, 0x191E}, {0x1920, 0x192B}, {0x1930, 0x193B},
|
||||
{0x1940, 0x1940}, {0x1944, 0x196D}, {0x1970, 0x1974},
|
||||
{0x1980, 0x19AB}, {0x19B0, 0x19C9}, {0x19D0, 0x19DA},
|
||||
{0x19DE, 0x1A1B}, {0x1A1E, 0x1A5E}, {0x1A60, 0x1A7C},
|
||||
{0x1A7F, 0x1A89}, {0x1A90, 0x1A99}, {0x1AA0, 0x1AAD},
|
||||
{0x1AB0, 0x1AC0}, {0x1B00, 0x1B4B}, {0x1B50, 0x1B7C},
|
||||
{0x1B80, 0x1BF3}, {0x1BFC, 0x1C37}, {0x1C3B, 0x1C49},
|
||||
{0x1C4D, 0x1C88}, {0x1C90, 0x1CBA}, {0x1CBD, 0x1CC7},
|
||||
{0x1CD0, 0x1CFA}, {0x1D00, 0x1DF9}, {0x1DFB, 0x1F15},
|
||||
{0x1F18, 0x1F1D}, {0x1F20, 0x1F45}, {0x1F48, 0x1F4D},
|
||||
{0x1F50, 0x1F57}, {0x1F59, 0x1F59}, {0x1F5B, 0x1F5B},
|
||||
{0x1F5D, 0x1F5D}, {0x1F5F, 0x1F7D}, {0x1F80, 0x1FB4},
|
||||
{0x1FB6, 0x1FC4}, {0x1FC6, 0x1FD3}, {0x1FD6, 0x1FDB},
|
||||
{0x1FDD, 0x1FEF}, {0x1FF2, 0x1FF4}, {0x1FF6, 0x1FFE},
|
||||
{0x2000, 0x200F}, {0x2011, 0x2012}, {0x2017, 0x2017},
|
||||
{0x201A, 0x201B}, {0x201E, 0x201F}, {0x2023, 0x2023},
|
||||
{0x2028, 0x202F}, {0x2031, 0x2031}, {0x2034, 0x2034},
|
||||
{0x2036, 0x203A}, {0x203C, 0x203D}, {0x203F, 0x2064},
|
||||
{0x2066, 0x2071}, {0x2075, 0x207E}, {0x2080, 0x2080},
|
||||
{0x2085, 0x208E}, {0x2090, 0x209C}, {0x20A0, 0x20A8},
|
||||
{0x20AA, 0x20AB}, {0x20AD, 0x20BF}, {0x20D0, 0x20F0},
|
||||
{0x2100, 0x2102}, {0x2104, 0x2104}, {0x2106, 0x2108},
|
||||
{0x210A, 0x2112}, {0x2114, 0x2115}, {0x2117, 0x2120},
|
||||
{0x2123, 0x2125}, {0x2127, 0x212A}, {0x212C, 0x2152},
|
||||
{0x2155, 0x215A}, {0x215F, 0x215F}, {0x216C, 0x216F},
|
||||
{0x217A, 0x2188}, {0x218A, 0x218B}, {0x219A, 0x21B7},
|
||||
{0x21BA, 0x21D1}, {0x21D3, 0x21D3}, {0x21D5, 0x21E6},
|
||||
{0x21E8, 0x21FF}, {0x2201, 0x2201}, {0x2204, 0x2206},
|
||||
{0x2209, 0x220A}, {0x220C, 0x220E}, {0x2210, 0x2210},
|
||||
{0x2212, 0x2214}, {0x2216, 0x2219}, {0x221B, 0x221C},
|
||||
{0x2221, 0x2222}, {0x2224, 0x2224}, {0x2226, 0x2226},
|
||||
{0x222D, 0x222D}, {0x222F, 0x2233}, {0x2238, 0x223B},
|
||||
{0x223E, 0x2247}, {0x2249, 0x224B}, {0x224D, 0x2251},
|
||||
{0x2253, 0x225F}, {0x2262, 0x2263}, {0x2268, 0x2269},
|
||||
{0x226C, 0x226D}, {0x2270, 0x2281}, {0x2284, 0x2285},
|
||||
{0x2288, 0x2294}, {0x2296, 0x2298}, {0x229A, 0x22A4},
|
||||
{0x22A6, 0x22BE}, {0x22C0, 0x2311}, {0x2313, 0x2319},
|
||||
{0x231C, 0x2328}, {0x232B, 0x23E8}, {0x23ED, 0x23EF},
|
||||
{0x23F1, 0x23F2}, {0x23F4, 0x2426}, {0x2440, 0x244A},
|
||||
{0x24EA, 0x24EA}, {0x254C, 0x254F}, {0x2574, 0x257F},
|
||||
{0x2590, 0x2591}, {0x2596, 0x259F}, {0x25A2, 0x25A2},
|
||||
{0x25AA, 0x25B1}, {0x25B4, 0x25B5}, {0x25B8, 0x25BB},
|
||||
{0x25BE, 0x25BF}, {0x25C2, 0x25C5}, {0x25C9, 0x25CA},
|
||||
{0x25CC, 0x25CD}, {0x25D2, 0x25E1}, {0x25E6, 0x25EE},
|
||||
{0x25F0, 0x25FC}, {0x25FF, 0x2604}, {0x2607, 0x2608},
|
||||
{0x260A, 0x260D}, {0x2610, 0x2613}, {0x2616, 0x261B},
|
||||
{0x261D, 0x261D}, {0x261F, 0x263F}, {0x2641, 0x2641},
|
||||
{0x2643, 0x2647}, {0x2654, 0x265F}, {0x2662, 0x2662},
|
||||
{0x2666, 0x2666}, {0x266B, 0x266B}, {0x266E, 0x266E},
|
||||
{0x2670, 0x267E}, {0x2680, 0x2692}, {0x2694, 0x269D},
|
||||
{0x26A0, 0x26A0}, {0x26A2, 0x26A9}, {0x26AC, 0x26BC},
|
||||
{0x26C0, 0x26C3}, {0x26E2, 0x26E2}, {0x26E4, 0x26E7},
|
||||
{0x2700, 0x2704}, {0x2706, 0x2709}, {0x270C, 0x2727},
|
||||
{0x2729, 0x273C}, {0x273E, 0x274B}, {0x274D, 0x274D},
|
||||
{0x274F, 0x2752}, {0x2756, 0x2756}, {0x2758, 0x2775},
|
||||
{0x2780, 0x2794}, {0x2798, 0x27AF}, {0x27B1, 0x27BE},
|
||||
{0x27C0, 0x27E5}, {0x27EE, 0x2984}, {0x2987, 0x2B1A},
|
||||
{0x2B1D, 0x2B4F}, {0x2B51, 0x2B54}, {0x2B5A, 0x2B73},
|
||||
{0x2B76, 0x2B95}, {0x2B97, 0x2C2E}, {0x2C30, 0x2C5E},
|
||||
{0x2C60, 0x2CF3}, {0x2CF9, 0x2D25}, {0x2D27, 0x2D27},
|
||||
{0x2D2D, 0x2D2D}, {0x2D30, 0x2D67}, {0x2D6F, 0x2D70},
|
||||
{0x2D7F, 0x2D96}, {0x2DA0, 0x2DA6}, {0x2DA8, 0x2DAE},
|
||||
{0x2DB0, 0x2DB6}, {0x2DB8, 0x2DBE}, {0x2DC0, 0x2DC6},
|
||||
{0x2DC8, 0x2DCE}, {0x2DD0, 0x2DD6}, {0x2DD8, 0x2DDE},
|
||||
{0x2DE0, 0x2E52}, {0x303F, 0x303F}, {0x4DC0, 0x4DFF},
|
||||
{0xA4D0, 0xA62B}, {0xA640, 0xA6F7}, {0xA700, 0xA7BF},
|
||||
{0xA7C2, 0xA7CA}, {0xA7F5, 0xA82C}, {0xA830, 0xA839},
|
||||
{0xA840, 0xA877}, {0xA880, 0xA8C5}, {0xA8CE, 0xA8D9},
|
||||
{0xA8E0, 0xA953}, {0xA95F, 0xA95F}, {0xA980, 0xA9CD},
|
||||
{0xA9CF, 0xA9D9}, {0xA9DE, 0xA9FE}, {0xAA00, 0xAA36},
|
||||
{0xAA40, 0xAA4D}, {0xAA50, 0xAA59}, {0xAA5C, 0xAAC2},
|
||||
{0xAADB, 0xAAF6}, {0xAB01, 0xAB06}, {0xAB09, 0xAB0E},
|
||||
{0xAB11, 0xAB16}, {0xAB20, 0xAB26}, {0xAB28, 0xAB2E},
|
||||
{0xAB30, 0xAB6B}, {0xAB70, 0xABED}, {0xABF0, 0xABF9},
|
||||
{0xD7B0, 0xD7C6}, {0xD7CB, 0xD7FB}, {0xD800, 0xDFFF},
|
||||
{0xFB00, 0xFB06}, {0xFB13, 0xFB17}, {0xFB1D, 0xFB36},
|
||||
{0xFB38, 0xFB3C}, {0xFB3E, 0xFB3E}, {0xFB40, 0xFB41},
|
||||
{0xFB43, 0xFB44}, {0xFB46, 0xFBC1}, {0xFBD3, 0xFD3F},
|
||||
{0xFD50, 0xFD8F}, {0xFD92, 0xFDC7}, {0xFDF0, 0xFDFD},
|
||||
{0xFE20, 0xFE2F}, {0xFE70, 0xFE74}, {0xFE76, 0xFEFC},
|
||||
{0xFEFF, 0xFEFF}, {0xFFF9, 0xFFFC}, {0x10000, 0x1000B},
|
||||
{0x1000D, 0x10026}, {0x10028, 0x1003A}, {0x1003C, 0x1003D},
|
||||
{0x1003F, 0x1004D}, {0x10050, 0x1005D}, {0x10080, 0x100FA},
|
||||
{0x10100, 0x10102}, {0x10107, 0x10133}, {0x10137, 0x1018E},
|
||||
{0x10190, 0x1019C}, {0x101A0, 0x101A0}, {0x101D0, 0x101FD},
|
||||
{0x10280, 0x1029C}, {0x102A0, 0x102D0}, {0x102E0, 0x102FB},
|
||||
{0x10300, 0x10323}, {0x1032D, 0x1034A}, {0x10350, 0x1037A},
|
||||
{0x10380, 0x1039D}, {0x1039F, 0x103C3}, {0x103C8, 0x103D5},
|
||||
{0x10400, 0x1049D}, {0x104A0, 0x104A9}, {0x104B0, 0x104D3},
|
||||
{0x104D8, 0x104FB}, {0x10500, 0x10527}, {0x10530, 0x10563},
|
||||
{0x1056F, 0x1056F}, {0x10600, 0x10736}, {0x10740, 0x10755},
|
||||
{0x10760, 0x10767}, {0x10800, 0x10805}, {0x10808, 0x10808},
|
||||
{0x1080A, 0x10835}, {0x10837, 0x10838}, {0x1083C, 0x1083C},
|
||||
{0x1083F, 0x10855}, {0x10857, 0x1089E}, {0x108A7, 0x108AF},
|
||||
{0x108E0, 0x108F2}, {0x108F4, 0x108F5}, {0x108FB, 0x1091B},
|
||||
{0x1091F, 0x10939}, {0x1093F, 0x1093F}, {0x10980, 0x109B7},
|
||||
{0x109BC, 0x109CF}, {0x109D2, 0x10A03}, {0x10A05, 0x10A06},
|
||||
{0x10A0C, 0x10A13}, {0x10A15, 0x10A17}, {0x10A19, 0x10A35},
|
||||
{0x10A38, 0x10A3A}, {0x10A3F, 0x10A48}, {0x10A50, 0x10A58},
|
||||
{0x10A60, 0x10A9F}, {0x10AC0, 0x10AE6}, {0x10AEB, 0x10AF6},
|
||||
{0x10B00, 0x10B35}, {0x10B39, 0x10B55}, {0x10B58, 0x10B72},
|
||||
{0x10B78, 0x10B91}, {0x10B99, 0x10B9C}, {0x10BA9, 0x10BAF},
|
||||
{0x10C00, 0x10C48}, {0x10C80, 0x10CB2}, {0x10CC0, 0x10CF2},
|
||||
{0x10CFA, 0x10D27}, {0x10D30, 0x10D39}, {0x10E60, 0x10E7E},
|
||||
{0x10E80, 0x10EA9}, {0x10EAB, 0x10EAD}, {0x10EB0, 0x10EB1},
|
||||
{0x10F00, 0x10F27}, {0x10F30, 0x10F59}, {0x10FB0, 0x10FCB},
|
||||
{0x10FE0, 0x10FF6}, {0x11000, 0x1104D}, {0x11052, 0x1106F},
|
||||
{0x1107F, 0x110C1}, {0x110CD, 0x110CD}, {0x110D0, 0x110E8},
|
||||
{0x110F0, 0x110F9}, {0x11100, 0x11134}, {0x11136, 0x11147},
|
||||
{0x11150, 0x11176}, {0x11180, 0x111DF}, {0x111E1, 0x111F4},
|
||||
{0x11200, 0x11211}, {0x11213, 0x1123E}, {0x11280, 0x11286},
|
||||
{0x11288, 0x11288}, {0x1128A, 0x1128D}, {0x1128F, 0x1129D},
|
||||
{0x1129F, 0x112A9}, {0x112B0, 0x112EA}, {0x112F0, 0x112F9},
|
||||
{0x11300, 0x11303}, {0x11305, 0x1130C}, {0x1130F, 0x11310},
|
||||
{0x11313, 0x11328}, {0x1132A, 0x11330}, {0x11332, 0x11333},
|
||||
{0x11335, 0x11339}, {0x1133B, 0x11344}, {0x11347, 0x11348},
|
||||
{0x1134B, 0x1134D}, {0x11350, 0x11350}, {0x11357, 0x11357},
|
||||
{0x1135D, 0x11363}, {0x11366, 0x1136C}, {0x11370, 0x11374},
|
||||
{0x11400, 0x1145B}, {0x1145D, 0x11461}, {0x11480, 0x114C7},
|
||||
{0x114D0, 0x114D9}, {0x11580, 0x115B5}, {0x115B8, 0x115DD},
|
||||
{0x11600, 0x11644}, {0x11650, 0x11659}, {0x11660, 0x1166C},
|
||||
{0x11680, 0x116B8}, {0x116C0, 0x116C9}, {0x11700, 0x1171A},
|
||||
{0x1171D, 0x1172B}, {0x11730, 0x1173F}, {0x11800, 0x1183B},
|
||||
{0x118A0, 0x118F2}, {0x118FF, 0x11906}, {0x11909, 0x11909},
|
||||
{0x1190C, 0x11913}, {0x11915, 0x11916}, {0x11918, 0x11935},
|
||||
{0x11937, 0x11938}, {0x1193B, 0x11946}, {0x11950, 0x11959},
|
||||
{0x119A0, 0x119A7}, {0x119AA, 0x119D7}, {0x119DA, 0x119E4},
|
||||
{0x11A00, 0x11A47}, {0x11A50, 0x11AA2}, {0x11AC0, 0x11AF8},
|
||||
{0x11C00, 0x11C08}, {0x11C0A, 0x11C36}, {0x11C38, 0x11C45},
|
||||
{0x11C50, 0x11C6C}, {0x11C70, 0x11C8F}, {0x11C92, 0x11CA7},
|
||||
{0x11CA9, 0x11CB6}, {0x11D00, 0x11D06}, {0x11D08, 0x11D09},
|
||||
{0x11D0B, 0x11D36}, {0x11D3A, 0x11D3A}, {0x11D3C, 0x11D3D},
|
||||
{0x11D3F, 0x11D47}, {0x11D50, 0x11D59}, {0x11D60, 0x11D65},
|
||||
{0x11D67, 0x11D68}, {0x11D6A, 0x11D8E}, {0x11D90, 0x11D91},
|
||||
{0x11D93, 0x11D98}, {0x11DA0, 0x11DA9}, {0x11EE0, 0x11EF8},
|
||||
{0x11FB0, 0x11FB0}, {0x11FC0, 0x11FF1}, {0x11FFF, 0x12399},
|
||||
{0x12400, 0x1246E}, {0x12470, 0x12474}, {0x12480, 0x12543},
|
||||
{0x13000, 0x1342E}, {0x13430, 0x13438}, {0x14400, 0x14646},
|
||||
{0x16800, 0x16A38}, {0x16A40, 0x16A5E}, {0x16A60, 0x16A69},
|
||||
{0x16A6E, 0x16A6F}, {0x16AD0, 0x16AED}, {0x16AF0, 0x16AF5},
|
||||
{0x16B00, 0x16B45}, {0x16B50, 0x16B59}, {0x16B5B, 0x16B61},
|
||||
{0x16B63, 0x16B77}, {0x16B7D, 0x16B8F}, {0x16E40, 0x16E9A},
|
||||
{0x16F00, 0x16F4A}, {0x16F4F, 0x16F87}, {0x16F8F, 0x16F9F},
|
||||
{0x1BC00, 0x1BC6A}, {0x1BC70, 0x1BC7C}, {0x1BC80, 0x1BC88},
|
||||
{0x1BC90, 0x1BC99}, {0x1BC9C, 0x1BCA3}, {0x1D000, 0x1D0F5},
|
||||
{0x1D100, 0x1D126}, {0x1D129, 0x1D1E8}, {0x1D200, 0x1D245},
|
||||
{0x1D2E0, 0x1D2F3}, {0x1D300, 0x1D356}, {0x1D360, 0x1D378},
|
||||
{0x1D400, 0x1D454}, {0x1D456, 0x1D49C}, {0x1D49E, 0x1D49F},
|
||||
{0x1D4A2, 0x1D4A2}, {0x1D4A5, 0x1D4A6}, {0x1D4A9, 0x1D4AC},
|
||||
{0x1D4AE, 0x1D4B9}, {0x1D4BB, 0x1D4BB}, {0x1D4BD, 0x1D4C3},
|
||||
{0x1D4C5, 0x1D505}, {0x1D507, 0x1D50A}, {0x1D50D, 0x1D514},
|
||||
{0x1D516, 0x1D51C}, {0x1D51E, 0x1D539}, {0x1D53B, 0x1D53E},
|
||||
{0x1D540, 0x1D544}, {0x1D546, 0x1D546}, {0x1D54A, 0x1D550},
|
||||
{0x1D552, 0x1D6A5}, {0x1D6A8, 0x1D7CB}, {0x1D7CE, 0x1DA8B},
|
||||
{0x1DA9B, 0x1DA9F}, {0x1DAA1, 0x1DAAF}, {0x1E000, 0x1E006},
|
||||
{0x1E008, 0x1E018}, {0x1E01B, 0x1E021}, {0x1E023, 0x1E024},
|
||||
{0x1E026, 0x1E02A}, {0x1E100, 0x1E12C}, {0x1E130, 0x1E13D},
|
||||
{0x1E140, 0x1E149}, {0x1E14E, 0x1E14F}, {0x1E2C0, 0x1E2F9},
|
||||
{0x1E2FF, 0x1E2FF}, {0x1E800, 0x1E8C4}, {0x1E8C7, 0x1E8D6},
|
||||
{0x1E900, 0x1E94B}, {0x1E950, 0x1E959}, {0x1E95E, 0x1E95F},
|
||||
{0x1EC71, 0x1ECB4}, {0x1ED01, 0x1ED3D}, {0x1EE00, 0x1EE03},
|
||||
{0x1EE05, 0x1EE1F}, {0x1EE21, 0x1EE22}, {0x1EE24, 0x1EE24},
|
||||
{0x1EE27, 0x1EE27}, {0x1EE29, 0x1EE32}, {0x1EE34, 0x1EE37},
|
||||
{0x1EE39, 0x1EE39}, {0x1EE3B, 0x1EE3B}, {0x1EE42, 0x1EE42},
|
||||
{0x1EE47, 0x1EE47}, {0x1EE49, 0x1EE49}, {0x1EE4B, 0x1EE4B},
|
||||
{0x1EE4D, 0x1EE4F}, {0x1EE51, 0x1EE52}, {0x1EE54, 0x1EE54},
|
||||
{0x1EE57, 0x1EE57}, {0x1EE59, 0x1EE59}, {0x1EE5B, 0x1EE5B},
|
||||
{0x1EE5D, 0x1EE5D}, {0x1EE5F, 0x1EE5F}, {0x1EE61, 0x1EE62},
|
||||
{0x1EE64, 0x1EE64}, {0x1EE67, 0x1EE6A}, {0x1EE6C, 0x1EE72},
|
||||
{0x1EE74, 0x1EE77}, {0x1EE79, 0x1EE7C}, {0x1EE7E, 0x1EE7E},
|
||||
{0x1EE80, 0x1EE89}, {0x1EE8B, 0x1EE9B}, {0x1EEA1, 0x1EEA3},
|
||||
{0x1EEA5, 0x1EEA9}, {0x1EEAB, 0x1EEBB}, {0x1EEF0, 0x1EEF1},
|
||||
{0x1F000, 0x1F003}, {0x1F005, 0x1F02B}, {0x1F030, 0x1F093},
|
||||
{0x1F0A0, 0x1F0AE}, {0x1F0B1, 0x1F0BF}, {0x1F0C1, 0x1F0CE},
|
||||
{0x1F0D1, 0x1F0F5}, {0x1F10B, 0x1F10F}, {0x1F12E, 0x1F12F},
|
||||
{0x1F16A, 0x1F16F}, {0x1F1AD, 0x1F1AD}, {0x1F1E6, 0x1F1FF},
|
||||
{0x1F321, 0x1F32C}, {0x1F336, 0x1F336}, {0x1F37D, 0x1F37D},
|
||||
{0x1F394, 0x1F39F}, {0x1F3CB, 0x1F3CE}, {0x1F3D4, 0x1F3DF},
|
||||
{0x1F3F1, 0x1F3F3}, {0x1F3F5, 0x1F3F7}, {0x1F43F, 0x1F43F},
|
||||
{0x1F441, 0x1F441}, {0x1F4FD, 0x1F4FE}, {0x1F53E, 0x1F54A},
|
||||
{0x1F54F, 0x1F54F}, {0x1F568, 0x1F579}, {0x1F57B, 0x1F594},
|
||||
{0x1F597, 0x1F5A3}, {0x1F5A5, 0x1F5FA}, {0x1F650, 0x1F67F},
|
||||
{0x1F6C6, 0x1F6CB}, {0x1F6CD, 0x1F6CF}, {0x1F6D3, 0x1F6D4},
|
||||
{0x1F6E0, 0x1F6EA}, {0x1F6F0, 0x1F6F3}, {0x1F700, 0x1F773},
|
||||
{0x1F780, 0x1F7D8}, {0x1F800, 0x1F80B}, {0x1F810, 0x1F847},
|
||||
{0x1F850, 0x1F859}, {0x1F860, 0x1F887}, {0x1F890, 0x1F8AD},
|
||||
{0x1F8B0, 0x1F8B1}, {0x1F900, 0x1F90B}, {0x1F93B, 0x1F93B},
|
||||
{0x1F946, 0x1F946}, {0x1FA00, 0x1FA53}, {0x1FA60, 0x1FA6D},
|
||||
{0x1FB00, 0x1FB92}, {0x1FB94, 0x1FBCA}, {0x1FBF0, 0x1FBF9},
|
||||
{0xE0001, 0xE0001}, {0xE0020, 0xE007F},
|
||||
}
|
||||
|
||||
var emoji = table{
|
||||
{0x203C, 0x203C}, {0x2049, 0x2049}, {0x2122, 0x2122},
|
||||
{0x2139, 0x2139}, {0x2194, 0x2199}, {0x21A9, 0x21AA},
|
||||
{0x231A, 0x231B}, {0x2328, 0x2328}, {0x2388, 0x2388},
|
||||
{0x23CF, 0x23CF}, {0x23E9, 0x23F3}, {0x23F8, 0x23FA},
|
||||
{0x24C2, 0x24C2}, {0x25AA, 0x25AB}, {0x25B6, 0x25B6},
|
||||
{0x25C0, 0x25C0}, {0x25FB, 0x25FE}, {0x2600, 0x2605},
|
||||
{0x2607, 0x2612}, {0x2614, 0x2685}, {0x2690, 0x2705},
|
||||
{0x2708, 0x2712}, {0x2714, 0x2714}, {0x2716, 0x2716},
|
||||
{0x271D, 0x271D}, {0x2721, 0x2721}, {0x2728, 0x2728},
|
||||
{0x2733, 0x2734}, {0x2744, 0x2744}, {0x2747, 0x2747},
|
||||
{0x274C, 0x274C}, {0x274E, 0x274E}, {0x2753, 0x2755},
|
||||
{0x2757, 0x2757}, {0x2763, 0x2767}, {0x2795, 0x2797},
|
||||
{0x27A1, 0x27A1}, {0x27B0, 0x27B0}, {0x27BF, 0x27BF},
|
||||
{0x2934, 0x2935}, {0x2B05, 0x2B07}, {0x2B1B, 0x2B1C},
|
||||
{0x2B50, 0x2B50}, {0x2B55, 0x2B55}, {0x3030, 0x3030},
|
||||
{0x303D, 0x303D}, {0x3297, 0x3297}, {0x3299, 0x3299},
|
||||
{0x1F000, 0x1F0FF}, {0x1F10D, 0x1F10F}, {0x1F12F, 0x1F12F},
|
||||
{0x1F16C, 0x1F171}, {0x1F17E, 0x1F17F}, {0x1F18E, 0x1F18E},
|
||||
{0x1F191, 0x1F19A}, {0x1F1AD, 0x1F1E5}, {0x1F201, 0x1F20F},
|
||||
{0x1F21A, 0x1F21A}, {0x1F22F, 0x1F22F}, {0x1F232, 0x1F23A},
|
||||
{0x1F23C, 0x1F23F}, {0x1F249, 0x1F3FA}, {0x1F400, 0x1F53D},
|
||||
{0x1F546, 0x1F64F}, {0x1F680, 0x1F6FF}, {0x1F774, 0x1F77F},
|
||||
{0x1F7D5, 0x1F7FF}, {0x1F80C, 0x1F80F}, {0x1F848, 0x1F84F},
|
||||
{0x1F85A, 0x1F85F}, {0x1F888, 0x1F88F}, {0x1F8AE, 0x1F8FF},
|
||||
{0x1F90C, 0x1F93A}, {0x1F93C, 0x1F945}, {0x1F947, 0x1FAFF},
|
||||
{0x1FC00, 0x1FFFD},
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
// +build windows
|
||||
// +build !appengine
|
||||
|
||||
package runewidth
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var (
|
||||
kernel32 = syscall.NewLazyDLL("kernel32")
|
||||
procGetConsoleOutputCP = kernel32.NewProc("GetConsoleOutputCP")
|
||||
)
|
||||
|
||||
// IsEastAsian return true if the current locale is CJK
|
||||
func IsEastAsian() bool {
|
||||
r1, _, _ := procGetConsoleOutputCP.Call()
|
||||
if r1 == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
switch int(r1) {
|
||||
case 932, 51932, 936, 949, 950:
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
language: go
|
||||
sudo: false
|
||||
go:
|
||||
- tip
|
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2018 Yasuhiro Matsumoto
|
||||
|
||||
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.
|
@ -0,0 +1,49 @@
|
||||
# go-tty
|
||||
|
||||
Simple tty utility
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
tty, err := tty.Open()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer tty.Close()
|
||||
|
||||
for {
|
||||
r, err := tty.ReadRune()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// handle key event
|
||||
}
|
||||
```
|
||||
|
||||
if you are on windows and want to display ANSI colors, use <a href="https://github.com/mattn/go-colorable">go-colorable</a>.
|
||||
|
||||
```go
|
||||
tty, err := tty.Open()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer tty.Close()
|
||||
|
||||
out := colorable.NewColorable(tty.Output())
|
||||
|
||||
fmt.Fprintln(out, "\x1b[2J")
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
$ go get github.com/mattn/go-tty
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
## Author
|
||||
|
||||
Yasuhiro Matsumoto (a.k.a mattn)
|
@ -0,0 +1,128 @@
|
||||
package tty
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
func Open() (*TTY, error) {
|
||||
return open("/dev/tty")
|
||||
}
|
||||
|
||||
func OpenDevice(path string) (*TTY, error) {
|
||||
return open(path)
|
||||
}
|
||||
|
||||
func (tty *TTY) Raw() (func() error, error) {
|
||||
return tty.raw()
|
||||
}
|
||||
|
||||
func (tty *TTY) MustRaw() func() error {
|
||||
f, err := tty.raw()
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func (tty *TTY) Buffered() bool {
|
||||
return tty.buffered()
|
||||
}
|
||||
|
||||
func (tty *TTY) ReadRune() (rune, error) {
|
||||
return tty.readRune()
|
||||
}
|
||||
|
||||
func (tty *TTY) Close() error {
|
||||
return tty.close()
|
||||
}
|
||||
|
||||
func (tty *TTY) Size() (int, int, error) {
|
||||
return tty.size()
|
||||
}
|
||||
|
||||
func (tty *TTY) SizePixel() (int, int, int, int, error) {
|
||||
return tty.sizePixel()
|
||||
}
|
||||
|
||||
func (tty *TTY) Input() *os.File {
|
||||
return tty.input()
|
||||
}
|
||||
|
||||
func (tty *TTY) Output() *os.File {
|
||||
return tty.output()
|
||||
}
|
||||
|
||||
// Display types.
|
||||
const (
|
||||
displayNone = iota
|
||||
displayRune
|
||||
displayMask
|
||||
)
|
||||
|
||||
func (tty *TTY) readString(displayType int) (string, error) {
|
||||
rs := []rune{}
|
||||
loop:
|
||||
for {
|
||||
r, err := tty.readRune()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
switch r {
|
||||
case 13:
|
||||
break loop
|
||||
case 8, 127:
|
||||
if len(rs) > 0 {
|
||||
rs = rs[:len(rs)-1]
|
||||
if displayType != displayNone {
|
||||
tty.Output().WriteString("\b \b")
|
||||
}
|
||||
}
|
||||
default:
|
||||
if unicode.IsPrint(r) {
|
||||
rs = append(rs, r)
|
||||
switch displayType {
|
||||
case displayRune:
|
||||
tty.Output().WriteString(string(r))
|
||||
case displayMask:
|
||||
tty.Output().WriteString("*")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return string(rs), nil
|
||||
}
|
||||
|
||||
func (tty *TTY) ReadString() (string, error) {
|
||||
defer tty.Output().WriteString("\n")
|
||||
return tty.readString(displayRune)
|
||||
}
|
||||
|
||||
func (tty *TTY) ReadPassword() (string, error) {
|
||||
defer tty.Output().WriteString("\n")
|
||||
return tty.readString(displayMask)
|
||||
}
|
||||
|
||||
func (tty *TTY) ReadPasswordNoEcho() (string, error) {
|
||||
defer tty.Output().WriteString("\n")
|
||||
return tty.readString(displayNone)
|
||||
}
|
||||
|
||||
func (tty *TTY) ReadPasswordClear() (string, error) {
|
||||
s, err := tty.readString(displayMask)
|
||||
tty.Output().WriteString(
|
||||
strings.Repeat("\b", len(s)) +
|
||||
strings.Repeat(" ", len(s)) +
|
||||
strings.Repeat("\b", len(s)))
|
||||
return s, err
|
||||
}
|
||||
|
||||
type WINSIZE struct {
|
||||
W int
|
||||
H int
|
||||
}
|
||||
|
||||
func (tty *TTY) SIGWINCH() <-chan WINSIZE {
|
||||
return tty.sigwinch()
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
// +build darwin dragonfly freebsd netbsd openbsd
|
||||
|
||||
package tty
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const (
|
||||
ioctlReadTermios = syscall.TIOCGETA
|
||||
ioctlWriteTermios = syscall.TIOCSETA
|
||||
)
|
@ -0,0 +1,8 @@
|
||||
// +build linux
|
||||
|
||||
package tty
|
||||
|
||||
const (
|
||||
ioctlReadTermios = 0x5401 // syscall.TCGETS
|
||||
ioctlWriteTermios = 0x5402 // syscall.TCSETS
|
||||
)
|
@ -0,0 +1,69 @@
|
||||
package tty
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type TTY struct {
|
||||
in *os.File
|
||||
bin *bufio.Reader
|
||||
out *os.File
|
||||
}
|
||||
|
||||
func open(path string) (*TTY, error) {
|
||||
tty := new(TTY)
|
||||
|
||||
in, err := os.Open("/dev/cons")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tty.in = in
|
||||
tty.bin = bufio.NewReader(in)
|
||||
|
||||
out, err := os.OpenFile("/dev/cons", syscall.O_WRONLY, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tty.out = out
|
||||
|
||||
return tty, nil
|
||||
}
|
||||
|
||||
func (tty *TTY) buffered() bool {
|
||||
return tty.bin.Buffered() > 0
|
||||
}
|
||||
|
||||
func (tty *TTY) readRune() (rune, error) {
|
||||
r, _, err := tty.bin.ReadRune()
|
||||
return r, err
|
||||
}
|
||||
|
||||
func (tty *TTY) close() (err error) {
|
||||
if err2 := tty.in.Close(); err2 != nil {
|
||||
err = err2
|
||||
}
|
||||
if err2 := tty.out.Close(); err2 != nil {
|
||||
err = err2
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (tty *TTY) size() (int, int, error) {
|
||||
return 80, 24, nil
|
||||
}
|
||||
|
||||
func (tty *TTY) sizePixel() (int, int, int, int, error) {
|
||||
x, y, _ := tty.size()
|
||||
return x, y, -1, -1, errors.New("no implemented method for querying size in pixels on Plan 9")
|
||||
}
|
||||
|
||||
func (tty *TTY) input() *os.File {
|
||||
return tty.in
|
||||
}
|
||||
|
||||
func (tty *TTY) output() *os.File {
|
||||
return tty.out
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
// +build solaris
|
||||
|
||||
package tty
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
ioctlReadTermios = unix.TCGETA
|
||||
ioctlWriteTermios = unix.TCSETA
|
||||
)
|
@ -0,0 +1,142 @@
|
||||
// +build !windows
|
||||
// +build !plan9
|
||||
|
||||
package tty
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type TTY struct {
|
||||
in *os.File
|
||||
bin *bufio.Reader
|
||||
out *os.File
|
||||
termios syscall.Termios
|
||||
ss chan os.Signal
|
||||
}
|
||||
|
||||
func open(path string) (*TTY, error) {
|
||||
tty := new(TTY)
|
||||
|
||||
in, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tty.in = in
|
||||
tty.bin = bufio.NewReader(in)
|
||||
|
||||
out, err := os.OpenFile(path, syscall.O_WRONLY, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tty.out = out
|
||||
|
||||
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(tty.in.Fd()), ioctlReadTermios, uintptr(unsafe.Pointer(&tty.termios))); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
newios := tty.termios
|
||||
newios.Iflag &^= syscall.ISTRIP | syscall.INLCR | syscall.ICRNL | syscall.IGNCR | syscall.IXOFF
|
||||
newios.Lflag &^= syscall.ECHO | syscall.ICANON /*| syscall.ISIG*/
|
||||
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(tty.in.Fd()), ioctlWriteTermios, uintptr(unsafe.Pointer(&newios))); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tty.ss = make(chan os.Signal, 1)
|
||||
|
||||
return tty, nil
|
||||
}
|
||||
|
||||
func (tty *TTY) buffered() bool {
|
||||
return tty.bin.Buffered() > 0
|
||||
}
|
||||
|
||||
func (tty *TTY) readRune() (rune, error) {
|
||||
r, _, err := tty.bin.ReadRune()
|
||||
return r, err
|
||||
}
|
||||
|
||||
func (tty *TTY) close() error {
|
||||
signal.Stop(tty.ss)
|
||||
close(tty.ss)
|
||||
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(tty.in.Fd()), ioctlWriteTermios, uintptr(unsafe.Pointer(&tty.termios)))
|
||||
return err
|
||||
}
|
||||
|
||||
func (tty *TTY) size() (int, int, error) {
|
||||
x, y, _, _, err := tty.sizePixel()
|
||||
return x, y, err
|
||||
}
|
||||
|
||||
func (tty *TTY) sizePixel() (int, int, int, int, error) {
|
||||
var dim [4]uint16
|
||||
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(tty.out.Fd()), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&dim))); err != 0 {
|
||||
return -1, -1, -1, -1, err
|
||||
}
|
||||
return int(dim[1]), int(dim[0]), int(dim[2]), int(dim[3]), nil
|
||||
}
|
||||
|
||||
func (tty *TTY) input() *os.File {
|
||||
return tty.in
|
||||
}
|
||||
|
||||
func (tty *TTY) output() *os.File {
|
||||
return tty.out
|
||||
}
|
||||
|
||||
func (tty *TTY) raw() (func() error, error) {
|
||||
termios, err := unix.IoctlGetTermios(int(tty.in.Fd()), ioctlReadTermios)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
backup := *termios
|
||||
|
||||
termios.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON
|
||||
termios.Oflag &^= unix.OPOST
|
||||
termios.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN
|
||||
termios.Cflag &^= unix.CSIZE | unix.PARENB
|
||||
termios.Cflag |= unix.CS8
|
||||
termios.Cc[unix.VMIN] = 1
|
||||
termios.Cc[unix.VTIME] = 0
|
||||
if err := unix.IoctlSetTermios(int(tty.in.Fd()), ioctlWriteTermios, termios); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return func() error {
|
||||
if err := unix.IoctlSetTermios(int(tty.in.Fd()), ioctlWriteTermios, &backup); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (tty *TTY) sigwinch() <-chan WINSIZE {
|
||||
signal.Notify(tty.ss, syscall.SIGWINCH)
|
||||
|
||||
ws := make(chan WINSIZE)
|
||||
go func() {
|
||||
defer close(ws)
|
||||
for sig := range tty.ss {
|
||||
if sig != syscall.SIGWINCH {
|
||||
continue
|
||||
}
|
||||
|
||||
w, h, err := tty.size()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
// send but do not block for it
|
||||
select {
|
||||
case ws <- WINSIZE{W: w, H: h}:
|
||||
default:
|
||||
}
|
||||
|
||||
}
|
||||
}()
|
||||
return ws
|
||||
}
|
@ -0,0 +1,383 @@
|
||||
// +build windows
|
||||
|
||||
package tty
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
const (
|
||||
rightAltPressed = 1
|
||||
leftAltPressed = 2
|
||||
rightCtrlPressed = 4
|
||||
leftCtrlPressed = 8
|
||||
shiftPressed = 0x0010
|
||||
ctrlPressed = rightCtrlPressed | leftCtrlPressed
|
||||
altPressed = rightAltPressed | leftAltPressed
|
||||
)
|
||||
|
||||
const (
|
||||
enableProcessedInput = 0x1
|
||||
enableLineInput = 0x2
|
||||
enableEchoInput = 0x4
|
||||
enableWindowInput = 0x8
|
||||
enableMouseInput = 0x10
|
||||
enableInsertMode = 0x20
|
||||
enableQuickEditMode = 0x40
|
||||
enableExtendedFlag = 0x80
|
||||
|
||||
enableProcessedOutput = 1
|
||||
enableWrapAtEolOutput = 2
|
||||
|
||||
keyEvent = 0x1
|
||||
mouseEvent = 0x2
|
||||
windowBufferSizeEvent = 0x4
|
||||
)
|
||||
|
||||
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
|
||||
var (
|
||||
procAllocConsole = kernel32.NewProc("AllocConsole")
|
||||
procSetStdHandle = kernel32.NewProc("SetStdHandle")
|
||||
procGetStdHandle = kernel32.NewProc("GetStdHandle")
|
||||
procSetConsoleScreenBufferSize = kernel32.NewProc("SetConsoleScreenBufferSize")
|
||||
procCreateConsoleScreenBuffer = kernel32.NewProc("CreateConsoleScreenBuffer")
|
||||
procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
|
||||
procWriteConsoleOutputCharacter = kernel32.NewProc("WriteConsoleOutputCharacterW")
|
||||
procWriteConsoleOutputAttribute = kernel32.NewProc("WriteConsoleOutputAttribute")
|
||||
procGetConsoleCursorInfo = kernel32.NewProc("GetConsoleCursorInfo")
|
||||
procSetConsoleCursorInfo = kernel32.NewProc("SetConsoleCursorInfo")
|
||||
procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition")
|
||||
procReadConsoleInput = kernel32.NewProc("ReadConsoleInputW")
|
||||
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
||||
procSetConsoleMode = kernel32.NewProc("SetConsoleMode")
|
||||
procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW")
|
||||
procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute")
|
||||
procScrollConsoleScreenBuffer = kernel32.NewProc("ScrollConsoleScreenBufferW")
|
||||
)
|
||||
|
||||
type wchar uint16
|
||||
type short int16
|
||||
type dword uint32
|
||||
type word uint16
|
||||
|
||||
type coord struct {
|
||||
x short
|
||||
y short
|
||||
}
|
||||
|
||||
type smallRect struct {
|
||||
left short
|
||||
top short
|
||||
right short
|
||||
bottom short
|
||||
}
|
||||
|
||||
type consoleScreenBufferInfo struct {
|
||||
size coord
|
||||
cursorPosition coord
|
||||
attributes word
|
||||
window smallRect
|
||||
maximumWindowSize coord
|
||||
}
|
||||
|
||||
type consoleCursorInfo struct {
|
||||
size dword
|
||||
visible int32
|
||||
}
|
||||
|
||||
type inputRecord struct {
|
||||
eventType word
|
||||
_ [2]byte
|
||||
event [16]byte
|
||||
}
|
||||
|
||||
type keyEventRecord struct {
|
||||
keyDown int32
|
||||
repeatCount word
|
||||
virtualKeyCode word
|
||||
virtualScanCode word
|
||||
unicodeChar wchar
|
||||
controlKeyState dword
|
||||
}
|
||||
|
||||
type windowBufferSizeRecord struct {
|
||||
size coord
|
||||
}
|
||||
|
||||
type mouseEventRecord struct {
|
||||
mousePos coord
|
||||
buttonState dword
|
||||
controlKeyState dword
|
||||
eventFlags dword
|
||||
}
|
||||
|
||||
type charInfo struct {
|
||||
unicodeChar wchar
|
||||
attributes word
|
||||
}
|
||||
|
||||
type TTY struct {
|
||||
in *os.File
|
||||
out *os.File
|
||||
st uint32
|
||||
rs []rune
|
||||
ws chan WINSIZE
|
||||
sigwinchCtx context.Context
|
||||
sigwinchCtxCancel context.CancelFunc
|
||||
}
|
||||
|
||||
func readConsoleInput(fd uintptr, record *inputRecord) (err error) {
|
||||
var w uint32
|
||||
r1, _, err := procReadConsoleInput.Call(fd, uintptr(unsafe.Pointer(record)), 1, uintptr(unsafe.Pointer(&w)))
|
||||
if r1 == 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func open(path string) (*TTY, error) {
|
||||
tty := new(TTY)
|
||||
if false && isatty.IsTerminal(os.Stdin.Fd()) {
|
||||
tty.in = os.Stdin
|
||||
} else {
|
||||
in, err := syscall.Open("CONIN$", syscall.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tty.in = os.NewFile(uintptr(in), "/dev/tty")
|
||||
}
|
||||
|
||||
if isatty.IsTerminal(os.Stdout.Fd()) {
|
||||
tty.out = os.Stdout
|
||||
} else {
|
||||
procAllocConsole.Call()
|
||||
out, err := syscall.Open("CONOUT$", syscall.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tty.out = os.NewFile(uintptr(out), "/dev/tty")
|
||||
}
|
||||
|
||||
h := tty.in.Fd()
|
||||
var st uint32
|
||||
r1, _, err := procGetConsoleMode.Call(h, uintptr(unsafe.Pointer(&st)))
|
||||
if r1 == 0 {
|
||||
return nil, err
|
||||
}
|
||||
tty.st = st
|
||||
|
||||
st &^= enableEchoInput
|
||||
st &^= enableInsertMode
|
||||
st &^= enableLineInput
|
||||
st &^= enableMouseInput
|
||||
st &^= enableWindowInput
|
||||
st &^= enableExtendedFlag
|
||||
st &^= enableQuickEditMode
|
||||
|
||||
// ignore error
|
||||
procSetConsoleMode.Call(h, uintptr(st))
|
||||
|
||||
tty.ws = make(chan WINSIZE)
|
||||
tty.sigwinchCtx, tty.sigwinchCtxCancel = context.WithCancel(context.Background())
|
||||
|
||||
return tty, nil
|
||||
}
|
||||
|
||||
func (tty *TTY) buffered() bool {
|
||||
return len(tty.rs) > 0
|
||||
}
|
||||
|
||||
func (tty *TTY) readRune() (rune, error) {
|
||||
if len(tty.rs) > 0 {
|
||||
r := tty.rs[0]
|
||||
tty.rs = tty.rs[1:]
|
||||
return r, nil
|
||||
}
|
||||
var ir inputRecord
|
||||
err := readConsoleInput(tty.in.Fd(), &ir)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
switch ir.eventType {
|
||||
case windowBufferSizeEvent:
|
||||
wr := (*windowBufferSizeRecord)(unsafe.Pointer(&ir.event))
|
||||
ws := WINSIZE{
|
||||
W: int(wr.size.x),
|
||||
H: int(wr.size.y),
|
||||
}
|
||||
|
||||
if err := tty.sigwinchCtx.Err(); err != nil {
|
||||
// closing
|
||||
// the following select might panic without this guard close
|
||||
return 0, err
|
||||
}
|
||||
|
||||
select {
|
||||
case tty.ws <- ws:
|
||||
case <-tty.sigwinchCtx.Done():
|
||||
return 0, tty.sigwinchCtx.Err()
|
||||
default:
|
||||
return 0, nil // no one is currently trying to read
|
||||
}
|
||||
case keyEvent:
|
||||
kr := (*keyEventRecord)(unsafe.Pointer(&ir.event))
|
||||
if kr.keyDown != 0 {
|
||||
if kr.controlKeyState&altPressed != 0 && kr.unicodeChar > 0 {
|
||||
tty.rs = []rune{rune(kr.unicodeChar)}
|
||||
return rune(0x1b), nil
|
||||
}
|
||||
if kr.unicodeChar > 0 {
|
||||
if kr.controlKeyState&shiftPressed != 0 {
|
||||
switch kr.unicodeChar {
|
||||
case 0x09:
|
||||
tty.rs = []rune{0x5b, 0x5a}
|
||||
return rune(0x1b), nil
|
||||
}
|
||||
}
|
||||
return rune(kr.unicodeChar), nil
|
||||
}
|
||||
vk := kr.virtualKeyCode
|
||||
if kr.controlKeyState&ctrlPressed != 0 {
|
||||
switch vk {
|
||||
case 0x21: // ctrl-page-up
|
||||
tty.rs = []rune{0x5b, 0x35, 0x3B, 0x35, 0x7e}
|
||||
return rune(0x1b), nil
|
||||
case 0x22: // ctrl-page-down
|
||||
tty.rs = []rune{0x5b, 0x36, 0x3B, 0x35, 0x7e}
|
||||
return rune(0x1b), nil
|
||||
case 0x23: // ctrl-end
|
||||
tty.rs = []rune{0x5b, 0x31, 0x3B, 0x35, 0x46}
|
||||
return rune(0x1b), nil
|
||||
case 0x24: // ctrl-home
|
||||
tty.rs = []rune{0x5b, 0x31, 0x3B, 0x35, 0x48}
|
||||
return rune(0x1b), nil
|
||||
case 0x25: // ctrl-left
|
||||
tty.rs = []rune{0x5b, 0x31, 0x3B, 0x35, 0x44}
|
||||
return rune(0x1b), nil
|
||||
case 0x26: // ctrl-up
|
||||
tty.rs = []rune{0x5b, 0x31, 0x3B, 0x35, 0x41}
|
||||
return rune(0x1b), nil
|
||||
case 0x27: // ctrl-right
|
||||
tty.rs = []rune{0x5b, 0x31, 0x3B, 0x35, 0x43}
|
||||
return rune(0x1b), nil
|
||||
case 0x28: // ctrl-down
|
||||
tty.rs = []rune{0x5b, 0x31, 0x3B, 0x35, 0x42}
|
||||
return rune(0x1b), nil
|
||||
case 0x2e: // ctrl-delete
|
||||
tty.rs = []rune{0x5b, 0x33, 0x3B, 0x35, 0x7e}
|
||||
return rune(0x1b), nil
|
||||
}
|
||||
}
|
||||
switch vk {
|
||||
case 0x21: // page-up
|
||||
tty.rs = []rune{0x5b, 0x35, 0x7e}
|
||||
return rune(0x1b), nil
|
||||
case 0x22: // page-down
|
||||
tty.rs = []rune{0x5b, 0x36, 0x7e}
|
||||
return rune(0x1b), nil
|
||||
case 0x23: // end
|
||||
tty.rs = []rune{0x5b, 0x46}
|
||||
return rune(0x1b), nil
|
||||
case 0x24: // home
|
||||
tty.rs = []rune{0x5b, 0x48}
|
||||
return rune(0x1b), nil
|
||||
case 0x25: // left
|
||||
tty.rs = []rune{0x5b, 0x44}
|
||||
return rune(0x1b), nil
|
||||
case 0x26: // up
|
||||
tty.rs = []rune{0x5b, 0x41}
|
||||
return rune(0x1b), nil
|
||||
case 0x27: // right
|
||||
tty.rs = []rune{0x5b, 0x43}
|
||||
return rune(0x1b), nil
|
||||
case 0x28: // down
|
||||
tty.rs = []rune{0x5b, 0x42}
|
||||
return rune(0x1b), nil
|
||||
case 0x2e: // delete
|
||||
tty.rs = []rune{0x5b, 0x33, 0x7e}
|
||||
return rune(0x1b), nil
|
||||
case 0x70, 0x71, 0x72, 0x73: // F1,F2,F3,F4
|
||||
tty.rs = []rune{0x5b, 0x4f, rune(vk) - 0x20}
|
||||
return rune(0x1b), nil
|
||||
case 0x074, 0x75, 0x76, 0x77: // F5,F6,F7,F8
|
||||
tty.rs = []rune{0x5b, 0x31, rune(vk) - 0x3f, 0x7e}
|
||||
return rune(0x1b), nil
|
||||
case 0x78, 0x79: // F9,F10
|
||||
tty.rs = []rune{0x5b, 0x32, rune(vk) - 0x48, 0x7e}
|
||||
return rune(0x1b), nil
|
||||
case 0x7a, 0x7b: // F11,F12
|
||||
tty.rs = []rune{0x5b, 0x32, rune(vk) - 0x47, 0x7e}
|
||||
return rune(0x1b), nil
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (tty *TTY) close() error {
|
||||
procSetConsoleMode.Call(tty.in.Fd(), uintptr(tty.st))
|
||||
tty.sigwinchCtxCancel()
|
||||
close(tty.ws)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tty *TTY) size() (int, int, error) {
|
||||
var csbi consoleScreenBufferInfo
|
||||
r1, _, err := procGetConsoleScreenBufferInfo.Call(tty.out.Fd(), uintptr(unsafe.Pointer(&csbi)))
|
||||
if r1 == 0 {
|
||||
return 0, 0, err
|
||||
}
|
||||
return int(csbi.window.right - csbi.window.left + 1), int(csbi.window.bottom - csbi.window.top + 1), nil
|
||||
}
|
||||
|
||||
func (tty *TTY) sizePixel() (int, int, int, int, error) {
|
||||
x, y, err := tty.size()
|
||||
if err != nil {
|
||||
x = -1
|
||||
y = -1
|
||||
}
|
||||
return x, y, -1, -1, errors.New("no implemented method for querying size in pixels on Windows")
|
||||
}
|
||||
|
||||
func (tty *TTY) input() *os.File {
|
||||
return tty.in
|
||||
}
|
||||
|
||||
func (tty *TTY) output() *os.File {
|
||||
return tty.out
|
||||
}
|
||||
|
||||
func (tty *TTY) raw() (func() error, error) {
|
||||
var st uint32
|
||||
r1, _, err := procGetConsoleMode.Call(tty.in.Fd(), uintptr(unsafe.Pointer(&st)))
|
||||
if r1 == 0 {
|
||||
return nil, err
|
||||
}
|
||||
mode := st &^ (enableEchoInput | enableProcessedInput | enableLineInput | enableProcessedOutput)
|
||||
r1, _, err = procSetConsoleMode.Call(tty.in.Fd(), uintptr(mode))
|
||||
if r1 == 0 {
|
||||
return nil, err
|
||||
}
|
||||
return func() error {
|
||||
r1, _, err := procSetConsoleMode.Call(tty.in.Fd(), uintptr(st))
|
||||
if r1 == 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (tty *TTY) sigwinch() <-chan WINSIZE {
|
||||
return tty.ws
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
Copyright (c) 2014, David Cheney
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@ -0,0 +1,4 @@
|
||||
// Package termios implements the low level termios(3) terminal line discipline facilities.
|
||||
//
|
||||
// For a higher level interface please use the github.com/pkg/term package.
|
||||
package termios
|
@ -0,0 +1,9 @@
|
||||
// +build !windows
|
||||
|
||||
package termios
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
func ioctl(fd, request, argp uintptr) error {
|
||||
return unix.IoctlSetInt(int(fd), uint(request), int(argp))
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
// +build !windows
|
||||
|
||||
package termios
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func open_device(path string) (uintptr, error) {
|
||||
fd, err := unix.Open(path, unix.O_NOCTTY|unix.O_RDWR|unix.O_CLOEXEC, 0666)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("unable to open %q: %v", path, err)
|
||||
}
|
||||
return uintptr(fd), nil
|
||||
}
|
||||
|
||||
// Pty returns a UNIX 98 pseudoterminal device.
|
||||
// Pty returns a pair of fds representing the master and slave pair.
|
||||
func Pty() (*os.File, *os.File, error) {
|
||||
ptm, err := open_pty_master()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
sname, err := Ptsname(ptm)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
err = grantpt(ptm)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
err = unlockpt(ptm)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pts, err := open_device(sname)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return os.NewFile(uintptr(ptm), "ptm"), os.NewFile(uintptr(pts), sname), nil
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
// +build dragonfly openbsd solaris
|
||||
|
||||
package termios
|
||||
|
||||
// #include<stdlib.h>
|
||||
import "C"
|
||||
|
||||
import "syscall"
|
||||
|
||||
func open_pty_master() (uintptr, error) {
|
||||
rc := C.posix_openpt(syscall.O_NOCTTY | syscall.O_RDWR)
|
||||
if rc < 0 {
|
||||
return 0, syscall.Errno(rc)
|
||||
}
|
||||
return uintptr(rc), nil
|
||||
}
|
||||
|
||||
func Ptsname(fd uintptr) (string, error) {
|
||||
slavename := C.GoString(C.ptsname(C.int(fd)))
|
||||
return slavename, nil
|
||||
}
|
||||
|
||||
func grantpt(fd uintptr) error {
|
||||
rc := C.grantpt(C.int(fd))
|
||||
if rc == 0 {
|
||||
return nil
|
||||
}
|
||||
return syscall.Errno(rc)
|
||||
}
|
||||
|
||||
func unlockpt(fd uintptr) error {
|
||||
rc := C.unlockpt(C.int(fd))
|
||||
if rc == 0 {
|
||||
return nil
|
||||
}
|
||||
return syscall.Errno(rc)
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package termios
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func open_pty_master() (uintptr, error) {
|
||||
return open_device("/dev/ptmx")
|
||||
}
|
||||
|
||||
const (
|
||||
_IOC_PARAM_SHIFT = 13
|
||||
_IOC_PARAM_MASK = (1 << _IOC_PARAM_SHIFT) - 1
|
||||
)
|
||||
|
||||
func _IOC_PARM_LEN(ioctl uintptr) uintptr {
|
||||
return (ioctl >> 16) & _IOC_PARAM_MASK
|
||||
}
|
||||
|
||||
func Ptsname(fd uintptr) (string, error) {
|
||||
n := make([]byte, _IOC_PARM_LEN(unix.TIOCPTYGNAME))
|
||||
|
||||
err := ioctl(fd, unix.TIOCPTYGNAME, uintptr(unsafe.Pointer(&n[0])))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for i, c := range n {
|
||||
if c == 0 {
|
||||
return string(n[:i]), nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("TIOCPTYGNAME string not NUL-terminated")
|
||||
}
|
||||
|
||||
func grantpt(fd uintptr) error {
|
||||
return unix.IoctlSetInt(int(fd), unix.TIOCPTYGRANT, 0)
|
||||
}
|
||||
|
||||
func unlockpt(fd uintptr) error {
|
||||
return unix.IoctlSetInt(int(fd), unix.TIOCPTYUNLK, 0)
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package termios
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func posix_openpt(oflag int) (fd uintptr, err error) {
|
||||
// Copied from debian-golang-pty/pty_freebsd.go.
|
||||
r0, _, e1 := unix.Syscall(unix.SYS_POSIX_OPENPT, uintptr(oflag), 0, 0)
|
||||
fd = uintptr(r0)
|
||||
if e1 != 0 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func open_pty_master() (uintptr, error) {
|
||||
return posix_openpt(unix.O_NOCTTY | unix.O_RDWR | unix.O_CLOEXEC)
|
||||
}
|
||||
|
||||
func Ptsname(fd uintptr) (string, error) {
|
||||
n, err := unix.IoctlGetInt(int(fd), unix.TIOCGPTN)
|
||||
return fmt.Sprintf("/dev/pts/%d", n), err
|
||||
}
|
||||
|
||||
func grantpt(fd uintptr) error {
|
||||
return unix.IoctlSetInt(int(fd), unix.TIOCGPTN, 0)
|
||||
}
|
||||
|
||||
func unlockpt(fd uintptr) error {
|
||||
return nil
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package termios
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func open_pty_master() (uintptr, error) {
|
||||
return open_device("/dev/ptmx")
|
||||
}
|
||||
|
||||
func Ptsname(fd uintptr) (string, error) {
|
||||
n, err := unix.IoctlGetInt(int(fd), unix.TIOCGPTN)
|
||||
return fmt.Sprintf("/dev/pts/%d", n), err
|
||||
}
|
||||
|
||||
func grantpt(fd uintptr) error {
|
||||
var n uintptr
|
||||
return ioctl(fd, unix.TIOCGPTN, uintptr(unsafe.Pointer(&n)))
|
||||
}
|
||||
|
||||
func unlockpt(fd uintptr) error {
|
||||
var n uintptr
|
||||
return ioctl(fd, unix.TIOCSPTLCK, uintptr(unsafe.Pointer(&n)))
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package termios
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func open_pty_master() (uintptr, error) {
|
||||
return open_device("/dev/ptmx")
|
||||
}
|
||||
|
||||
func Ptsname(fd uintptr) (string, error) {
|
||||
ptm, err := unix.IoctlGetPtmget(int(fd), unix.TIOCPTSNAME)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(ptm.Sn[:bytes.IndexByte(ptm.Sn[:], 0)]), nil
|
||||
}
|
||||
|
||||
func grantpt(fd uintptr) error {
|
||||
return unix.IoctlSetInt(int(fd), unix.TIOCGRANTPT, 0)
|
||||
}
|
||||
|
||||
func unlockpt(fd uintptr) error {
|
||||
return nil
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
// +build !windows
|
||||
|
||||
package termios
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Tiocmget returns the state of the MODEM bits.
|
||||
func Tiocmget(fd uintptr) (int, error) {
|
||||
return unix.IoctlGetInt(int(fd), unix.TIOCMGET)
|
||||
}
|
||||
|
||||
// Tiocmset sets the state of the MODEM bits.
|
||||
func Tiocmset(fd uintptr, status int) error {
|
||||
return unix.IoctlSetInt(int(fd), unix.TIOCMSET, status)
|
||||
}
|
||||
|
||||
// Tiocmbis sets the indicated modem bits.
|
||||
func Tiocmbis(fd uintptr, status int) error {
|
||||
return unix.IoctlSetPointerInt(int(fd), unix.TIOCMBIS, status)
|
||||
}
|
||||
|
||||
// Tiocmbic clears the indicated modem bits.
|
||||
func Tiocmbic(fd uintptr, status int) error {
|
||||
return unix.IoctlSetPointerInt(int(fd), unix.TIOCMBIC, status)
|
||||
}
|
||||
|
||||
// Tiocoutq return the number of bytes in the output buffer.
|
||||
func Tiocoutq(fd uintptr) (int, error) {
|
||||
return unix.IoctlGetInt(int(fd), unix.TIOCOUTQ)
|
||||
}
|
||||
|
||||
// Cfmakecbreak modifies attr for cbreak mode.
|
||||
func Cfmakecbreak(attr *unix.Termios) {
|
||||
attr.Lflag &^= unix.ECHO | unix.ICANON
|
||||
attr.Cc[unix.VMIN] = 1
|
||||
attr.Cc[unix.VTIME] = 0
|
||||
}
|
||||
|
||||
// Cfmakeraw modifies attr for raw mode.
|
||||
func Cfmakeraw(attr *unix.Termios) {
|
||||
attr.Iflag &^= unix.BRKINT | unix.ICRNL | unix.INPCK | unix.ISTRIP | unix.IXON
|
||||
attr.Oflag &^= unix.OPOST
|
||||
attr.Cflag &^= unix.CSIZE | unix.PARENB
|
||||
attr.Cflag |= unix.CS8
|
||||
attr.Lflag &^= unix.ECHO | unix.ICANON | unix.IEXTEN | unix.ISIG
|
||||
attr.Cc[unix.VMIN] = 1
|
||||
attr.Cc[unix.VTIME] = 0
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
// +build darwin freebsd openbsd netbsd dragonfly
|
||||
|
||||
package termios
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
IXON = 0x00000200
|
||||
IXOFF = 0x00000400
|
||||
IXANY = 0x00000800
|
||||
CCTS_OFLOW = 0x00010000
|
||||
CRTS_IFLOW = 0x00020000
|
||||
CRTSCTS = CCTS_OFLOW | CRTS_IFLOW
|
||||
)
|
||||
|
||||
// Tcgetattr gets the current serial port settings.
|
||||
func Tcgetattr(fd uintptr) (*unix.Termios, error) {
|
||||
return unix.IoctlGetTermios(int(fd), unix.TIOCGETA)
|
||||
}
|
||||
|
||||
// Tcsetattr sets the current serial port settings.
|
||||
func Tcsetattr(fd, opt uintptr, argp *unix.Termios) error {
|
||||
switch opt {
|
||||
case TCSANOW:
|
||||
opt = unix.TIOCSETA
|
||||
case TCSADRAIN:
|
||||
opt = unix.TIOCSETAW
|
||||
case TCSAFLUSH:
|
||||
opt = unix.TIOCSETAF
|
||||
default:
|
||||
return unix.EINVAL
|
||||
}
|
||||
return unix.IoctlSetTermios(int(fd), uint(opt), argp)
|
||||
}
|
||||
|
||||
// Tcsendbreak function transmits a continuous stream of zero-valued bits for
|
||||
// four-tenths of a second to the terminal referenced by fildes. The duration
|
||||
// parameter is ignored in this implementation.
|
||||
func Tcsendbreak(fd uintptr, duration int) error {
|
||||
if err := unix.IoctlSetInt(int(fd), unix.TIOCSBRK, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(4 / 10 * time.Second)
|
||||
return unix.IoctlSetInt(int(fd), unix.TIOCCBRK, 0)
|
||||
}
|
||||
|
||||
// Tcdrain waits until all output written to the terminal referenced by fd has been transmitted to the terminal.
|
||||
func Tcdrain(fd uintptr) error {
|
||||
return unix.IoctlSetInt(int(fd), unix.TIOCDRAIN, 0)
|
||||
}
|
||||
|
||||
// Tcflush discards data written to the object referred to by fd but not transmitted, or data received but not read, depending on the value of which.
|
||||
func Tcflush(fd, which uintptr) error {
|
||||
return unix.IoctlSetPointerInt(int(fd), unix.TIOCFLUSH, int(which))
|
||||
}
|
||||
|
||||
// Cfgetispeed returns the input baud rate stored in the termios structure.
|
||||
func Cfgetispeed(attr *unix.Termios) uint32 { return uint32(attr.Ispeed) }
|
||||
|
||||
// Cfgetospeed returns the output baud rate stored in the termios structure.
|
||||
func Cfgetospeed(attr *unix.Termios) uint32 { return uint32(attr.Ospeed) }
|
||||
|
||||
// Tiocinq returns the number of bytes in the input buffer.
|
||||
func Tiocinq(fd uintptr) (int, error) {
|
||||
return 0, nil
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
// +build !windows,!solaris
|
||||
|
||||
package termios
|
||||
|
||||
const (
|
||||
TCIFLUSH = 0
|
||||
TCOFLUSH = 1
|
||||
TCIOFLUSH = 2
|
||||
|
||||
TCSANOW = 0
|
||||
TCSADRAIN = 1
|
||||
TCSAFLUSH = 2
|
||||
)
|
@ -0,0 +1,14 @@
|
||||
package termios
|
||||
|
||||
// #include<termios.h>
|
||||
import "C"
|
||||
|
||||
const (
|
||||
TCIFLUSH = C.TCIFLUSH
|
||||
TCOFLUSH = C.TCOFLUSH
|
||||
TCIOFLUSH = C.TCIOFLUSH
|
||||
|
||||
TCSANOW = C.TCSANOW
|
||||
TCSADRAIN = C.TCSADRAIN
|
||||
TCSAFLUSH = C.TCSAFLUSH
|
||||
)
|
@ -0,0 +1,68 @@
|
||||
package termios
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
IXON = 0x00000400
|
||||
IXANY = 0x00000800
|
||||
IXOFF = 0x00001000
|
||||
CRTSCTS = 0x80000000
|
||||
)
|
||||
|
||||
// Tcgetattr gets the current serial port settings.
|
||||
func Tcgetattr(fd uintptr) (*unix.Termios, error) {
|
||||
return unix.IoctlGetTermios(int(fd), unix.TCGETS)
|
||||
}
|
||||
|
||||
// Tcsetattr sets the current serial port settings.
|
||||
func Tcsetattr(fd, action uintptr, argp *unix.Termios) error {
|
||||
var request uintptr
|
||||
switch action {
|
||||
case TCSANOW:
|
||||
request = unix.TCSETS
|
||||
case TCSADRAIN:
|
||||
request = unix.TCSETSW
|
||||
case TCSAFLUSH:
|
||||
request = unix.TCSETSF
|
||||
default:
|
||||
return unix.EINVAL
|
||||
}
|
||||
return unix.IoctlSetTermios(int(fd), uint(request), argp)
|
||||
}
|
||||
|
||||
// Tcsendbreak transmits a continuous stream of zero-valued bits for a specific
|
||||
// duration, if the terminal is using asynchronous serial data transmission. If
|
||||
// duration is zero, it transmits zero-valued bits for at least 0.25 seconds, and not more that 0.5 seconds.
|
||||
// If duration is not zero, it sends zero-valued bits for some
|
||||
// implementation-defined length of time.
|
||||
func Tcsendbreak(fd uintptr, duration int) error {
|
||||
return unix.IoctlSetInt(int(fd), unix.TCSBRKP, duration)
|
||||
}
|
||||
|
||||
// Tcdrain waits until all output written to the object referred to by fd has been transmitted.
|
||||
func Tcdrain(fd uintptr) error {
|
||||
// simulate drain with TCSADRAIN
|
||||
attr, err := Tcgetattr(fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return Tcsetattr(fd, TCSADRAIN, attr)
|
||||
}
|
||||
|
||||
// Tcflush discards data written to the object referred to by fd but not transmitted, or data received but not read, depending on the value of selector.
|
||||
func Tcflush(fd, selector uintptr) error {
|
||||
return unix.IoctlSetInt(int(fd), unix.TCFLSH, int(selector))
|
||||
}
|
||||
|
||||
// Tiocinq returns the number of bytes in the input buffer.
|
||||
func Tiocinq(fd uintptr) (int, error) {
|
||||
return unix.IoctlGetInt(int(fd), unix.TIOCINQ)
|
||||
}
|
||||
|
||||
// Cfgetispeed returns the input baud rate stored in the termios structure.
|
||||
func Cfgetispeed(attr *unix.Termios) uint32 { return attr.Ispeed }
|
||||
|
||||
// Cfgetospeed returns the output baud rate stored in the termios structure.
|
||||
func Cfgetospeed(attr *unix.Termios) uint32 { return attr.Ospeed }
|
@ -0,0 +1,112 @@
|
||||
package termios
|
||||
|
||||
// #include <termios.h>
|
||||
// typedef struct termios termios_t;
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
TCSETS = 0x5402
|
||||
TCSETSW = 0x5403
|
||||
TCSETSF = 0x5404
|
||||
TCSBRK = 0x5409
|
||||
TCSBRKP = 0x5425
|
||||
|
||||
IXON = 0x00000400
|
||||
IXANY = 0x00000800
|
||||
IXOFF = 0x00001000
|
||||
CRTSCTS = 0x80000000
|
||||
)
|
||||
|
||||
// Tcgetattr gets the current serial port settings.
|
||||
func Tcgetattr(fd uintptr) (*unix.Termios, error) {
|
||||
return unix.IoctlGetTermios(int(fd), unix.TCGETS)
|
||||
}
|
||||
|
||||
// Tcsetattr sets the current serial port settings.
|
||||
func Tcsetattr(fd, action uintptr, argp *unix.Termios) error {
|
||||
return unix.IoctlSetTermios(int(fd), uint(action), tiosToUnix(argp))
|
||||
}
|
||||
|
||||
// Tcsendbreak transmits a continuous stream of zero-valued bits for a specific
|
||||
// duration, if the terminal is using asynchronous serial data transmission. If
|
||||
// duration is zero, it transmits zero-valued bits for at least 0.25 seconds, and not more that 0.5 seconds.
|
||||
// If duration is not zero, it sends zero-valued bits for some
|
||||
// implementation-defined length of time.
|
||||
func Tcsendbreak(fd uintptr, duration int) error {
|
||||
return ioctl(fd, TCSBRKP, uintptr(duration))
|
||||
}
|
||||
|
||||
// Tcdrain waits until all output written to the object referred to by fd has been transmitted.
|
||||
func Tcdrain(fd uintptr) error {
|
||||
// simulate drain with TCSADRAIN
|
||||
var attr unix.Termios
|
||||
if err := Tcgetattr(fd, &attr); err != nil {
|
||||
return err
|
||||
}
|
||||
return Tcsetattr(fd, TCSADRAIN, &attr)
|
||||
}
|
||||
|
||||
// Tcflush discards data written to the object referred to by fd but not transmitted, or data received but not read, depending on the value of selector.
|
||||
func Tcflush(fd, selector uintptr) error {
|
||||
return ioctl(fd, unix.TCFLSH, selector)
|
||||
}
|
||||
|
||||
// Tiocinq returns the number of bytes in the input buffer.
|
||||
func Tiocinq(fd uintptr) (int, error) {
|
||||
return unix.IoctlGetInt(int(fd), unix.FIORDCHK)
|
||||
}
|
||||
|
||||
// Cfgetispeed returns the input baud rate stored in the termios structure.
|
||||
func Cfgetispeed(attr *unix.Termios) uint32 {
|
||||
solTermios := tiosToUnix(attr)
|
||||
return uint32(C.cfgetispeed((*C.termios_t)(unsafe.Pointer(solTermios))))
|
||||
}
|
||||
|
||||
// Cfsetispeed sets the input baud rate stored in the termios structure.
|
||||
func Cfsetispeed(attr *unix.Termios, speed uintptr) error {
|
||||
solTermios := tiosToUnix(attr)
|
||||
_, err := C.cfsetispeed((*C.termios_t)(unsafe.Pointer(solTermios)), C.speed_t(speed))
|
||||
return err
|
||||
}
|
||||
|
||||
// Cfgetospeed returns the output baud rate stored in the termios structure.
|
||||
func Cfgetospeed(attr *unix.Termios) uint32 {
|
||||
solTermios := tiosToUnix(attr)
|
||||
return uint32(C.cfgetospeed((*C.termios_t)(unsafe.Pointer(solTermios))))
|
||||
}
|
||||
|
||||
// Cfsetospeed sets the output baud rate stored in the termios structure.
|
||||
func Cfsetospeed(attr *unix.Termios, speed uintptr) error {
|
||||
solTermios := tiosToUnix(attr)
|
||||
_, err := C.cfsetospeed((*C.termios_t)(unsafe.Pointer(solTermios)), C.speed_t(speed))
|
||||
return err
|
||||
}
|
||||
|
||||
// tiosToUnix copies a unix.Termios to a x/sys/unix.Termios.
|
||||
// This is needed since type conversions between the two fail due to
|
||||
// more recent x/sys/unix.Termios renaming the padding field.
|
||||
func tiosToUnix(st *unix.Termios) *unix.Termios {
|
||||
return &unix.Termios{
|
||||
Iflag: st.Iflag,
|
||||
Oflag: st.Oflag,
|
||||
Cflag: st.Cflag,
|
||||
Lflag: st.Lflag,
|
||||
Cc: st.Cc,
|
||||
}
|
||||
}
|
||||
|
||||
// tiosTounix copies a x/sys/unix.Termios to a unix.Termios.
|
||||
func tiosTounix(ut *unix.Termios) *unix.Termios {
|
||||
return &unix.Termios{
|
||||
Iflag: ut.Iflag,
|
||||
Oflag: ut.Oflag,
|
||||
Cflag: ut.Cflag,
|
||||
Lflag: ut.Lflag,
|
||||
Cc: ut.Cc,
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue