-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e3b41ab
commit 1c59f1a
Showing
12 changed files
with
549 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
module github.com/charmbracelet/x/term | ||
|
||
go 1.18 | ||
|
||
require ( | ||
github.com/charmbracelet/x/ansi v0.1.0 | ||
github.com/charmbracelet/x/input v0.1.0 | ||
golang.org/x/sys v0.20.0 | ||
) | ||
|
||
require ( | ||
github.com/charmbracelet/x/windows v0.1.0 // indirect | ||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect | ||
github.com/muesli/cancelreader v0.2.2 // indirect | ||
github.com/rivo/uniseg v0.4.7 // indirect | ||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
github.com/charmbracelet/x/ansi v0.1.0 h1:o4NbQQCoVgbLpD5RC1cI687baoLwrLZyCOTGlF0gne4= | ||
github.com/charmbracelet/x/ansi v0.1.0/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= | ||
github.com/charmbracelet/x/input v0.1.0 h1:TEsGSfZYQyOtp+STIjyBq6tpRaorH0qpwZUj8DavAhQ= | ||
github.com/charmbracelet/x/input v0.1.0/go.mod h1:ZZwaBxPF7IG8gWWzPUVqHEtWhc1+HXJPNuerJGRGZ28= | ||
github.com/charmbracelet/x/windows v0.1.0 h1:gTaxdvzDM5oMa/I2ZNF7wN78X/atWemG9Wph7Ika2k4= | ||
github.com/charmbracelet/x/windows v0.1.0/go.mod h1:GLEO/l+lizvFDBPLIOk+49gdX49L9YWMB5t+DZd0jkQ= | ||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= | ||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= | ||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= | ||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= | ||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= | ||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= | ||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= | ||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= | ||
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= | ||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= | ||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package term | ||
|
||
// State contains platform-specific state of a terminal. | ||
type State struct { | ||
state | ||
} | ||
|
||
// IsTerminal returns whether the given file descriptor is a terminal. | ||
func IsTerminal(fd uintptr) bool { | ||
return isTerminal(fd) | ||
} | ||
|
||
// MakeRaw puts the terminal connected to the given file descriptor into raw | ||
// mode and returns the previous state of the terminal so that it can be | ||
// restored. | ||
func MakeRaw(fd uintptr) (*State, error) { | ||
return makeRaw(fd) | ||
} | ||
|
||
// GetState returns the current state of a terminal which may be useful to | ||
// restore the terminal after a signal. | ||
func GetState(fd uintptr) (*State, error) { | ||
return getState(fd) | ||
} | ||
|
||
// SetState sets the given state of the terminal. | ||
func SetState(fd uintptr, state *State) error { | ||
return setState(fd, state) | ||
} | ||
|
||
// Restore restores the terminal connected to the given file descriptor to a | ||
// previous state. | ||
func Restore(fd uintptr, oldState *State) error { | ||
return restore(fd, oldState) | ||
} | ||
|
||
// GetSize returns the visible dimensions of the given terminal. | ||
// | ||
// These dimensions don't include any scrollback buffer height. | ||
func GetSize(fd uintptr) (width, height int, err error) { | ||
return getSize(fd) | ||
} | ||
|
||
// ReadPassword reads a line of input from a terminal without local echo. This | ||
// is commonly used for inputting passwords and other sensitive data. The slice | ||
// returned does not include the \n. | ||
func ReadPassword(fd uintptr) ([]byte, error) { | ||
return readPassword(fd) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !zos && !windows && !solaris && !plan9 | ||
// +build !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!zos,!windows,!solaris,!plan9 | ||
|
||
package term | ||
|
||
import ( | ||
"fmt" | ||
"runtime" | ||
) | ||
|
||
type state struct{} | ||
|
||
func isTerminal(fd uintptr) bool { | ||
return false | ||
} | ||
|
||
func makeRaw(fd uintptr) (*State, error) { | ||
return nil, fmt.Errorf("terminal: MakeRaw not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) | ||
} | ||
|
||
func getState(fd uintptr) (*State, error) { | ||
return nil, fmt.Errorf("terminal: GetState not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) | ||
} | ||
|
||
func restore(fd uintptr, state *State) error { | ||
return fmt.Errorf("terminal: Restore not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) | ||
} | ||
|
||
func getSize(fd uintptr) (width, height int, err error) { | ||
return 0, 0, fmt.Errorf("terminal: GetSize not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) | ||
} | ||
|
||
func setState(fd uintptr, state *State) error { | ||
return fmt.Errorf("terminal: SetState not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) | ||
} | ||
|
||
func readPassword(fd uintptr) ([]byte, error) { | ||
return nil, fmt.Errorf("terminal: ReadPassword not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package term_test | ||
|
||
import ( | ||
"os" | ||
"runtime" | ||
"testing" | ||
|
||
"github.com/charmbracelet/x/term" | ||
) | ||
|
||
func TestIsTerminalTempFile(t *testing.T) { | ||
file, err := os.CreateTemp("", "TestIsTerminalTempFile") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
defer os.Remove(file.Name()) | ||
defer file.Close() | ||
|
||
if term.IsTerminal(file.Fd()) { | ||
t.Fatalf("IsTerminal unexpectedly returned true for temporary file %s", file.Name()) | ||
} | ||
} | ||
|
||
func TestIsTerminalTerm(t *testing.T) { | ||
if runtime.GOOS != "linux" { | ||
t.Skipf("unknown terminal path for GOOS %v", runtime.GOOS) | ||
} | ||
file, err := os.OpenFile("/dev/ptmx", os.O_RDWR, 0) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
defer file.Close() | ||
|
||
if !term.IsTerminal(file.Fd()) { | ||
t.Fatalf("IsTerminal unexpectedly returned false for terminal file %s", file.Name()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos | ||
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos | ||
|
||
package term | ||
|
||
import ( | ||
"golang.org/x/sys/unix" | ||
) | ||
|
||
type state struct { | ||
unix.Termios | ||
} | ||
|
||
func isTerminal(fd uintptr) bool { | ||
_, err := unix.IoctlGetTermios(int(fd), ioctlReadTermios) | ||
return err == nil | ||
} | ||
|
||
func makeRaw(fd uintptr) (*State, error) { | ||
termios, err := unix.IoctlGetTermios(int(fd), ioctlReadTermios) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
oldState := State{state{Termios: *termios}} | ||
|
||
// This attempts to replicate the behaviour documented for cfmakeraw in | ||
// the termios(3) manpage. | ||
termios.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON | ||
termios.Oflag &^= unix.OPOST | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong. |
||
termios.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN | ||
termios.Cflag &^= unix.CSIZE | unix.PARENB | ||
termios.Cflag |= unix.CS8 | ||
termios.Cc[unix.VMIN] = 1 | ||
termios.Cc[unix.VTIME] = 0 | ||
if err := unix.IoctlSetTermios(int(fd), ioctlWriteTermios, termios); err != nil { | ||
return nil, err | ||
} | ||
|
||
return &oldState, nil | ||
} | ||
|
||
func setState(fd uintptr, state *State) error { | ||
var termios *unix.Termios | ||
if state != nil { | ||
termios = &state.Termios | ||
} | ||
return unix.IoctlSetTermios(int(fd), ioctlWriteTermios, termios) | ||
} | ||
|
||
func getState(fd uintptr) (*State, error) { | ||
termios, err := unix.IoctlGetTermios(int(fd), ioctlReadTermios) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &State{state{Termios: *termios}}, nil | ||
} | ||
|
||
func restore(fd uintptr, state *State) error { | ||
return unix.IoctlSetTermios(int(fd), ioctlWriteTermios, &state.Termios) | ||
} | ||
|
||
func getSize(fd uintptr) (width, height int, err error) { | ||
ws, err := unix.IoctlGetWinsize(int(fd), unix.TIOCGWINSZ) | ||
if err != nil { | ||
return 0, 0, err | ||
} | ||
return int(ws.Col), int(ws.Row), nil | ||
} | ||
|
||
// passwordReader is an io.Reader that reads from a specific file descriptor. | ||
type passwordReader int | ||
|
||
func (r passwordReader) Read(buf []byte) (int, error) { | ||
return unix.Read(int(r), buf) | ||
} | ||
|
||
func readPassword(fd uintptr) ([]byte, error) { | ||
termios, err := unix.IoctlGetTermios(int(fd), ioctlReadTermios) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
newState := *termios | ||
newState.Lflag &^= unix.ECHO | ||
newState.Lflag |= unix.ICANON | unix.ISIG | ||
newState.Iflag |= unix.ICRNL | ||
if err := unix.IoctlSetTermios(int(fd), ioctlWriteTermios, &newState); err != nil { | ||
return nil, err | ||
} | ||
|
||
defer unix.IoctlSetTermios(int(fd), ioctlWriteTermios, termios) | ||
|
||
return readPasswordLine(passwordReader(fd)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
//go:build darwin || dragonfly || freebsd || netbsd || openbsd | ||
// +build darwin dragonfly freebsd netbsd openbsd | ||
|
||
package term | ||
|
||
import "golang.org/x/sys/unix" | ||
|
||
const ( | ||
ioctlReadTermios = unix.TIOCGETA | ||
ioctlWriteTermios = unix.TIOCSETA | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
//go:build aix || linux || solaris || zos | ||
// +build aix linux solaris zos | ||
|
||
package term | ||
|
||
import "golang.org/x/sys/unix" | ||
|
||
const ( | ||
ioctlReadTermios = unix.TCGETS | ||
ioctlWriteTermios = unix.TCSETS | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
//go:build windows | ||
// +build windows | ||
|
||
package term | ||
|
||
import ( | ||
"os" | ||
|
||
"golang.org/x/sys/windows" | ||
) | ||
|
||
type state struct { | ||
Mode uint32 | ||
} | ||
|
||
func isTerminal(fd uintptr) bool { | ||
var st uint32 | ||
err := windows.GetConsoleMode(windows.Handle(fd), &st) | ||
return err == nil | ||
} | ||
|
||
func makeRaw(fd uintptr) (*State, error) { | ||
var st uint32 | ||
if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil { | ||
return nil, err | ||
} | ||
raw := st &^ (windows.ENABLE_ECHO_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT | windows.ENABLE_PROCESSED_OUTPUT) | ||
if err := windows.SetConsoleMode(windows.Handle(fd), raw); err != nil { | ||
return nil, err | ||
} | ||
return &State{state{st}}, nil | ||
} | ||
|
||
func setState(fd uintptr, state *State) error { | ||
var mode uint32 | ||
if state != nil { | ||
mode = state.Mode | ||
} | ||
return windows.SetConsoleMode(windows.Handle(fd), mode) | ||
} | ||
|
||
func getState(fd uintptr) (*State, error) { | ||
var st uint32 | ||
if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil { | ||
return nil, err | ||
} | ||
return &State{state{st}}, nil | ||
} | ||
|
||
func restore(fd uintptr, state *State) error { | ||
return windows.SetConsoleMode(windows.Handle(fd), state.Mode) | ||
} | ||
|
||
func getSize(fd uintptr) (width, height int, err error) { | ||
var info windows.ConsoleScreenBufferInfo | ||
if err := windows.GetConsoleScreenBufferInfo(windows.Handle(fd), &info); err != nil { | ||
return 0, 0, err | ||
} | ||
return int(info.Window.Right - info.Window.Left + 1), int(info.Window.Bottom - info.Window.Top + 1), nil | ||
} | ||
|
||
func readPassword(fd uintptr) ([]byte, error) { | ||
var st uint32 | ||
if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil { | ||
return nil, err | ||
} | ||
old := st | ||
|
||
st &^= (windows.ENABLE_ECHO_INPUT | windows.ENABLE_LINE_INPUT) | ||
st |= (windows.ENABLE_PROCESSED_OUTPUT | windows.ENABLE_PROCESSED_INPUT) | ||
if err := windows.SetConsoleMode(windows.Handle(fd), st); err != nil { | ||
return nil, err | ||
} | ||
|
||
defer windows.SetConsoleMode(windows.Handle(fd), old) | ||
|
||
var h windows.Handle | ||
p, _ := windows.GetCurrentProcess() | ||
if err := windows.DuplicateHandle(p, windows.Handle(fd), p, &h, 0, false, windows.DUPLICATE_SAME_ACCESS); err != nil { | ||
return nil, err | ||
} | ||
|
||
f := os.NewFile(uintptr(h), "stdin") | ||
defer f.Close() | ||
return readPasswordLine(f) | ||
} |
Oops, something went wrong.
When using bubbletea's nilRenderer, the terminal is placed in raw mode.
unix.OPOST
messes up writes to stderr/stdout. Here's a simple application to illustrate:https://gist.github.com/cwarden/5bc56a96481c1145231f6e5973187487
Should this be addressed here, or should the terminal not be put in raw mode when using the nil renderer?