readline windows terminal support (#950)

- update the readline package to have basic support on windows, this is not full feature parity with the unix cli yet
This commit is contained in:
Bruce MacDonald 2023-10-30 16:18:12 -04:00 committed by GitHub
parent 874bb31986
commit 0818b5e318
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 68 additions and 3 deletions

View file

@ -2,6 +2,7 @@ package readline
import ( import (
"fmt" "fmt"
"os"
"github.com/emirpasic/gods/lists/arraylist" "github.com/emirpasic/gods/lists/arraylist"
"golang.org/x/term" "golang.org/x/term"
@ -17,7 +18,8 @@ type Buffer struct {
} }
func NewBuffer(prompt *Prompt) (*Buffer, error) { func NewBuffer(prompt *Prompt) (*Buffer, error) {
width, height, err := term.GetSize(0) fd := int(os.Stdout.Fd())
width, height, err := term.GetSize(fd)
if err != nil { if err != nil {
fmt.Println("Error getting size:", err) fmt.Println("Error getting size:", err)
return nil, err return nil, err

View file

@ -51,11 +51,12 @@ func (i *Instance) Readline() (string, error) {
} }
fmt.Print(prompt) fmt.Print(prompt)
termios, err := SetRawMode(syscall.Stdin) fd := int(syscall.Stdin)
termios, err := SetRawMode(fd)
if err != nil { if err != nil {
return "", err return "", err
} }
defer UnsetRawMode(syscall.Stdin, termios) defer UnsetRawMode(fd, termios)
buf, _ := NewBuffer(i.Prompt) buf, _ := NewBuffer(i.Prompt)

62
readline/term_windows.go Normal file
View file

@ -0,0 +1,62 @@
package readline
import (
"syscall"
"unsafe"
)
const (
enableLineInput = 2
enableWindowInput = 8
enableMouseInput = 16
enableInsertMode = 32
enableQuickEditMode = 64
enableExtendedFlags = 128
enableProcessedOutput = 1
enableWrapAtEolOutput = 2
enableAutoPosition = 256 // Cursor position is not affected by writing data to the console.
enableEchoInput = 4 // Characters are written to the console as they're read.
enableProcessedInput = 1 // Enables input processing (like recognizing Ctrl+C).
)
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
var (
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
procSetConsoleMode = kernel32.NewProc("SetConsoleMode")
)
type State struct {
mode uint32
}
// IsTerminal checks if the given file descriptor is associated with a terminal
func IsTerminal(fd int) bool {
var st uint32
r, _, e := syscall.SyscallN(procGetConsoleMode.Addr(), uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
// if the call succeeds and doesn't produce an error, it's a terminal
return r != 0 && e == 0
}
func SetRawMode(fd int) (*State, error) {
var st uint32
// retrieve the current mode of the terminal
_, _, e := syscall.SyscallN(procGetConsoleMode.Addr(), uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
if e != 0 {
return nil, error(e)
}
// modify the mode to set it to raw
raw := st &^ (enableEchoInput | enableProcessedInput | enableLineInput | enableProcessedOutput)
// apply the new mode to the terminal
_, _, e = syscall.SyscallN(procSetConsoleMode.Addr(), uintptr(fd), uintptr(raw), 0)
if e != 0 {
return nil, error(e)
}
// return the original state so that it can be restored later
return &State{st}, nil
}
func UnsetRawMode(fd int, state *State) error {
_, _, err := syscall.SyscallN(procSetConsoleMode.Addr(), uintptr(fd), uintptr(state.mode), 0)
return err
}