mirror of
https://github.com/Threnklyn/jira.git
synced 2026-06-07 13:33:32 +02:00
update for github.com/AlecAivazis/survey => gopkg.in/AlecAivazis/survey.v1 package
This commit is contained in:
+134
@@ -0,0 +1,134 @@
|
||||
// +build !windows
|
||||
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CursorUp moves the cursor n cells to up.
|
||||
func CursorUp(n int) {
|
||||
fmt.Printf("\x1b[%dA", n)
|
||||
}
|
||||
|
||||
// CursorDown moves the cursor n cells to down.
|
||||
func CursorDown(n int) {
|
||||
fmt.Printf("\x1b[%dB", n)
|
||||
}
|
||||
|
||||
// CursorForward moves the cursor n cells to right.
|
||||
func CursorForward(n int) {
|
||||
fmt.Printf("\x1b[%dC", n)
|
||||
}
|
||||
|
||||
// CursorBack moves the cursor n cells to left.
|
||||
func CursorBack(n int) {
|
||||
fmt.Printf("\x1b[%dD", n)
|
||||
}
|
||||
|
||||
// CursorNextLine moves cursor to beginning of the line n lines down.
|
||||
func CursorNextLine(n int) {
|
||||
fmt.Printf("\x1b[%dE", n)
|
||||
}
|
||||
|
||||
// CursorPreviousLine moves cursor to beginning of the line n lines up.
|
||||
func CursorPreviousLine(n int) {
|
||||
fmt.Printf("\x1b[%dF", n)
|
||||
}
|
||||
|
||||
// CursorHorizontalAbsolute moves cursor horizontally to x.
|
||||
func CursorHorizontalAbsolute(x int) {
|
||||
fmt.Printf("\x1b[%dG", x)
|
||||
}
|
||||
|
||||
// CursorShow shows the cursor.
|
||||
func CursorShow() {
|
||||
fmt.Print("\x1b[?25h")
|
||||
}
|
||||
|
||||
// CursorHide hide the cursor.
|
||||
func CursorHide() {
|
||||
fmt.Print("\x1b[?25l")
|
||||
}
|
||||
|
||||
// CursorMove moves the cursor to a specific x,y location.
|
||||
func CursorMove(x int, y int) {
|
||||
fmt.Printf("\x1b[%d;%df", x, y)
|
||||
}
|
||||
|
||||
// CursorLocation returns the current location of the cursor in the terminal
|
||||
func CursorLocation() (*Coord, error) {
|
||||
// print the escape sequence to recieve the position in our stdin
|
||||
fmt.Print("\x1b[6n")
|
||||
|
||||
// read from stdin to get the response
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
// spec says we read 'til R, so do that
|
||||
text, err := reader.ReadSlice('R')
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// spec also says they're split by ;, so do that too
|
||||
if strings.Contains(string(text), ";") {
|
||||
// a regex to parse the output of the ansi code
|
||||
re := regexp.MustCompile(`\d+;\d+`)
|
||||
line := re.FindString(string(text))
|
||||
|
||||
// find the column and rows embedded in the string
|
||||
coords := strings.Split(line, ";")
|
||||
|
||||
// try to cast the col number to an int
|
||||
col, err := strconv.Atoi(coords[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// try to cast the row number to an int
|
||||
row, err := strconv.Atoi(coords[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// return the coordinate object with the col and row we calculated
|
||||
return &Coord{Short(col), Short(row)}, nil
|
||||
}
|
||||
|
||||
// it didn't work so return an error
|
||||
return nil, fmt.Errorf("could not compute the cursor position using ascii escape sequences")
|
||||
}
|
||||
|
||||
// Size returns the height and width of the terminal.
|
||||
func Size() (*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
|
||||
|
||||
// save the current location of the cursor
|
||||
origin, err := CursorLocation()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// move the cursor to the very bottom of the terminal
|
||||
CursorMove(999, 999)
|
||||
|
||||
// ask for the current location
|
||||
bottom, err := CursorLocation()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// move back where we began
|
||||
CursorUp(int(bottom.Y - origin.Y))
|
||||
CursorHorizontalAbsolute(int(origin.X))
|
||||
|
||||
// sice the bottom was calcuated in the lower right corner, it
|
||||
// is the dimensions we are looking for
|
||||
return bottom, nil
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func CursorUp(n int) {
|
||||
cursorMove(0, n)
|
||||
}
|
||||
|
||||
func CursorDown(n int) {
|
||||
cursorMove(0, -1*n)
|
||||
}
|
||||
|
||||
func CursorForward(n int) {
|
||||
cursorMove(n, 0)
|
||||
}
|
||||
|
||||
func CursorBack(n int) {
|
||||
cursorMove(-1*n, 0)
|
||||
}
|
||||
|
||||
func cursorMove(x int, y int) {
|
||||
handle := syscall.Handle(os.Stdout.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 CursorNextLine(n int) {
|
||||
CursorUp(n)
|
||||
CursorHorizontalAbsolute(0)
|
||||
}
|
||||
|
||||
func CursorPreviousLine(n int) {
|
||||
CursorDown(n)
|
||||
CursorHorizontalAbsolute(0)
|
||||
}
|
||||
|
||||
func CursorHorizontalAbsolute(x int) {
|
||||
handle := syscall.Handle(os.Stdout.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 CursorShow() {
|
||||
handle := syscall.Handle(os.Stdout.Fd())
|
||||
|
||||
var cci consoleCursorInfo
|
||||
procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci)))
|
||||
cci.visible = 1
|
||||
|
||||
procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci)))
|
||||
}
|
||||
|
||||
func CursorHide() {
|
||||
handle := syscall.Handle(os.Stdout.Fd())
|
||||
|
||||
var cci consoleCursorInfo
|
||||
procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci)))
|
||||
cci.visible = 0
|
||||
|
||||
procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci)))
|
||||
}
|
||||
|
||||
func CursorLocation() (Coord, error) {
|
||||
handle := syscall.Handle(os.Stdout.Fd())
|
||||
|
||||
var csbi consoleScreenBufferInfo
|
||||
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
|
||||
return csbi.cursorPosition, nil
|
||||
}
|
||||
|
||||
func Size() (Coord, error) {
|
||||
handle := syscall.Handle(os.Stdout.Fd())
|
||||
|
||||
var csbi consoleScreenBufferInfo
|
||||
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
|
||||
return csbi.size, nil
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package terminal
|
||||
|
||||
type EraseLineMode int
|
||||
|
||||
const (
|
||||
ERASE_LINE_END EraseLineMode = iota
|
||||
ERASE_LINE_START
|
||||
ERASE_LINE_ALL
|
||||
)
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// +build !windows
|
||||
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func EraseLine(mode EraseLineMode) {
|
||||
fmt.Printf("\x1b[%dK", mode)
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func EraseLine(mode EraseLineMode) {
|
||||
handle := syscall.Handle(os.Stdout.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)))
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
// +build !windows
|
||||
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Returns special stdout, which converts escape sequences to Windows API calls
|
||||
// on Windows environment.
|
||||
func NewAnsiStdout() io.Writer {
|
||||
return os.Stdout
|
||||
}
|
||||
|
||||
// Returns special stderr, which converts escape sequences to Windows API calls
|
||||
// on Windows environment.
|
||||
func NewAnsiStderr() io.Writer {
|
||||
return os.Stderr
|
||||
}
|
||||
+204
@@ -0,0 +1,204 @@
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
var (
|
||||
singleArgFunctions = map[rune]func(int){
|
||||
'A': CursorUp,
|
||||
'B': CursorDown,
|
||||
'C': CursorForward,
|
||||
'D': CursorBack,
|
||||
'E': CursorNextLine,
|
||||
'F': CursorPreviousLine,
|
||||
'G': CursorHorizontalAbsolute,
|
||||
}
|
||||
)
|
||||
|
||||
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 io.Writer
|
||||
handle syscall.Handle
|
||||
orgAttr word
|
||||
}
|
||||
|
||||
func NewAnsiStdout() io.Writer {
|
||||
var csbi consoleScreenBufferInfo
|
||||
out := os.Stdout
|
||||
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() io.Writer {
|
||||
var csbi consoleScreenBufferInfo
|
||||
out := os.Stderr
|
||||
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) {
|
||||
switch arg + string(code) {
|
||||
case "?25h":
|
||||
CursorShow()
|
||||
return
|
||||
case "?25l":
|
||||
CursorHide()
|
||||
return
|
||||
}
|
||||
|
||||
if f, ok := singleArgFunctions[code]; ok {
|
||||
if n, err := strconv.Atoi(arg); err == nil {
|
||||
f(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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(attr))
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
Stdout = NewAnsiStdout()
|
||||
)
|
||||
|
||||
// Print prints given arguments with escape sequence conversion for windows.
|
||||
func Print(a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprint(Stdout, a...)
|
||||
}
|
||||
|
||||
// Printf prints a given format with escape sequence conversion for windows.
|
||||
func Printf(format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintf(Stdout, format, a...)
|
||||
}
|
||||
|
||||
// Println prints given arguments with newline and escape sequence conversion
|
||||
// for windows.
|
||||
func Println(a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintln(Stdout, a...)
|
||||
}
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type RuneReader struct {
|
||||
Input *os.File
|
||||
|
||||
state runeReaderState
|
||||
}
|
||||
|
||||
func NewRuneReader(input *os.File) *RuneReader {
|
||||
return &RuneReader{
|
||||
Input: input,
|
||||
state: newRuneReaderState(input),
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
for {
|
||||
// wait for some input
|
||||
r, _, err := rr.ReadRune()
|
||||
if err != nil {
|
||||
return line, err
|
||||
}
|
||||
|
||||
// if the user pressed enter or some other newline/termination like ctrl+d
|
||||
if r == '\r' || r == '\n' || r == KeyEndTransmission {
|
||||
// go to the beginning of the next line
|
||||
Print("\r\n")
|
||||
|
||||
// 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
|
||||
Print("\r\n")
|
||||
|
||||
// we're done processing the input, and treat interrupt like an error
|
||||
return line, fmt.Errorf("interrupt")
|
||||
}
|
||||
|
||||
// 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
|
||||
CursorBack(1)
|
||||
|
||||
// clear the rest of the line
|
||||
EraseLine(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:]...)
|
||||
|
||||
// go back one space so we can clear the rest
|
||||
CursorBack(1)
|
||||
|
||||
// clear the rest of the line
|
||||
EraseLine(ERASE_LINE_END)
|
||||
|
||||
// print what comes after
|
||||
Print(string(line[index-1:]))
|
||||
|
||||
// leave the cursor where the user left it
|
||||
CursorBack(len(line) - index + 1)
|
||||
}
|
||||
|
||||
// decrement the index
|
||||
index--
|
||||
} else {
|
||||
// otherwise the user pressed backspace while at the beginning of the line
|
||||
soundBell()
|
||||
}
|
||||
|
||||
// we're done processing this key
|
||||
continue
|
||||
}
|
||||
|
||||
// if the left arrow is pressed
|
||||
if r == KeyArrowLeft {
|
||||
// and we have space to the left
|
||||
if index > 0 {
|
||||
// move the cursor to the left
|
||||
CursorBack(1)
|
||||
// decrement the index
|
||||
index--
|
||||
|
||||
} else {
|
||||
// otherwise we are at the beginning of where we started reading lines
|
||||
// sound the bell
|
||||
soundBell()
|
||||
}
|
||||
|
||||
// we're done processing this key press
|
||||
continue
|
||||
}
|
||||
|
||||
// if the right arrow is pressed
|
||||
if r == KeyArrowRight {
|
||||
// and we have space to the right of the word
|
||||
if index < len(line) {
|
||||
// move the cursor to the right
|
||||
CursorForward(1)
|
||||
// increment the index
|
||||
index++
|
||||
|
||||
} else {
|
||||
// otherwise we are at the end of the word and can't go past
|
||||
// sound the bell
|
||||
soundBell()
|
||||
}
|
||||
|
||||
// we're done processing this key press
|
||||
continue
|
||||
}
|
||||
|
||||
// if the letter is another escape sequence
|
||||
if unicode.IsControl(r) {
|
||||
// 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)
|
||||
// increment the location counter
|
||||
index++
|
||||
|
||||
// if we don't need to mask the input
|
||||
if mask == 0 {
|
||||
// just print the character the user pressed
|
||||
Printf("%c", r)
|
||||
} else {
|
||||
// otherwise print the mask we were given
|
||||
Printf("%c", 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:]...)...)
|
||||
|
||||
// visually insert the character by deleting the rest of the line
|
||||
EraseLine(ERASE_LINE_END)
|
||||
|
||||
// print the rest of the word after
|
||||
for _, char := range line[index:] {
|
||||
// if we don't need to mask the input
|
||||
if mask == 0 {
|
||||
// just print the character the user pressed
|
||||
Printf("%c", char)
|
||||
} else {
|
||||
// otherwise print the mask we were given
|
||||
Printf("%c", mask)
|
||||
}
|
||||
}
|
||||
|
||||
// leave the cursor where the user left it
|
||||
CursorBack(len(line) - index - 1)
|
||||
|
||||
// accomodate the new letter in our counter
|
||||
index++
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
@@ -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
|
||||
+12
@@ -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
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
// +build !windows
|
||||
|
||||
// The terminal mode manipluation 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"
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type runeReaderState struct {
|
||||
term syscall.Termios
|
||||
buf *bufio.Reader
|
||||
}
|
||||
|
||||
func newRuneReaderState(input *os.File) runeReaderState {
|
||||
return runeReaderState{
|
||||
buf: bufio.NewReader(input),
|
||||
}
|
||||
}
|
||||
|
||||
// For reading runes we just want to disable echo.
|
||||
func (rr *RuneReader) SetTermMode() error {
|
||||
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(rr.Input.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.Input.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.Input.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.buf.ReadRune()
|
||||
if err != nil {
|
||||
return r, size, err
|
||||
}
|
||||
// parse ^[ sequences to look for arrow keys
|
||||
if r == '\033' {
|
||||
r, size, err = rr.state.buf.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.buf.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
|
||||
}
|
||||
return r, size, fmt.Errorf("Unknown Escape Sequence: %q", []rune{'\033', '[', r})
|
||||
}
|
||||
return r, size, err
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"os"
|
||||
"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_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 *os.File) runeReaderState {
|
||||
return runeReaderState{}
|
||||
}
|
||||
|
||||
func (rr *RuneReader) SetTermMode() error {
|
||||
r, _, err := getConsoleMode.Call(uintptr(rr.Input.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.Input.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.Input.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.Input.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
|
||||
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
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package terminal
|
||||
|
||||
const (
|
||||
KeyArrowLeft = '\x02'
|
||||
KeyArrowRight = '\x06'
|
||||
KeyArrowUp = '\x10'
|
||||
KeyArrowDown = '\x0e'
|
||||
KeySpace = ' '
|
||||
KeyEnter = '\r'
|
||||
KeyBackspace = '\b'
|
||||
KeyDelete = '\x7f'
|
||||
KeyInterrupt = '\x03'
|
||||
KeyEndTransmission = '\x04'
|
||||
)
|
||||
|
||||
func soundBell() {
|
||||
Print("\a")
|
||||
}
|
||||
+39
@@ -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
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package terminal
|
||||
|
||||
type Short int16
|
||||
|
||||
type Coord struct {
|
||||
X Short
|
||||
Y Short
|
||||
}
|
||||
Reference in New Issue
Block a user