Initial commit

This commit is contained in:
Donny
2019-04-22 20:46:32 +08:00
commit 49ab8aadd1
25441 changed files with 4055000 additions and 0 deletions

21
vendor/gopkg.in/AlecAivazis/survey.v1/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Alec Aivazis
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.

36
vendor/gopkg.in/AlecAivazis/survey.v1/core/BUILD generated vendored Normal file
View File

@@ -0,0 +1,36 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"renderer.go",
"template.go",
"write.go",
],
importpath = "gopkg.in/AlecAivazis/survey.v1/core",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/mgutz/ansi:go_default_library",
"//vendor/gopkg.in/AlecAivazis/survey.v1/terminal:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

84
vendor/gopkg.in/AlecAivazis/survey.v1/core/renderer.go generated vendored Normal file
View File

@@ -0,0 +1,84 @@
package core
import (
"fmt"
"strings"
"gopkg.in/AlecAivazis/survey.v1/terminal"
)
type Renderer struct {
stdio terminal.Stdio
lineCount int
errorLineCount int
}
var ErrorTemplate = `{{color "red"}}{{ ErrorIcon }} Sorry, your reply was invalid: {{.Error}}{{color "reset"}}
`
func (r *Renderer) WithStdio(stdio terminal.Stdio) {
r.stdio = stdio
}
func (r *Renderer) Stdio() terminal.Stdio {
return r.stdio
}
func (r *Renderer) NewRuneReader() *terminal.RuneReader {
return terminal.NewRuneReader(r.stdio)
}
func (r *Renderer) NewCursor() *terminal.Cursor {
return &terminal.Cursor{
In: r.stdio.In,
Out: r.stdio.Out,
}
}
func (r *Renderer) Error(invalid error) error {
// since errors are printed on top we need to reset the prompt
// as well as any previous error print
r.resetPrompt(r.lineCount + r.errorLineCount)
// we just cleared the prompt lines
r.lineCount = 0
out, err := RunTemplate(ErrorTemplate, invalid)
if err != nil {
return err
}
// keep track of how many lines are printed so we can clean up later
r.errorLineCount = strings.Count(out, "\n")
// send the message to the user
fmt.Fprint(terminal.NewAnsiStdout(r.stdio.Out), out)
return nil
}
func (r *Renderer) resetPrompt(lines int) {
// clean out current line in case tmpl didnt end in newline
cursor := r.NewCursor()
cursor.HorizontalAbsolute(0)
terminal.EraseLine(r.stdio.Out, terminal.ERASE_LINE_ALL)
// clean up what we left behind last time
for i := 0; i < lines; i++ {
cursor.PreviousLine(1)
terminal.EraseLine(r.stdio.Out, terminal.ERASE_LINE_ALL)
}
}
func (r *Renderer) Render(tmpl string, data interface{}) error {
r.resetPrompt(r.lineCount)
// render the template summarizing the current state
out, err := RunTemplate(tmpl, data)
if err != nil {
return err
}
// keep track of how many lines are printed so we can clean up later
r.lineCount = strings.Count(out, "\n")
// print the summary
fmt.Fprint(terminal.NewAnsiStdout(r.stdio.Out), out)
// nothing went wrong
return nil
}

120
vendor/gopkg.in/AlecAivazis/survey.v1/core/template.go generated vendored Normal file
View File

@@ -0,0 +1,120 @@
package core
import (
"bytes"
"sync"
"text/template"
"github.com/mgutz/ansi"
)
var DisableColor = false
var (
// HelpInputRune is the rune which the user should enter to trigger
// more detailed question help
HelpInputRune = '?'
// ErrorIcon will be be shown before an error
ErrorIcon = "X"
// HelpIcon will be shown before more detailed question help
HelpIcon = "?"
// QuestionIcon will be shown before a question Message
QuestionIcon = "?"
// MarkedOptionIcon will be prepended before a selected multiselect option
MarkedOptionIcon = "[x]"
// UnmarkedOptionIcon will be prepended before an unselected multiselect option
UnmarkedOptionIcon = "[ ]"
// SelectFocusIcon is prepended to an option to signify the user is
// currently focusing that option
SelectFocusIcon = ">"
)
/*
SetFancyIcons changes the err, help, marked, and focus input icons to their
fancier forms. These forms may not be compatible with most terminals.
This function will not touch the QuestionIcon as its fancy and non fancy form
are the same.
*/
func SetFancyIcons() {
ErrorIcon = "✘"
HelpIcon = "ⓘ"
// QuestionIcon fancy and non-fancy form are the same
MarkedOptionIcon = "◉"
UnmarkedOptionIcon = "◯"
SelectFocusIcon = ""
}
var TemplateFuncs = map[string]interface{}{
// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format
"color": func(color string) string {
if DisableColor {
return ""
}
return ansi.ColorCode(color)
},
"HelpInputRune": func() string {
return string(HelpInputRune)
},
"ErrorIcon": func() string {
return ErrorIcon
},
"HelpIcon": func() string {
return HelpIcon
},
"QuestionIcon": func() string {
return QuestionIcon
},
"MarkedOptionIcon": func() string {
return MarkedOptionIcon
},
"UnmarkedOptionIcon": func() string {
return UnmarkedOptionIcon
},
"SelectFocusIcon": func() string {
return SelectFocusIcon
},
}
var (
memoizedGetTemplate = map[string]*template.Template{}
memoMutex = &sync.RWMutex{}
)
func getTemplate(tmpl string) (*template.Template, error) {
memoMutex.RLock()
if t, ok := memoizedGetTemplate[tmpl]; ok {
memoMutex.RUnlock()
return t, nil
}
memoMutex.RUnlock()
t, err := template.New("prompt").Funcs(TemplateFuncs).Parse(tmpl)
if err != nil {
return nil, err
}
memoMutex.Lock()
memoizedGetTemplate[tmpl] = t
memoMutex.Unlock()
return t, nil
}
func RunTemplate(tmpl string, data interface{}) (string, error) {
t, err := getTemplate(tmpl)
if err != nil {
return "", err
}
buf := bytes.NewBufferString("")
err = t.Execute(buf, data)
if err != nil {
return "", err
}
return buf.String(), err
}

246
vendor/gopkg.in/AlecAivazis/survey.v1/core/write.go generated vendored Normal file
View File

@@ -0,0 +1,246 @@
package core
import (
"errors"
"fmt"
"reflect"
"strconv"
"strings"
)
// the tag used to denote the name of the question
const tagName = "survey"
// add a few interfaces so users can configure how the prompt values are set
type settable interface {
WriteAnswer(field string, value interface{}) error
}
func WriteAnswer(t interface{}, name string, v interface{}) (err error) {
// if the field is a custom type
if s, ok := t.(settable); ok {
// use the interface method
return s.WriteAnswer(name, v)
}
// the target to write to
target := reflect.ValueOf(t)
// the value to write from
value := reflect.ValueOf(v)
// make sure we are writing to a pointer
if target.Kind() != reflect.Ptr {
return errors.New("you must pass a pointer as the target of a Write operation")
}
// the object "inside" of the target pointer
elem := target.Elem()
// handle the special types
switch elem.Kind() {
// if we are writing to a struct
case reflect.Struct:
// get the name of the field that matches the string we were given
fieldIndex, err := findFieldIndex(elem, name)
// if something went wrong
if err != nil {
// bubble up
return err
}
field := elem.Field(fieldIndex)
// handle references to the settable interface aswell
if s, ok := field.Interface().(settable); ok {
// use the interface method
return s.WriteAnswer(name, v)
}
if field.CanAddr() {
if s, ok := field.Addr().Interface().(settable); ok {
// use the interface method
return s.WriteAnswer(name, v)
}
}
// copy the value over to the normal struct
return copy(field, value)
case reflect.Map:
mapType := reflect.TypeOf(t).Elem()
if mapType.Key().Kind() != reflect.String || mapType.Elem().Kind() != reflect.Interface {
return errors.New("answer maps must be of type map[string]interface")
}
mt := *t.(*map[string]interface{})
mt[name] = value.Interface()
return nil
}
// otherwise just copy the value to the target
return copy(elem, value)
}
// BUG(AlecAivazis): the current implementation might cause weird conflicts if there are
// two fields with same name that only differ by casing.
func findFieldIndex(s reflect.Value, name string) (int, error) {
// the type of the value
sType := s.Type()
// first look for matching tags so we can overwrite matching field names
for i := 0; i < sType.NumField(); i++ {
// the field we are current scanning
field := sType.Field(i)
// the value of the survey tag
tag := field.Tag.Get(tagName)
// if the tag matches the name we are looking for
if tag != "" && tag == name {
// then we found our index
return i, nil
}
}
// then look for matching names
for i := 0; i < sType.NumField(); i++ {
// the field we are current scanning
field := sType.Field(i)
// if the name of the field matches what we're looking for
if strings.ToLower(field.Name) == strings.ToLower(name) {
return i, nil
}
}
// we didn't find the field
return -1, fmt.Errorf("could not find field matching %v", name)
}
// isList returns true if the element is something we can Len()
func isList(v reflect.Value) bool {
switch v.Type().Kind() {
case reflect.Array, reflect.Slice:
return true
default:
return false
}
}
// Write takes a value and copies it to the target
func copy(t reflect.Value, v reflect.Value) (err error) {
// if something ends up panicing we need to catch it in a deferred func
defer func() {
if r := recover(); r != nil {
// if we paniced with an error
if _, ok := r.(error); ok {
// cast the result to an error object
err = r.(error)
} else if _, ok := r.(string); ok {
// otherwise we could have paniced with a string so wrap it in an error
err = errors.New(r.(string))
}
}
}()
// if we are copying from a string result to something else
if v.Kind() == reflect.String && v.Type() != t.Type() {
var castVal interface{}
var casterr error
vString := v.Interface().(string)
switch t.Kind() {
case reflect.Bool:
castVal, casterr = strconv.ParseBool(vString)
case reflect.Int:
castVal, casterr = strconv.Atoi(vString)
case reflect.Int8:
var val64 int64
val64, casterr = strconv.ParseInt(vString, 10, 8)
if casterr == nil {
castVal = int8(val64)
}
case reflect.Int16:
var val64 int64
val64, casterr = strconv.ParseInt(vString, 10, 16)
if casterr == nil {
castVal = int16(val64)
}
case reflect.Int32:
var val64 int64
val64, casterr = strconv.ParseInt(vString, 10, 32)
if casterr == nil {
castVal = int32(val64)
}
case reflect.Int64:
castVal, casterr = strconv.ParseInt(vString, 10, 64)
case reflect.Uint:
var val64 uint64
val64, casterr = strconv.ParseUint(vString, 10, 8)
if casterr == nil {
castVal = uint(val64)
}
case reflect.Uint8:
var val64 uint64
val64, casterr = strconv.ParseUint(vString, 10, 8)
if casterr == nil {
castVal = uint8(val64)
}
case reflect.Uint16:
var val64 uint64
val64, casterr = strconv.ParseUint(vString, 10, 16)
if casterr == nil {
castVal = uint16(val64)
}
case reflect.Uint32:
var val64 uint64
val64, casterr = strconv.ParseUint(vString, 10, 32)
if casterr == nil {
castVal = uint32(val64)
}
case reflect.Uint64:
castVal, casterr = strconv.ParseUint(vString, 10, 64)
case reflect.Float32:
var val64 float64
val64, casterr = strconv.ParseFloat(vString, 32)
if casterr == nil {
castVal = float32(val64)
}
case reflect.Float64:
castVal, casterr = strconv.ParseFloat(vString, 64)
default:
return fmt.Errorf("Unable to convert from string to type %s", t.Kind())
}
if casterr != nil {
return casterr
}
t.Set(reflect.ValueOf(castVal))
return
}
// if we are copying from one slice or array to another
if isList(v) && isList(t) {
// loop over every item in the desired value
for i := 0; i < v.Len(); i++ {
// write to the target given its kind
switch t.Kind() {
// if its a slice
case reflect.Slice:
// an object of the correct type
obj := reflect.Indirect(reflect.New(t.Type().Elem()))
// write the appropriate value to the obj and catch any errors
if err := copy(obj, v.Index(i)); err != nil {
return err
}
// just append the value to the end
t.Set(reflect.Append(t, obj))
// otherwise it could be an array
case reflect.Array:
// set the index to the appropriate value
copy(t.Slice(i, i+1).Index(0), v.Index(i))
}
}
} else {
// set the value to the target
t.Set(v)
}
// we're done
return
}

47
vendor/gopkg.in/AlecAivazis/survey.v1/terminal/BUILD generated vendored Normal file
View File

@@ -0,0 +1,47 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"buffered_reader.go",
"cursor.go",
"cursor_windows.go",
"display.go",
"display_posix.go",
"display_windows.go",
"error.go",
"output.go",
"output_windows.go",
"runereader.go",
"runereader_bsd.go",
"runereader_linux.go",
"runereader_posix.go",
"runereader_windows.go",
"sequences.go",
"stdio.go",
"syscall_windows.go",
"terminal.go",
],
importpath = "gopkg.in/AlecAivazis/survey.v1/terminal",
tags = ["manual"],
visibility = ["//visibility:public"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,22 @@
Copyright (c) 2014 Takashi Kokubun
MIT License
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.

View File

@@ -0,0 +1,3 @@
# survey/terminal
This package started as a copy of [kokuban/go-ansi](http://github.com/k0kubun/go-ansi) but has since been modified to fit survey's specific needs.

View File

@@ -0,0 +1,22 @@
package terminal
import (
"bytes"
"io"
)
type BufferedReader struct {
In io.Reader
Buffer *bytes.Buffer
}
func (br *BufferedReader) Read(p []byte) (int, error) {
n, err := br.Buffer.Read(p)
if err != nil && err != io.EOF {
return n, err
} else if err == nil {
return n, nil
}
return br.In.Read(p[n:])
}

View File

@@ -0,0 +1,174 @@
// +build !windows
package terminal
import (
"bufio"
"bytes"
"fmt"
"regexp"
"strconv"
)
var COORDINATE_SYSTEM_BEGIN Short = 1
var dsrPattern = regexp.MustCompile(`\x1b\[(\d+);(\d+)R$`)
type Cursor struct {
In FileReader
Out FileWriter
}
// Up moves the cursor n cells to up.
func (c *Cursor) Up(n int) {
fmt.Fprintf(c.Out, "\x1b[%dA", n)
}
// Down moves the cursor n cells to down.
func (c *Cursor) Down(n int) {
fmt.Fprintf(c.Out, "\x1b[%dB", n)
}
// Forward moves the cursor n cells to right.
func (c *Cursor) Forward(n int) {
fmt.Fprintf(c.Out, "\x1b[%dC", n)
}
// Back moves the cursor n cells to left.
func (c *Cursor) Back(n int) {
fmt.Fprintf(c.Out, "\x1b[%dD", n)
}
// NextLine moves cursor to beginning of the line n lines down.
func (c *Cursor) NextLine(n int) {
fmt.Fprintf(c.Out, "\x1b[%dE", n)
}
// PreviousLine moves cursor to beginning of the line n lines up.
func (c *Cursor) PreviousLine(n int) {
fmt.Fprintf(c.Out, "\x1b[%dF", n)
}
// HorizontalAbsolute moves cursor horizontally to x.
func (c *Cursor) HorizontalAbsolute(x int) {
fmt.Fprintf(c.Out, "\x1b[%dG", x)
}
// Show shows the cursor.
func (c *Cursor) Show() {
fmt.Fprint(c.Out, "\x1b[?25h")
}
// Hide hide the cursor.
func (c *Cursor) Hide() {
fmt.Fprint(c.Out, "\x1b[?25l")
}
// Move moves the cursor to a specific x,y location.
func (c *Cursor) Move(x int, y int) {
fmt.Fprintf(c.Out, "\x1b[%d;%df", x, y)
}
// Save saves the current position
func (c *Cursor) Save() {
fmt.Fprint(c.Out, "\x1b7")
}
// Restore restores the saved position of the cursor
func (c *Cursor) Restore() {
fmt.Fprint(c.Out, "\x1b8")
}
// for comparability purposes between windows
// in unix we need to print out a new line on some terminals
func (c *Cursor) MoveNextLine(cur *Coord, terminalSize *Coord) {
if cur.Y == terminalSize.Y {
fmt.Fprintln(c.Out)
}
c.NextLine(1)
}
// Location returns the current location of the cursor in the terminal
func (c *Cursor) Location(buf *bytes.Buffer) (*Coord, error) {
// ANSI escape sequence for DSR - Device Status Report
// https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_sequences
fmt.Fprint(c.Out, "\x1b[6n")
// There may be input in Stdin prior to CursorLocation so make sure we don't
// drop those bytes.
var loc []int
var match string
for loc == nil {
// Reports the cursor position (CPR) to the application as (as though typed at
// the keyboard) ESC[n;mR, where n is the row and m is the column.
reader := bufio.NewReader(c.In)
text, err := reader.ReadSlice('R')
if err != nil {
return nil, err
}
loc = dsrPattern.FindStringIndex(string(text))
if loc == nil {
// Stdin contains R that doesn't match DSR.
buf.Write(text)
} else {
buf.Write(text[:loc[0]])
match = string(text[loc[0]:loc[1]])
}
}
matches := dsrPattern.FindStringSubmatch(string(match))
if len(matches) != 3 {
return nil, fmt.Errorf("incorrect number of matches: %d", len(matches))
}
col, err := strconv.Atoi(matches[2])
if err != nil {
return nil, err
}
row, err := strconv.Atoi(matches[1])
if err != nil {
return nil, err
}
return &Coord{Short(col), Short(row)}, nil
}
func (cur Coord) CursorIsAtLineEnd(size *Coord) bool {
return cur.X == size.X
}
func (cur Coord) CursorIsAtLineBegin() bool {
return cur.X == COORDINATE_SYSTEM_BEGIN
}
// Size returns the height and width of the terminal.
func (c *Cursor) Size(buf *bytes.Buffer) (*Coord, error) {
// the general approach here is to move the cursor to the very bottom
// of the terminal, ask for the current location and then move the
// cursor back where we started
// hide the cursor (so it doesn't blink when getting the size of the terminal)
c.Hide()
// save the current location of the cursor
c.Save()
// move the cursor to the very bottom of the terminal
c.Move(999, 999)
// ask for the current location
bottom, err := c.Location(buf)
if err != nil {
return nil, err
}
// move back where we began
c.Restore()
// show the cursor
c.Show()
// sice the bottom was calcuated in the lower right corner, it
// is the dimensions we are looking for
return bottom, nil
}

View File

@@ -0,0 +1,138 @@
package terminal
import (
"bytes"
"syscall"
"unsafe"
)
var COORDINATE_SYSTEM_BEGIN Short = 0
// shared variable to save the cursor location from CursorSave()
var cursorLoc Coord
type Cursor struct {
In FileReader
Out FileWriter
}
func (c *Cursor) Up(n int) {
c.cursorMove(0, n)
}
func (c *Cursor) Down(n int) {
c.cursorMove(0, -1*n)
}
func (c *Cursor) Forward(n int) {
c.cursorMove(n, 0)
}
func (c *Cursor) Back(n int) {
c.cursorMove(-1*n, 0)
}
// save the cursor location
func (c *Cursor) Save() {
cursorLoc, _ = c.Location(nil)
}
func (c *Cursor) Restore() {
handle := syscall.Handle(c.Out.Fd())
// restore it to the original position
procSetConsoleCursorPosition.Call(uintptr(handle), uintptr(*(*int32)(unsafe.Pointer(&cursorLoc))))
}
func (cur Coord) CursorIsAtLineEnd(size *Coord) bool {
return cur.X == size.X
}
func (cur Coord) CursorIsAtLineBegin() bool {
return cur.X == 0
}
func (c *Cursor) cursorMove(x int, y int) {
handle := syscall.Handle(c.Out.Fd())
var csbi consoleScreenBufferInfo
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
var cursor Coord
cursor.X = csbi.cursorPosition.X + Short(x)
cursor.Y = csbi.cursorPosition.Y + Short(y)
procSetConsoleCursorPosition.Call(uintptr(handle), uintptr(*(*int32)(unsafe.Pointer(&cursor))))
}
func (c *Cursor) NextLine(n int) {
c.Up(n)
c.HorizontalAbsolute(0)
}
func (c *Cursor) PreviousLine(n int) {
c.Down(n)
c.HorizontalAbsolute(0)
}
// for comparability purposes between windows
// in windows we don't have to print out a new line
func (c *Cursor) MoveNextLine(cur Coord, terminalSize *Coord) {
c.NextLine(1)
}
func (c *Cursor) HorizontalAbsolute(x int) {
handle := syscall.Handle(c.Out.Fd())
var csbi consoleScreenBufferInfo
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
var cursor Coord
cursor.X = Short(x)
cursor.Y = csbi.cursorPosition.Y
if csbi.size.X < cursor.X {
cursor.X = csbi.size.X
}
procSetConsoleCursorPosition.Call(uintptr(handle), uintptr(*(*int32)(unsafe.Pointer(&cursor))))
}
func (c *Cursor) Show() {
handle := syscall.Handle(c.Out.Fd())
var cci consoleCursorInfo
procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci)))
cci.visible = 1
procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci)))
}
func (c *Cursor) Hide() {
handle := syscall.Handle(c.Out.Fd())
var cci consoleCursorInfo
procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci)))
cci.visible = 0
procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci)))
}
func (c *Cursor) Location(buf *bytes.Buffer) (Coord, error) {
handle := syscall.Handle(c.Out.Fd())
var csbi consoleScreenBufferInfo
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
return csbi.cursorPosition, nil
}
func (c *Cursor) Size(buf *bytes.Buffer) (*Coord, error) {
handle := syscall.Handle(c.Out.Fd())
var csbi consoleScreenBufferInfo
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
// windows' coordinate system begins at (0, 0)
csbi.size.X--
csbi.size.Y--
return &csbi.size, nil
}

View File

@@ -0,0 +1,9 @@
package terminal
type EraseLineMode int
const (
ERASE_LINE_END EraseLineMode = iota
ERASE_LINE_START
ERASE_LINE_ALL
)

View File

@@ -0,0 +1,11 @@
// +build !windows
package terminal
import (
"fmt"
)
func EraseLine(out FileWriter, mode EraseLineMode) {
fmt.Fprintf(out, "\x1b[%dK", mode)
}

View File

@@ -0,0 +1,27 @@
package terminal
import (
"syscall"
"unsafe"
)
func EraseLine(out FileWriter, mode EraseLineMode) {
handle := syscall.Handle(out.Fd())
var csbi consoleScreenBufferInfo
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
var w uint32
var x Short
cursor := csbi.cursorPosition
switch mode {
case ERASE_LINE_END:
x = csbi.size.X
case ERASE_LINE_START:
x = 0
case ERASE_LINE_ALL:
cursor.X = 0
x = csbi.size.X
}
procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(x), uintptr(*(*int32)(unsafe.Pointer(&cursor))), uintptr(unsafe.Pointer(&w)))
}

View File

@@ -0,0 +1,9 @@
package terminal
import (
"errors"
)
var (
InterruptErr = errors.New("interrupt")
)

View File

@@ -0,0 +1,19 @@
// +build !windows
package terminal
import (
"io"
)
// Returns special stdout, which converts escape sequences to Windows API calls
// on Windows environment.
func NewAnsiStdout(out FileWriter) io.Writer {
return out
}
// Returns special stderr, which converts escape sequences to Windows API calls
// on Windows environment.
func NewAnsiStderr(out FileWriter) io.Writer {
return out
}

View File

@@ -0,0 +1,227 @@
package terminal
import (
"bytes"
"fmt"
"io"
"strconv"
"strings"
"syscall"
"unsafe"
"github.com/mattn/go-isatty"
)
var (
cursorFunctions = map[rune]func(c *Cursor) func(int){
'A': func(c *Cursor) func(int) { return c.Up },
'B': func(c *Cursor) func(int) { return c.Down },
'C': func(c *Cursor) func(int) { return c.Forward },
'D': func(c *Cursor) func(int) { return c.Back },
'E': func(c *Cursor) func(int) { return c.NextLine },
'F': func(c *Cursor) func(int) { return c.PreviousLine },
'G': func(c *Cursor) func(int) { return c.HorizontalAbsolute },
}
)
const (
foregroundBlue = 0x1
foregroundGreen = 0x2
foregroundRed = 0x4
foregroundIntensity = 0x8
foregroundMask = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity)
backgroundBlue = 0x10
backgroundGreen = 0x20
backgroundRed = 0x40
backgroundIntensity = 0x80
backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity)
)
type Writer struct {
out FileWriter
handle syscall.Handle
orgAttr word
}
func NewAnsiStdout(out FileWriter) io.Writer {
var csbi consoleScreenBufferInfo
if !isatty.IsTerminal(out.Fd()) {
return out
}
handle := syscall.Handle(out.Fd())
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
return &Writer{out: out, handle: handle, orgAttr: csbi.attributes}
}
func NewAnsiStderr(out FileWriter) io.Writer {
var csbi consoleScreenBufferInfo
if !isatty.IsTerminal(out.Fd()) {
return out
}
handle := syscall.Handle(out.Fd())
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
return &Writer{out: out, handle: handle, orgAttr: csbi.attributes}
}
func (w *Writer) Write(data []byte) (n int, err error) {
r := bytes.NewReader(data)
for {
ch, size, err := r.ReadRune()
if err != nil {
break
}
n += size
switch ch {
case '\x1b':
size, err = w.handleEscape(r)
n += size
if err != nil {
break
}
default:
fmt.Fprint(w.out, string(ch))
}
}
return
}
func (w *Writer) handleEscape(r *bytes.Reader) (n int, err error) {
buf := make([]byte, 0, 10)
buf = append(buf, "\x1b"...)
// Check '[' continues after \x1b
ch, size, err := r.ReadRune()
if err != nil {
fmt.Fprint(w.out, string(buf))
return
}
n += size
if ch != '[' {
fmt.Fprint(w.out, string(buf))
return
}
// Parse escape code
var code rune
argBuf := make([]byte, 0, 10)
for {
ch, size, err = r.ReadRune()
if err != nil {
fmt.Fprint(w.out, string(buf))
return
}
n += size
if ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') {
code = ch
break
}
argBuf = append(argBuf, string(ch)...)
}
w.applyEscapeCode(buf, string(argBuf), code)
return
}
func (w *Writer) applyEscapeCode(buf []byte, arg string, code rune) {
c := &Cursor{Out: w.out}
switch arg + string(code) {
case "?25h":
c.Show()
return
case "?25l":
c.Hide()
return
}
if f, ok := cursorFunctions[code]; ok {
if n, err := strconv.Atoi(arg); err == nil {
f(c)(n)
return
}
}
switch code {
case 'm':
w.applySelectGraphicRendition(arg)
default:
buf = append(buf, string(code)...)
fmt.Fprint(w.out, string(buf))
}
}
// Original implementation: https://github.com/mattn/go-colorable
func (w *Writer) applySelectGraphicRendition(arg string) {
if arg == "" {
procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(w.orgAttr))
return
}
var csbi consoleScreenBufferInfo
procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
attr := csbi.attributes
for _, param := range strings.Split(arg, ";") {
n, err := strconv.Atoi(param)
if err != nil {
continue
}
switch {
case n == 0 || n == 100:
attr = w.orgAttr
case 1 <= n && n <= 5:
attr |= foregroundIntensity
case 30 <= n && n <= 37:
attr = (attr & backgroundMask)
if (n-30)&1 != 0 {
attr |= foregroundRed
}
if (n-30)&2 != 0 {
attr |= foregroundGreen
}
if (n-30)&4 != 0 {
attr |= foregroundBlue
}
case 40 <= n && n <= 47:
attr = (attr & foregroundMask)
if (n-40)&1 != 0 {
attr |= backgroundRed
}
if (n-40)&2 != 0 {
attr |= backgroundGreen
}
if (n-40)&4 != 0 {
attr |= backgroundBlue
}
case 90 <= n && n <= 97:
attr = (attr & backgroundMask)
attr |= foregroundIntensity
if (n-90)&1 != 0 {
attr |= foregroundRed
}
if (n-90)&2 != 0 {
attr |= foregroundGreen
}
if (n-90)&4 != 0 {
attr |= foregroundBlue
}
case 100 <= n && n <= 107:
attr = (attr & foregroundMask)
attr |= backgroundIntensity
if (n-100)&1 != 0 {
attr |= backgroundRed
}
if (n-100)&2 != 0 {
attr |= backgroundGreen
}
if (n-100)&4 != 0 {
attr |= backgroundBlue
}
}
}
procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(attr))
}

View File

@@ -0,0 +1,312 @@
package terminal
import (
"fmt"
"unicode"
)
type RuneReader struct {
stdio Stdio
cursor *Cursor
state runeReaderState
}
func NewRuneReader(stdio Stdio) *RuneReader {
return &RuneReader{
stdio: stdio,
state: newRuneReaderState(stdio.In),
}
}
func (rr *RuneReader) printChar(char rune, mask rune) {
// if we don't need to mask the input
if mask == 0 {
// just print the character the user pressed
fmt.Fprintf(rr.stdio.Out, "%c", char)
} else {
// otherwise print the mask we were given
fmt.Fprintf(rr.stdio.Out, "%c", mask)
}
}
func (rr *RuneReader) ReadLine(mask rune) ([]rune, error) {
line := []rune{}
// we only care about horizontal displacements from the origin so start counting at 0
index := 0
cursor := &Cursor{
In: rr.stdio.In,
Out: rr.stdio.Out,
}
// we get the terminal width and height (if resized after this point the property might become invalid)
terminalSize, _ := cursor.Size(rr.Buffer())
for {
// wait for some input
r, _, err := rr.ReadRune()
if err != nil {
return line, err
}
// we set the current location of the cursor and update it after every key press
cursorCurrent, err := cursor.Location(rr.Buffer())
// if the user pressed enter or some other newline/termination like ctrl+d
if r == '\r' || r == '\n' || r == KeyEndTransmission {
// delete what's printed out on the console screen (cleanup)
for index > 0 {
if cursorCurrent.CursorIsAtLineBegin() {
EraseLine(rr.stdio.Out, ERASE_LINE_END)
cursor.PreviousLine(1)
cursor.Forward(int(terminalSize.X))
cursorCurrent.X = terminalSize.X
cursorCurrent.Y--
} else {
cursor.Back(1)
cursorCurrent.X--
}
index--
}
// move the cursor the a new line
cursor.MoveNextLine(cursorCurrent, terminalSize)
// we're done processing the input
return line, nil
}
// if the user interrupts (ie with ctrl+c)
if r == KeyInterrupt {
// go to the beginning of the next line
fmt.Fprint(rr.stdio.Out, "\r\n")
// we're done processing the input, and treat interrupt like an error
return line, InterruptErr
}
// allow for backspace/delete editing of inputs
if r == KeyBackspace || r == KeyDelete {
// and we're not at the beginning of the line
if index > 0 && len(line) > 0 {
// if we are at the end of the word
if index == len(line) {
// just remove the last letter from the internal representation
line = line[:len(line)-1]
// go back one
if cursorCurrent.X == 1 {
cursor.PreviousLine(1)
cursor.Forward(int(terminalSize.X))
} else {
cursor.Back(1)
}
// clear the rest of the line
EraseLine(rr.stdio.Out, ERASE_LINE_END)
} else {
// we need to remove a character from the middle of the word
// remove the current index from the list
line = append(line[:index-1], line[index:]...)
// save the current position of the cursor, as we have to move the cursor one back to erase the current symbol
// and then move the cursor for each symbol in line[index-1:] to print it out, afterwards we want to restore
// the cursor to its previous location.
cursor.Save()
// clear the rest of the line
cursor.Back(1)
// print what comes after
for _, char := range line[index-1:] {
//Erase symbols which are left over from older print
EraseLine(rr.stdio.Out, ERASE_LINE_END)
// print characters to the new line appropriately
rr.printChar(char, mask)
}
// erase what's left over from last print
if cursorCurrent.Y < terminalSize.Y {
cursor.NextLine(1)
EraseLine(rr.stdio.Out, ERASE_LINE_END)
}
// restore cursor
cursor.Restore()
if cursorCurrent.CursorIsAtLineBegin() {
cursor.PreviousLine(1)
cursor.Forward(int(terminalSize.X))
} else {
cursor.Back(1)
}
}
// decrement the index
index--
} else {
// otherwise the user pressed backspace while at the beginning of the line
soundBell(rr.stdio.Out)
}
// we're done processing this key
continue
}
// if the left arrow is pressed
if r == KeyArrowLeft {
// if we have space to the left
if index > 0 {
//move the cursor to the prev line if necessary
if cursorCurrent.CursorIsAtLineBegin() {
cursor.PreviousLine(1)
cursor.Forward(int(terminalSize.X))
} else {
cursor.Back(1)
}
//decrement the index
index--
} else {
// otherwise we are at the beginning of where we started reading lines
// sound the bell
soundBell(rr.stdio.Out)
}
// we're done processing this key press
continue
}
// if the right arrow is pressed
if r == KeyArrowRight {
// if we have space to the right
if index < len(line) {
// move the cursor to the next line if necessary
if cursorCurrent.CursorIsAtLineEnd(terminalSize) {
cursor.NextLine(1)
} else {
cursor.Forward(1)
}
index++
} else {
// otherwise we are at the end of the word and can't go past
// sound the bell
soundBell(rr.stdio.Out)
}
// we're done processing this key press
continue
}
// the user pressed one of the special keys
if r == SpecialKeyHome {
for index > 0 {
if cursorCurrent.CursorIsAtLineBegin() {
cursor.PreviousLine(1)
cursor.Forward(int(terminalSize.X))
cursorCurrent.X = terminalSize.X
cursorCurrent.Y--
} else {
cursor.Back(1)
cursorCurrent.X--
}
index--
}
continue
// user pressed end
} else if r == SpecialKeyEnd {
for index != len(line) {
if cursorCurrent.CursorIsAtLineEnd(terminalSize) {
cursor.NextLine(1)
cursorCurrent.X = COORDINATE_SYSTEM_BEGIN
cursorCurrent.Y++
} else {
cursor.Forward(1)
cursorCurrent.X++
}
index++
}
continue
// user pressed forward delete key
} else if r == SpecialKeyDelete {
// if index at the end of the line nothing to delete
if index != len(line) {
// save the current position of the cursor, as we have to erase the current symbol
// and then move the cursor for each symbol in line[index:] to print it out, afterwards we want to restore
// the cursor to its previous location.
cursor.Save()
// remove the symbol after the cursor
line = append(line[:index], line[index+1:]...)
// print the updated line
for _, char := range line[index:] {
EraseLine(rr.stdio.Out, ERASE_LINE_END)
// print out the character
rr.printChar(char, mask)
}
// erase what's left on last line
if cursorCurrent.Y < terminalSize.Y {
cursor.NextLine(1)
EraseLine(rr.stdio.Out, ERASE_LINE_END)
}
// restore cursor
cursor.Restore()
if len(line) == 0 || index == len(line) {
EraseLine(rr.stdio.Out, ERASE_LINE_END)
}
}
continue
}
// if the letter is another escape sequence
if unicode.IsControl(r) || r == IgnoreKey {
// ignore it
continue
}
// the user pressed a regular key
// if we are at the end of the line
if index == len(line) {
// just append the character at the end of the line
line = append(line, r)
// save the location of the cursor
index++
// print out the character
rr.printChar(r, mask)
} else {
// we are in the middle of the word so we need to insert the character the user pressed
line = append(line[:index], append([]rune{r}, line[index:]...)...)
// save the current position of the cursor, as we have to move the cursor back to erase the current symbol
// and then move for each symbol in line[index:] to print it out, afterwards we want to restore
// cursor's location to its previous one.
cursor.Save()
EraseLine(rr.stdio.Out, ERASE_LINE_END)
// remove the symbol after the cursor
// print the updated line
for _, char := range line[index:] {
EraseLine(rr.stdio.Out, ERASE_LINE_END)
// print out the character
rr.printChar(char, mask)
cursorCurrent.X++
}
// if we are at the last line, we want to visually insert a new line and append to it.
if cursorCurrent.CursorIsAtLineEnd(terminalSize) && cursorCurrent.Y == terminalSize.Y {
// add a new line to the terminal
fmt.Fprintln(rr.stdio.Out)
// restore the position of the cursor horizontally
cursor.Restore()
// restore the position of the cursor vertically
cursor.Up(1)
} else {
// restore cursor
cursor.Restore()
}
// check if cursor needs to move to next line
cursorCurrent, _ = cursor.Location(rr.Buffer())
if cursorCurrent.CursorIsAtLineEnd(terminalSize) {
cursor.NextLine(1)
} else {
cursor.Forward(1)
}
// increment the index
index++
}
}
}

View File

@@ -0,0 +1,13 @@
// copied from: https://github.com/golang/crypto/blob/master/ssh/terminal/util_bsd.go
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin dragonfly freebsd netbsd openbsd
package terminal
import "syscall"
const ioctlReadTermios = syscall.TIOCGETA
const ioctlWriteTermios = syscall.TIOCSETA

View File

@@ -0,0 +1,12 @@
// copied from https://github.com/golang/crypto/blob/master/ssh/terminal/util_linux.go
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package terminal
// These constants are declared here, rather than importing
// them from the syscall package as some syscall packages, even
// on linux, for example gccgo, do not declare them.
const ioctlReadTermios = 0x5401 // syscall.TCGETS
const ioctlWriteTermios = 0x5402 // syscall.TCSETS

View File

@@ -0,0 +1,111 @@
// +build !windows
// The terminal mode manipulation code is derived heavily from:
// https://github.com/golang/crypto/blob/master/ssh/terminal/util.go:
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package terminal
import (
"bufio"
"bytes"
"fmt"
"syscall"
"unsafe"
)
type runeReaderState struct {
term syscall.Termios
reader *bufio.Reader
buf *bytes.Buffer
}
func newRuneReaderState(input FileReader) runeReaderState {
buf := new(bytes.Buffer)
return runeReaderState{
reader: bufio.NewReader(&BufferedReader{
In: input,
Buffer: buf,
}),
buf: buf,
}
}
func (rr *RuneReader) Buffer() *bytes.Buffer {
return rr.state.buf
}
// For reading runes we just want to disable echo.
func (rr *RuneReader) SetTermMode() error {
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(rr.stdio.In.Fd()), ioctlReadTermios, uintptr(unsafe.Pointer(&rr.state.term)), 0, 0, 0); err != 0 {
return err
}
newState := rr.state.term
newState.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(rr.stdio.In.Fd()), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 {
return err
}
return nil
}
func (rr *RuneReader) RestoreTermMode() error {
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(rr.stdio.In.Fd()), ioctlWriteTermios, uintptr(unsafe.Pointer(&rr.state.term)), 0, 0, 0); err != 0 {
return err
}
return nil
}
func (rr *RuneReader) ReadRune() (rune, int, error) {
r, size, err := rr.state.reader.ReadRune()
if err != nil {
return r, size, err
}
// parse ^[ sequences to look for arrow keys
if r == '\033' {
if rr.state.reader.Buffered() == 0 {
// no more characters so must be `Esc` key
return KeyEscape, 1, nil
}
r, size, err = rr.state.reader.ReadRune()
if err != nil {
return r, size, err
}
if r != '[' {
return r, size, fmt.Errorf("Unexpected Escape Sequence: %q", []rune{'\033', r})
}
r, size, err = rr.state.reader.ReadRune()
if err != nil {
return r, size, err
}
switch r {
case 'D':
return KeyArrowLeft, 1, nil
case 'C':
return KeyArrowRight, 1, nil
case 'A':
return KeyArrowUp, 1, nil
case 'B':
return KeyArrowDown, 1, nil
case 'H': // Home button
return SpecialKeyHome, 1, nil
case 'F': // End button
return SpecialKeyEnd, 1, nil
case '3': // Delete Button
// discard the following '~' key from buffer
rr.state.reader.Discard(1)
return SpecialKeyDelete, 1, nil
default:
// discard the following '~' key from buffer
rr.state.reader.Discard(1)
return IgnoreKey, 1, nil
}
return r, size, fmt.Errorf("Unknown Escape Sequence: %q", []rune{'\033', '[', r})
}
return r, size, err
}

View File

@@ -0,0 +1,142 @@
package terminal
import (
"bytes"
"syscall"
"unsafe"
)
var (
dll = syscall.NewLazyDLL("kernel32.dll")
setConsoleMode = dll.NewProc("SetConsoleMode")
getConsoleMode = dll.NewProc("GetConsoleMode")
readConsoleInput = dll.NewProc("ReadConsoleInputW")
)
const (
EVENT_KEY = 0x0001
// key codes for arrow keys
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
VK_DELETE = 0x2E
VK_END = 0x23
VK_HOME = 0x24
VK_LEFT = 0x25
VK_UP = 0x26
VK_RIGHT = 0x27
VK_DOWN = 0x28
RIGHT_CTRL_PRESSED = 0x0004
LEFT_CTRL_PRESSED = 0x0008
ENABLE_ECHO_INPUT uint32 = 0x0004
ENABLE_LINE_INPUT uint32 = 0x0002
ENABLE_PROCESSED_INPUT uint32 = 0x0001
)
type inputRecord struct {
eventType uint16
padding uint16
event [16]byte
}
type keyEventRecord struct {
bKeyDown int32
wRepeatCount uint16
wVirtualKeyCode uint16
wVirtualScanCode uint16
unicodeChar uint16
wdControlKeyState uint32
}
type runeReaderState struct {
term uint32
}
func newRuneReaderState(input FileReader) runeReaderState {
return runeReaderState{}
}
func (rr *RuneReader) Buffer() *bytes.Buffer {
return nil
}
func (rr *RuneReader) SetTermMode() error {
r, _, err := getConsoleMode.Call(uintptr(rr.stdio.In.Fd()), uintptr(unsafe.Pointer(&rr.state.term)))
// windows return 0 on error
if r == 0 {
return err
}
newState := rr.state.term
newState &^= ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT
r, _, err = setConsoleMode.Call(uintptr(rr.stdio.In.Fd()), uintptr(newState))
// windows return 0 on error
if r == 0 {
return err
}
return nil
}
func (rr *RuneReader) RestoreTermMode() error {
r, _, err := setConsoleMode.Call(uintptr(rr.stdio.In.Fd()), uintptr(rr.state.term))
// windows return 0 on error
if r == 0 {
return err
}
return nil
}
func (rr *RuneReader) ReadRune() (rune, int, error) {
ir := &inputRecord{}
bytesRead := 0
for {
rv, _, e := readConsoleInput.Call(rr.stdio.In.Fd(), uintptr(unsafe.Pointer(ir)), 1, uintptr(unsafe.Pointer(&bytesRead)))
// windows returns non-zero to indicate success
if rv == 0 && e != nil {
return 0, 0, e
}
if ir.eventType != EVENT_KEY {
continue
}
// the event data is really a c struct union, so here we have to do an usafe
// cast to put the data into the keyEventRecord (since we have already verified
// above that this event does correspond to a key event
key := (*keyEventRecord)(unsafe.Pointer(&ir.event[0]))
// we only care about key down events
if key.bKeyDown == 0 {
continue
}
if key.wdControlKeyState&(LEFT_CTRL_PRESSED|RIGHT_CTRL_PRESSED) != 0 && key.unicodeChar == 'C' {
return KeyInterrupt, bytesRead, nil
}
// not a normal character so look up the input sequence from the
// virtual key code mappings (VK_*)
if key.unicodeChar == 0 {
switch key.wVirtualKeyCode {
case VK_DOWN:
return KeyArrowDown, bytesRead, nil
case VK_LEFT:
return KeyArrowLeft, bytesRead, nil
case VK_RIGHT:
return KeyArrowRight, bytesRead, nil
case VK_UP:
return KeyArrowUp, bytesRead, nil
case VK_DELETE:
return SpecialKeyDelete, bytesRead, nil
case VK_HOME:
return SpecialKeyHome, bytesRead, nil
case VK_END:
return SpecialKeyEnd, bytesRead, nil
default:
// not a virtual key that we care about so just continue on to
// the next input key
continue
}
}
r := rune(key.unicodeChar)
return r, bytesRead, nil
}
}

View File

@@ -0,0 +1,30 @@
package terminal
import (
"fmt"
"io"
)
const (
KeyArrowLeft = '\x02'
KeyArrowRight = '\x06'
KeyArrowUp = '\x10'
KeyArrowDown = '\x0e'
KeySpace = ' '
KeyEnter = '\r'
KeyBackspace = '\b'
KeyDelete = '\x7f'
KeyInterrupt = '\x03'
KeyEndTransmission = '\x04'
KeyEscape = '\x1b'
KeyDeleteWord = '\x17' // Ctrl+W
KeyDeleteLine = '\x18' // Ctrl+X
SpecialKeyHome = '\x01'
SpecialKeyEnd = '\x11'
SpecialKeyDelete = '\x12'
IgnoreKey = '\000'
)
func soundBell(out io.Writer) {
fmt.Fprint(out, "\a")
}

View File

@@ -0,0 +1,24 @@
package terminal
import (
"io"
)
// Stdio is the standard input/output the terminal reads/writes with.
type Stdio struct {
In FileReader
Out FileWriter
Err io.Writer
}
// FileWriter provides a minimal interface for Stdin.
type FileWriter interface {
io.Writer
Fd() uintptr
}
// FileReader provides a minimal interface for Stdout.
type FileReader interface {
io.Reader
Fd() uintptr
}

View File

@@ -0,0 +1,39 @@
package terminal
import (
"syscall"
)
var (
kernel32 = syscall.NewLazyDLL("kernel32.dll")
procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute")
procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition")
procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW")
procGetConsoleCursorInfo = kernel32.NewProc("GetConsoleCursorInfo")
procSetConsoleCursorInfo = kernel32.NewProc("SetConsoleCursorInfo")
)
type wchar uint16
type dword uint32
type word uint16
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
}

View File

@@ -0,0 +1,8 @@
package terminal
type Short int16
type Coord struct {
X Short
Y Short
}