-
-
Notifications
You must be signed in to change notification settings - Fork 531
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move terminal related functions to internal/term
- Loading branch information
Showing
4 changed files
with
113 additions
and
95 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
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
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
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,98 @@ | ||
// Copyright 2021 The age 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 term | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"os" | ||
"runtime" | ||
|
||
"golang.org/x/term" | ||
) | ||
|
||
// clearLine clears the current line on the terminal, or opens a new line if | ||
// terminal escape codes don't work. | ||
func clearLine(out io.Writer) { | ||
const ( | ||
CUI = "\033[" // Control Sequence Introducer | ||
CPL = CUI + "F" // Cursor Previous Line | ||
EL = CUI + "K" // Erase in Line | ||
) | ||
|
||
// First, open a new line, which is guaranteed to work everywhere. Then, try | ||
// to erase the line above with escape codes. | ||
// | ||
// (We use CRLF instead of LF to work around an apparent bug in WSL2's | ||
// handling of CONOUT$. Only when running a Windows binary from WSL2, the | ||
// cursor would not go back to the start of the line with a simple LF. | ||
// Honestly, it's impressive CONIN$ and CONOUT$ work at all inside WSL2.) | ||
fmt.Fprintf(out, "\r\n"+CPL+EL) | ||
} | ||
|
||
// term.ReadSecret reads a value from the terminal with no echo. The prompt is ephemeral. | ||
func ReadSecret(prompt string) (s []byte, err error) { | ||
err = WithTerminal(func(in, out *os.File) error { | ||
fmt.Fprintf(out, "%s ", prompt) | ||
defer clearLine(out) | ||
s, err = term.ReadPassword(int(in.Fd())) | ||
return err | ||
}) | ||
return | ||
} | ||
|
||
// ReadCharacter reads a single character from the terminal with no echo. The | ||
// prompt is ephemeral. | ||
func ReadCharacter(prompt string) (c byte, err error) { | ||
err = WithTerminal(func(in, out *os.File) error { | ||
fmt.Fprintf(out, "%s ", prompt) | ||
defer clearLine(out) | ||
|
||
oldState, err := term.MakeRaw(int(in.Fd())) | ||
if err != nil { | ||
return err | ||
} | ||
defer term.Restore(int(in.Fd()), oldState) | ||
|
||
b := make([]byte, 1) | ||
if _, err := in.Read(b); err != nil { | ||
return err | ||
} | ||
|
||
c = b[0] | ||
return nil | ||
}) | ||
return | ||
} | ||
|
||
// WithTerminal runs f with the terminal input and output files, if available. | ||
// WithTerminal does not open a non-terminal stdin, so the caller does not need | ||
// to check stdinInUse. | ||
func WithTerminal(f func(in, out *os.File) error) error { | ||
if runtime.GOOS == "windows" { | ||
in, err := os.OpenFile("CONIN$", os.O_RDWR, 0) | ||
if err != nil { | ||
return err | ||
} | ||
defer in.Close() | ||
out, err := os.OpenFile("CONOUT$", os.O_WRONLY, 0) | ||
if err != nil { | ||
return err | ||
} | ||
defer out.Close() | ||
return f(in, out) | ||
} else if tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0); err == nil { | ||
defer tty.Close() | ||
return f(tty, tty) | ||
} else if IsTerminal() { | ||
return f(os.Stdin, os.Stdin) | ||
} else { | ||
return fmt.Errorf("standard input is not a terminal, and /dev/tty is not available: %v", err) | ||
} | ||
} | ||
|
||
func IsTerminal() bool { | ||
return term.IsTerminal(int(os.Stdout.Fd())) | ||
} |