From 0818b5e318b8ffc10daf91e9d676e8668680d343 Mon Sep 17 00:00:00 2001 From: Bruce MacDonald Date: Mon, 30 Oct 2023 16:18:12 -0400 Subject: [PATCH] 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 --- readline/buffer.go | 4 ++- readline/readline.go | 5 ++-- readline/term_windows.go | 62 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 readline/term_windows.go diff --git a/readline/buffer.go b/readline/buffer.go index 8b680282..d66b512a 100644 --- a/readline/buffer.go +++ b/readline/buffer.go @@ -2,6 +2,7 @@ package readline import ( "fmt" + "os" "github.com/emirpasic/gods/lists/arraylist" "golang.org/x/term" @@ -17,7 +18,8 @@ type Buffer struct { } 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 { fmt.Println("Error getting size:", err) return nil, err diff --git a/readline/readline.go b/readline/readline.go index d1fe2c8d..d3fcc537 100644 --- a/readline/readline.go +++ b/readline/readline.go @@ -51,11 +51,12 @@ func (i *Instance) Readline() (string, error) { } fmt.Print(prompt) - termios, err := SetRawMode(syscall.Stdin) + fd := int(syscall.Stdin) + termios, err := SetRawMode(fd) if err != nil { return "", err } - defer UnsetRawMode(syscall.Stdin, termios) + defer UnsetRawMode(fd, termios) buf, _ := NewBuffer(i.Prompt) diff --git a/readline/term_windows.go b/readline/term_windows.go new file mode 100644 index 00000000..3d1c80e1 --- /dev/null +++ b/readline/term_windows.go @@ -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 +}