Initial commit
This commit is contained in:
commit
8b537d30a4
|
@ -0,0 +1,357 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"tildegit.org/sloum/nums/qline"
|
||||
"tildegit.org/sloum/nums/termios"
|
||||
)
|
||||
|
||||
const (
|
||||
add int = iota
|
||||
sub
|
||||
mul
|
||||
div
|
||||
pow
|
||||
rnd
|
||||
flr
|
||||
cil
|
||||
abs
|
||||
inv
|
||||
sqt
|
||||
)
|
||||
|
||||
type stack struct {
|
||||
ptr int
|
||||
data [100]float64
|
||||
}
|
||||
|
||||
func (s *stack) Push(v float64) {
|
||||
if s.ptr >= 99 {
|
||||
PrintError("Stack overflow")
|
||||
s.Clear()
|
||||
return
|
||||
}
|
||||
s.ptr++
|
||||
s.data[s.ptr] = v
|
||||
}
|
||||
|
||||
func (s *stack) Pop() (float64, error) {
|
||||
if s.ptr < 0 {
|
||||
PrintError("Stack underflow")
|
||||
s.Clear()
|
||||
return 0.0, fmt.Errorf("Stack underflow")
|
||||
}
|
||||
s.ptr--
|
||||
return s.data[s.ptr+1], nil
|
||||
}
|
||||
|
||||
func (s *stack) Clear() {
|
||||
s.ptr = -1
|
||||
}
|
||||
|
||||
func (s stack) Len() int {
|
||||
return s.ptr+1
|
||||
}
|
||||
|
||||
func (s *stack) Add() {
|
||||
n2, err := s.Pop()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
n1, err := s.Pop()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.Push(n1+n2)
|
||||
}
|
||||
|
||||
func (s *stack) Sub() {
|
||||
n2, err := s.Pop()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
n1, err := s.Pop()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.Push(n1-n2)
|
||||
}
|
||||
|
||||
func (s *stack) Mul() {
|
||||
n2, err := s.Pop()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
n1, err := s.Pop()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.Push(n1*n2)
|
||||
}
|
||||
|
||||
func (s *stack) Div() {
|
||||
n2, err := s.Pop()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
n1, err := s.Pop()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.Push(n1/n2)
|
||||
}
|
||||
|
||||
func (s *stack) Pow() {
|
||||
exp, err := s.Pop()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
base, err := s.Pop()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.Push(math.Pow(base, exp))
|
||||
}
|
||||
|
||||
|
||||
func (s *stack) Floor() {
|
||||
n, err := s.Pop()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.Push(math.Floor(n))
|
||||
}
|
||||
|
||||
func (s *stack) Ceil() {
|
||||
n, err := s.Pop()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.Push(math.Ceil(n))
|
||||
}
|
||||
|
||||
func (s *stack) Round() {
|
||||
decimals, err := s.Pop()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
num, err := s.Pop()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.Push(math.Round(num*math.Pow(10, decimals)) / math.Pow(10, decimals))
|
||||
}
|
||||
|
||||
func (s *stack) Abs() {
|
||||
n, err := s.Pop()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if n < 0 {
|
||||
s.Push(n*-1)
|
||||
} else {
|
||||
s.Push(n)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stack) Inv() {
|
||||
n, err := s.Pop()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.Push(n*-1)
|
||||
}
|
||||
|
||||
func (s *stack) Sqt() {
|
||||
n, err := s.Pop()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.Push(math.Sqrt(n))
|
||||
}
|
||||
|
||||
func (s *stack) Dup() {
|
||||
val, err := s.Pop()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.Push(val)
|
||||
s.Push(val)
|
||||
}
|
||||
|
||||
func (s *stack) Ovr() {
|
||||
tos, err := s.Pop()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cpy, err := s.Pop()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.Push(cpy)
|
||||
s.Push(tos)
|
||||
s.Push(cpy)
|
||||
}
|
||||
|
||||
func (s *stack) Swp() {
|
||||
tos, err := s.Pop()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
newTos, err := s.Pop()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.Push(tos)
|
||||
s.Push(newTos)
|
||||
}
|
||||
|
||||
func (s *stack) Drp() {
|
||||
s.Pop()
|
||||
}
|
||||
|
||||
func (s stack) TOS() string {
|
||||
if s.ptr < 0 {
|
||||
return "0"
|
||||
}
|
||||
return strconv.FormatFloat(s.data[s.ptr], 'f', -1, 64)
|
||||
}
|
||||
|
||||
func PrintError(s string) {
|
||||
fmt.Fprintf(os.Stderr, "\033[1;91m[Err] %s,\033[0m\n", s)
|
||||
}
|
||||
|
||||
func PrintOperatorHelp() {
|
||||
txt := `# General
|
||||
|
||||
This calculator uses reverse polish notation. As such, the operators come after the operands. For example:
|
||||
|
||||
5 7 + 1 -
|
||||
|
||||
The output of the above would be '11' (using infix notation this would be written ' 5 + 7 - 1'). For more information on reverse polish notation see: https://en.wikipedia.org/wiki/Reverse_Polish_notation
|
||||
|
||||
The output of a calculation will always be the top of the stack. If there is more than one value on the stack at the end of a calculation a warning will be output to stderr.
|
||||
|
||||
# Command List
|
||||
|
||||
The following commands all have a three character version and a single character version. Either one is valid. They are displayed as a comma separated values (single character version first) followed by a colon and a description of the operator. The operators are case insensitive.
|
||||
|
||||
+, add: Adds the top two values on the stack
|
||||
-, sub: Subtracts the top stack value from the value underneath it
|
||||
/, div: Divides the top two stack values with the top of stack being the divisor and the value below it on the stack being the dividend
|
||||
*, mul: Multiplies the top two values on the stack
|
||||
^, pow: Raises the value underneath top of stack to the power of top of stack
|
||||
@, rnd: Rounds the value underneath top of stack to the number of decimal places represented by top of stack. If top of stack is not a whole number an error will result
|
||||
>, cil: Performs a ceiling function on the top of stack (rounds it up to the next whole number)
|
||||
<, flr: Performs a floor function on the top of stack (rounds it down to the next whole number)
|
||||
|, abs: Takes the top value from the stack and leaves its absolute value
|
||||
!, inv: Multiplies top of stack by -1
|
||||
V, sqt: Takes the top value from the stack and leaves its square root
|
||||
|
||||
The following commands operate on the stack, rather than as numerical operations:
|
||||
|
||||
x, swp: Swap the position of the top two values on the stack
|
||||
#, dup: Duplicate/copy the value on top of the stack such that the copy is now above the original on the stack
|
||||
_, clr: Clear the stack (wipe out all values and start over)
|
||||
., drp: Drop the top value on the stack (remove it and throw it away)
|
||||
|
||||
?, help: Prints this help message
|
||||
`
|
||||
fmt.Fprintf(os.Stderr, "%s\033\n", txt)
|
||||
}
|
||||
|
||||
func processWord(w string, s *stack) {
|
||||
num, err := strconv.ParseFloat(w, 64)
|
||||
if err == nil {
|
||||
s.Push(num)
|
||||
return
|
||||
}
|
||||
w = strings.ToLower(w)
|
||||
switch w {
|
||||
case "+", "add":
|
||||
s.Add()
|
||||
case "-", "sub":
|
||||
s.Sub()
|
||||
case "*", "mul":
|
||||
s.Mul()
|
||||
case "/", "div":
|
||||
s.Div()
|
||||
case "^", "pow":
|
||||
s.Pow()
|
||||
case "@", "rnd":
|
||||
s.Round()
|
||||
case ">", "cil":
|
||||
s.Ceil()
|
||||
case "<", "flr":
|
||||
s.Floor()
|
||||
case "|", "abs":
|
||||
s.Abs()
|
||||
case "!", "inv":
|
||||
s.Inv()
|
||||
case "V", "sqt":
|
||||
s.Sqt()
|
||||
case "#", "dup":
|
||||
s.Dup()
|
||||
case "x", "swp":
|
||||
s.Swp()
|
||||
case "_", "clr":
|
||||
s.Clear()
|
||||
case ".", "drp":
|
||||
s.Drp()
|
||||
case "?", "help":
|
||||
PrintOperatorHelp()
|
||||
s.Clear()
|
||||
default:
|
||||
PrintError(fmt.Sprintf("Unknown operator: %q", w))
|
||||
s.Clear()
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
pipe := flag.Bool("p", false, "Take input from a pipe")
|
||||
flag.Parse()
|
||||
s := stack{-1, [100]float64{}}
|
||||
|
||||
if !*pipe {
|
||||
termios.SetInitialTermios()
|
||||
defer termios.Restore()
|
||||
termios.SetCharMode()
|
||||
|
||||
for {
|
||||
var cols, _ = termios.GetWindowSize()
|
||||
ln := qline.GetInput("", "", cols)
|
||||
f := strings.Fields(ln)
|
||||
if len(f) == 0 {
|
||||
break
|
||||
}
|
||||
fmt.Print("\n")
|
||||
for _, val := range f {
|
||||
processWord(val, &s)
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "%v\n", s.data[:s.ptr+1])
|
||||
}
|
||||
} else {
|
||||
b, err := ioutil.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
fmt.Fprint(os.Stderr, "Unable to read from pipe")
|
||||
}
|
||||
f := strings.Fields(string(b))
|
||||
for _, val := range f {
|
||||
processWord(val, &s)
|
||||
}
|
||||
if s.ptr >= 0 {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", s.data[s.ptr])
|
||||
} else {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,284 @@
|
|||
package qline
|
||||
|
||||
// qline is a line input library for terminals that utilize
|
||||
// vt100 compatible escape sequences
|
||||
//
|
||||
// Copyright © 2021 Brian Evans
|
||||
//
|
||||
// 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.
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
UpArrow rune = iota - 20
|
||||
DownArrow
|
||||
LeftArrow
|
||||
RightArrow
|
||||
Delete
|
||||
Home
|
||||
End
|
||||
PageUp
|
||||
PageDown
|
||||
Escape rune = 27
|
||||
NewLine rune = 10
|
||||
CarriageReturn rune = 13
|
||||
BackSpace rune = 127
|
||||
)
|
||||
|
||||
var (
|
||||
width int
|
||||
)
|
||||
|
||||
type buffer struct {
|
||||
buf []rune
|
||||
cursor int
|
||||
maxWidth int
|
||||
offset int
|
||||
cursorStart int
|
||||
prompt string
|
||||
}
|
||||
|
||||
func GetInput(prompt string, content string, cols int) string {
|
||||
b := buffer{make([]rune, 0, (len(content)+1)*2), 0, cols-len(prompt), 0, 0, prompt}
|
||||
|
||||
var ch rune
|
||||
var err error
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
|
||||
// b.seedContent(content)
|
||||
// b.printBuf()
|
||||
|
||||
for {
|
||||
ch, err = ReadKey(reader)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if ch == CarriageReturn || ch == NewLine {
|
||||
break
|
||||
}
|
||||
|
||||
if isControl(ch) {
|
||||
b.controlInput(ch)
|
||||
} else {
|
||||
b.addChar(ch, true)
|
||||
}
|
||||
}
|
||||
return b.string()
|
||||
}
|
||||
|
||||
|
||||
func (lb buffer) string() string {
|
||||
return string(lb.buf)
|
||||
}
|
||||
|
||||
func (lb *buffer) deleteChar() {
|
||||
if lb.cursor == len(lb.buf) {
|
||||
return
|
||||
} else if lb.cursor == len(lb.buf)-1 {
|
||||
lb.buf = lb.buf[:len(lb.buf)-1]
|
||||
} else {
|
||||
lb.buf = append(lb.buf[:lb.cursor], lb.buf[lb.cursor+1:]...)
|
||||
}
|
||||
if lb.offset > 0 {
|
||||
lb.offset--
|
||||
}
|
||||
}
|
||||
|
||||
func (lb *buffer) addChar(c rune, echo bool) {
|
||||
if c < 9 || (c > 10 && c < 13) || (c > 13 && c < 32) {
|
||||
return
|
||||
}
|
||||
if lb.cursor == len(lb.buf) {
|
||||
lb.buf = append(lb.buf, c)
|
||||
lb.cursor++
|
||||
} else {
|
||||
lb.buf = append(lb.buf[:lb.cursor+1], lb.buf[lb.cursor:]...)
|
||||
lb.buf[lb.cursor] = c
|
||||
lb.cursor++
|
||||
}
|
||||
if lb.cursor - lb.offset > lb.maxWidth {
|
||||
lb.offset++
|
||||
}
|
||||
if echo {
|
||||
lb.printBuf()
|
||||
}
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (lb buffer) printBuf() {
|
||||
out := lb.buf[lb.offset:min(len(lb.buf), lb.offset+lb.maxWidth)]
|
||||
fmt.Printf("\r%s%s\033[0K\r\033[%dC", lb.prompt, string(out), lb.cursor - lb.offset + len(lb.prompt))
|
||||
}
|
||||
|
||||
func (lb *buffer) controlInput(c rune) {
|
||||
switch c {
|
||||
case Delete:
|
||||
lb.deleteChar()
|
||||
case BackSpace:
|
||||
if lb.cursor > 0 {
|
||||
lb.cursor--
|
||||
lb.deleteChar()
|
||||
}
|
||||
case LeftArrow:
|
||||
if lb.offset > 0 && lb.cursor - lb.offset == 0 {
|
||||
lb.offset--
|
||||
}
|
||||
if lb.cursor > 0 {
|
||||
lb.cursor--
|
||||
}
|
||||
case RightArrow:
|
||||
// This is still mildly funky, but works enough
|
||||
for ;lb.cursor - lb.offset >= lb.maxWidth && lb.cursor < len(lb.buf); {
|
||||
lb.offset++
|
||||
}
|
||||
if lb.cursor < len(lb.buf) {
|
||||
lb.cursor++
|
||||
}
|
||||
case Home:
|
||||
lb.offset = 0
|
||||
lb.cursor = 0
|
||||
case End:
|
||||
lb.cursor = len(lb.buf)
|
||||
lb.offset = max(lb.cursor - lb.maxWidth, 0)
|
||||
}
|
||||
lb.printBuf()
|
||||
}
|
||||
|
||||
func (lb *buffer) seedContent(s string) {
|
||||
for _, r := range s {
|
||||
lb.addChar(r, false)
|
||||
}
|
||||
}
|
||||
|
||||
func parseCursorPosition(esc string) (int, int, error) {
|
||||
var row, col int
|
||||
r := strings.NewReader(esc)
|
||||
_, err := fmt.Fscanf(r, "\033[%d;%dR", &row, &col)
|
||||
return row, col, err
|
||||
}
|
||||
|
||||
|
||||
func ReadKey(reader *bufio.Reader) (rune, error) {
|
||||
char, _, err := reader.ReadRune()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
avail := reader.Buffered()
|
||||
if char == Escape && avail > 0 {
|
||||
var b strings.Builder
|
||||
b.WriteRune(27)
|
||||
for ; avail > 0; avail-- {
|
||||
c, _, e := reader.ReadRune()
|
||||
if e != nil {
|
||||
break
|
||||
}
|
||||
b.WriteRune(c)
|
||||
}
|
||||
escSeq := b.String()
|
||||
switch true {
|
||||
case escSeq == "\033[A":
|
||||
char = UpArrow
|
||||
case escSeq == "\033[B":
|
||||
char = DownArrow
|
||||
case escSeq == "\033[C":
|
||||
char = RightArrow
|
||||
case escSeq == "\033[D":
|
||||
char = LeftArrow
|
||||
case escSeq == "\033[5~":
|
||||
char = PageUp
|
||||
case escSeq == "\033[6~":
|
||||
char = PageDown
|
||||
case isHomeKey(escSeq):
|
||||
char = Home
|
||||
case isEndKey(escSeq):
|
||||
char = End
|
||||
case isDeleteKey(escSeq):
|
||||
char = Delete
|
||||
case escSeq[len(escSeq)-1] == 'R':
|
||||
// This is a request for cursor position
|
||||
_, cols, err := parseCursorPosition(escSeq)
|
||||
if err == nil {
|
||||
err = fmt.Errorf("response")
|
||||
}
|
||||
return rune(cols), err
|
||||
}
|
||||
}
|
||||
return char, nil
|
||||
}
|
||||
|
||||
func isControl(c rune) bool {
|
||||
switch c {
|
||||
case UpArrow, DownArrow, RightArrow, LeftArrow, PageUp, PageDown, Home, End, Delete, BackSpace, CarriageReturn, NewLine, Escape:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func isDeleteKey(seq string) bool {
|
||||
switch seq {
|
||||
case "\033[3~", "\033[P":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func isHomeKey(seq string) bool {
|
||||
switch seq {
|
||||
case "\033[1~", "\033[7~", "\033[H", "\033OH":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func isEndKey(seq string) bool {
|
||||
switch seq {
|
||||
case "\033[4~", "\033[8~", "\033[F", "\033OF":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
// +build linux
|
||||
|
||||
package termios
|
||||
|
||||
import "syscall"
|
||||
|
||||
const (
|
||||
getTermiosIoctl = syscall.TCGETS
|
||||
setTermiosIoctl = syscall.TCSETS
|
||||
)
|
|
@ -0,0 +1,10 @@
|
|||
// +build !linux
|
||||
|
||||
package termios
|
||||
|
||||
import "syscall"
|
||||
|
||||
const (
|
||||
getTermiosIoctl = syscall.TIOCGETA
|
||||
setTermiosIoctl = syscall.TIOCSETAF
|
||||
)
|
|
@ -0,0 +1,73 @@
|
|||
package termios
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type winsize struct {
|
||||
Row uint16
|
||||
Col uint16
|
||||
Xpixel uint16
|
||||
Ypixel uint16
|
||||
}
|
||||
|
||||
var fd = os.Stdin.Fd()
|
||||
var initial syscall.Termios
|
||||
var initialSet bool = false
|
||||
|
||||
func ioctl(fd, request, argp uintptr) error {
|
||||
if _, _, e := syscall.Syscall(syscall.SYS_IOCTL, fd, request, argp); e != 0 {
|
||||
return e
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetWindowSize() (int, int) {
|
||||
var value winsize
|
||||
ioctl(fd, syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(&value)))
|
||||
return int(value.Col), int(value.Row)
|
||||
}
|
||||
|
||||
func getTermios() syscall.Termios {
|
||||
var value syscall.Termios
|
||||
err := ioctl(fd, getTermiosIoctl, uintptr(unsafe.Pointer(&value)))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func setTermios(termios syscall.Termios) {
|
||||
err := ioctl(fd, setTermiosIoctl, uintptr(unsafe.Pointer(&termios)))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
runtime.KeepAlive(termios)
|
||||
}
|
||||
|
||||
func SetInitialTermios() {
|
||||
if !initialSet {
|
||||
initial = getTermios()
|
||||
initialSet = true
|
||||
}
|
||||
}
|
||||
|
||||
func SetCharMode() {
|
||||
t := getTermios()
|
||||
t.Lflag = t.Lflag ^ syscall.ICANON
|
||||
t.Lflag = t.Lflag ^ syscall.ECHO
|
||||
setTermios(t)
|
||||
}
|
||||
|
||||
func SetLineMode() {
|
||||
var t = getTermios()
|
||||
t.Lflag = t.Lflag | (syscall.ICANON | syscall.ECHO)
|
||||
setTermios(t)
|
||||
}
|
||||
|
||||
func Restore() {
|
||||
setTermios(initial)
|
||||
}
|
Loading…
Reference in New Issue