mirror of
https://github.com/golang/term.git
synced 2026-02-08 03:36:04 +03:00
Compare commits
85 Commits
tmp.crypto
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7e5b0437f | ||
|
|
943f25d359 | ||
|
|
9b991dd831 | ||
|
|
3863673230 | ||
|
|
1231d5465b | ||
|
|
3475bc8ef1 | ||
|
|
3a0828a666 | ||
|
|
1a11b45a6f | ||
|
|
d862cd548e | ||
|
|
a35244d18d | ||
|
|
4f53e0cd39 | ||
|
|
27f29d8328 | ||
|
|
30da5dd58f | ||
|
|
2ec7864a3e | ||
|
|
a809085bff | ||
|
|
5d2308b09d | ||
|
|
e770dddbf5 | ||
|
|
04218fdaf7 | ||
|
|
208db03875 | ||
|
|
743b2709ab | ||
|
|
40b02d69cd | ||
|
|
442846aa8d | ||
|
|
b725e362a8 | ||
|
|
54df7da90d | ||
|
|
9d5441ab55 | ||
|
|
2f7b0dd743 | ||
|
|
f867b7695b | ||
|
|
d59895469a | ||
|
|
d4346f0be2 | ||
|
|
c976cb1d70 | ||
|
|
5f0bb72315 | ||
|
|
46c790f81f | ||
|
|
5b15d269ba | ||
|
|
c5eaf76d63 | ||
|
|
353276a841 | ||
|
|
ae941452f5 | ||
|
|
ee66497fa3 | ||
|
|
70d3a0bd3f | ||
|
|
6a610bc55b | ||
|
|
ea6303ae75 | ||
|
|
f413282cd8 | ||
|
|
19e73c2b80 | ||
|
|
edd9fb7f4a | ||
|
|
88fcf87c53 | ||
|
|
f6de4a13df | ||
|
|
119f703398 | ||
|
|
7ae6be6d01 | ||
|
|
0edf009663 | ||
|
|
d974fe8326 | ||
|
|
1efcd90d86 | ||
|
|
97ca0e3821 | ||
|
|
f72a2d8d64 | ||
|
|
f6f2839df8 | ||
|
|
8365914569 | ||
|
|
7a66f970e0 | ||
|
|
a9ba230a40 | ||
|
|
065cf7ba24 | ||
|
|
e5f449aeb1 | ||
|
|
03fcf44c22 | ||
|
|
f766a8b9ae | ||
|
|
140adaaadf | ||
|
|
6886f2dfbf | ||
|
|
a79de5458b | ||
|
|
c04ba851c2 | ||
|
|
f5beecf764 | ||
|
|
b80969c673 | ||
|
|
72f3dc4e9b | ||
|
|
de623e64d2 | ||
|
|
6a3ed077a4 | ||
|
|
2321bbc49c | ||
|
|
ee85cb95a7 | ||
|
|
7de9c90e9d | ||
|
|
f5c789dd32 | ||
|
|
d7a72108b8 | ||
|
|
ad39bd3f04 | ||
|
|
761de043ff | ||
|
|
5215a04f73 | ||
|
|
159e5304a9 | ||
|
|
f887077f25 | ||
|
|
c3296ef303 | ||
|
|
4b5627afda | ||
|
|
05bb8bb4be | ||
|
|
aa45bf8df0 | ||
|
|
fcc174f3e9 | ||
|
|
0d63f7188e |
26
CONTRIBUTING.md
Normal file
26
CONTRIBUTING.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Contributing to Go
|
||||||
|
|
||||||
|
Go is an open source project.
|
||||||
|
|
||||||
|
It is the work of hundreds of contributors. We appreciate your help!
|
||||||
|
|
||||||
|
## Filing issues
|
||||||
|
|
||||||
|
When [filing an issue](https://golang.org/issue/new), make sure to answer these five questions:
|
||||||
|
|
||||||
|
1. What version of Go are you using (`go version`)?
|
||||||
|
2. What operating system and processor architecture are you using?
|
||||||
|
3. What did you do?
|
||||||
|
4. What did you expect to see?
|
||||||
|
5. What did you see instead?
|
||||||
|
|
||||||
|
General questions should go to the [golang-nuts mailing list](https://groups.google.com/group/golang-nuts) instead of the issue tracker.
|
||||||
|
The gophers there will answer or ask you to file an issue if you've tripped over a bug.
|
||||||
|
|
||||||
|
## Contributing code
|
||||||
|
|
||||||
|
Please read the [Contribution Guidelines](https://golang.org/doc/contribute.html)
|
||||||
|
before sending patches.
|
||||||
|
|
||||||
|
Unless otherwise noted, the Go source files are distributed under
|
||||||
|
the BSD-style license found in the LICENSE file.
|
||||||
27
LICENSE
Normal file
27
LICENSE
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
Copyright 2009 The Go Authors.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google LLC nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
22
PATENTS
Normal file
22
PATENTS
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
Additional IP Rights Grant (Patents)
|
||||||
|
|
||||||
|
"This implementation" means the copyrightable works distributed by
|
||||||
|
Google as part of the Go project.
|
||||||
|
|
||||||
|
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||||
|
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||||
|
patent license to make, have made, use, offer to sell, sell, import,
|
||||||
|
transfer and otherwise run, modify and propagate the contents of this
|
||||||
|
implementation of Go, where such license applies only to those patent
|
||||||
|
claims, both currently owned or controlled by Google and acquired in
|
||||||
|
the future, licensable by Google that are necessarily infringed by this
|
||||||
|
implementation of Go. This grant does not include claims that would be
|
||||||
|
infringed only as a consequence of further modification of this
|
||||||
|
implementation. If you or your agent or exclusive licensee institute or
|
||||||
|
order or agree to the institution of patent litigation against any
|
||||||
|
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||||
|
that this implementation of Go or any code incorporated within this
|
||||||
|
implementation of Go constitutes direct or contributory patent
|
||||||
|
infringement, or inducement of patent infringement, then any patent
|
||||||
|
rights granted to you under this License for this implementation of Go
|
||||||
|
shall terminate as of the date such litigation is filed.
|
||||||
16
README.md
Normal file
16
README.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Go terminal/console support
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/golang.org/x/term)
|
||||||
|
|
||||||
|
This repository provides Go terminal and console support packages.
|
||||||
|
|
||||||
|
## Report Issues / Send Patches
|
||||||
|
|
||||||
|
This repository uses Gerrit for code changes. To learn how to submit changes to
|
||||||
|
this repository, see https://go.dev/doc/contribute.
|
||||||
|
|
||||||
|
The git repository is https://go.googlesource.com/term.
|
||||||
|
|
||||||
|
The main issue tracker for the term repository is located at
|
||||||
|
https://go.dev/issues. Prefix your issue with "x/term:" in the
|
||||||
|
subject line, so it is easy to find.
|
||||||
1
codereview.cfg
Normal file
1
codereview.cfg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
issuerepo: golang/go
|
||||||
5
go.mod
Normal file
5
go.mod
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module golang.org/x/term
|
||||||
|
|
||||||
|
go 1.24.0
|
||||||
|
|
||||||
|
require golang.org/x/sys v0.40.0
|
||||||
2
go.sum
Normal file
2
go.sum
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||||
|
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
60
term.go
Normal file
60
term.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
// Copyright 2019 The Go 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 provides support functions for dealing with terminals, as
|
||||||
|
// commonly found on UNIX systems.
|
||||||
|
//
|
||||||
|
// Putting a terminal into raw mode is the most common requirement:
|
||||||
|
//
|
||||||
|
// oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
|
||||||
|
// if err != nil {
|
||||||
|
// panic(err)
|
||||||
|
// }
|
||||||
|
// defer term.Restore(int(os.Stdin.Fd()), oldState)
|
||||||
|
//
|
||||||
|
// Note that on non-Unix systems os.Stdin.Fd() may not be 0.
|
||||||
|
package term
|
||||||
|
|
||||||
|
// State contains the state of a terminal.
|
||||||
|
type State struct {
|
||||||
|
state
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTerminal returns whether the given file descriptor is a terminal.
|
||||||
|
func IsTerminal(fd int) 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 int) (*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 int) (*State, error) {
|
||||||
|
return getState(fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore restores the terminal connected to the given file descriptor to a
|
||||||
|
// previous state.
|
||||||
|
func Restore(fd int, 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 int) (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 int) ([]byte, error) {
|
||||||
|
return readPassword(fd)
|
||||||
|
}
|
||||||
42
term_plan9.go
Normal file
42
term_plan9.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
// Copyright 2019 The Go 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"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"golang.org/x/sys/plan9"
|
||||||
|
)
|
||||||
|
|
||||||
|
type state struct{}
|
||||||
|
|
||||||
|
func isTerminal(fd int) bool {
|
||||||
|
path, err := plan9.Fd2path(fd)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return path == "/dev/cons" || path == "/mnt/term/dev/cons"
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeRaw(fd int) (*State, error) {
|
||||||
|
return nil, fmt.Errorf("terminal: MakeRaw not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getState(fd int) (*State, error) {
|
||||||
|
return nil, fmt.Errorf("terminal: GetState not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
||||||
|
|
||||||
|
func restore(fd int, state *State) error {
|
||||||
|
return fmt.Errorf("terminal: Restore not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSize(fd int) (width, height int, err error) {
|
||||||
|
return 0, 0, fmt.Errorf("terminal: GetSize not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readPassword(fd int) ([]byte, error) {
|
||||||
|
return nil, fmt.Errorf("terminal: ReadPassword not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
||||||
41
term_test.go
Normal file
41
term_test.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
// Copyright 2019 The Go 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_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/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(int(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(int(file.Fd())) {
|
||||||
|
t.Fatalf("IsTerminal unexpectedly returned false for terminal file %s", file.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,46 +1,31 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
// Copyright 2019 The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build aix darwin dragonfly freebsd linux,!appengine netbsd openbsd zos
|
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos
|
||||||
|
|
||||||
// Package terminal provides support functions for dealing with terminals, as
|
package term
|
||||||
// commonly found on UNIX systems.
|
|
||||||
//
|
|
||||||
// Putting a terminal into raw mode is the most common requirement:
|
|
||||||
//
|
|
||||||
// oldState, err := terminal.MakeRaw(0)
|
|
||||||
// if err != nil {
|
|
||||||
// panic(err)
|
|
||||||
// }
|
|
||||||
// defer terminal.Restore(0, oldState)
|
|
||||||
package terminal // import "golang.org/x/crypto/ssh/terminal"
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
// State contains the state of a terminal.
|
type state struct {
|
||||||
type State struct {
|
|
||||||
termios unix.Termios
|
termios unix.Termios
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsTerminal returns whether the given file descriptor is a terminal.
|
func isTerminal(fd int) bool {
|
||||||
func IsTerminal(fd int) bool {
|
|
||||||
_, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
_, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeRaw put the terminal connected to the given file descriptor into raw
|
func makeRaw(fd int) (*State, error) {
|
||||||
// mode and returns the previous state of the terminal so that it can be
|
|
||||||
// restored.
|
|
||||||
func MakeRaw(fd int) (*State, error) {
|
|
||||||
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
oldState := State{termios: *termios}
|
oldState := State{state{termios: *termios}}
|
||||||
|
|
||||||
// This attempts to replicate the behaviour documented for cfmakeraw in
|
// This attempts to replicate the behaviour documented for cfmakeraw in
|
||||||
// the termios(3) manpage.
|
// the termios(3) manpage.
|
||||||
@@ -58,28 +43,23 @@ func MakeRaw(fd int) (*State, error) {
|
|||||||
return &oldState, nil
|
return &oldState, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetState returns the current state of a terminal which may be useful to
|
func getState(fd int) (*State, error) {
|
||||||
// restore the terminal after a signal.
|
|
||||||
func GetState(fd int) (*State, error) {
|
|
||||||
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &State{termios: *termios}, nil
|
return &State{state{termios: *termios}}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore restores the terminal connected to the given file descriptor to a
|
func restore(fd int, state *State) error {
|
||||||
// previous state.
|
|
||||||
func Restore(fd int, state *State) error {
|
|
||||||
return unix.IoctlSetTermios(fd, ioctlWriteTermios, &state.termios)
|
return unix.IoctlSetTermios(fd, ioctlWriteTermios, &state.termios)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSize returns the dimensions of the given terminal.
|
func getSize(fd int) (width, height int, err error) {
|
||||||
func GetSize(fd int) (width, height int, err error) {
|
|
||||||
ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
|
ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1, -1, err
|
return 0, 0, err
|
||||||
}
|
}
|
||||||
return int(ws.Col), int(ws.Row), nil
|
return int(ws.Col), int(ws.Row), nil
|
||||||
}
|
}
|
||||||
@@ -91,10 +71,7 @@ func (r passwordReader) Read(buf []byte) (int, error) {
|
|||||||
return unix.Read(int(r), buf)
|
return unix.Read(int(r), buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadPassword reads a line of input from a terminal without local echo. This
|
func readPassword(fd int) ([]byte, error) {
|
||||||
// is commonly used for inputting passwords and other sensitive data. The slice
|
|
||||||
// returned does not include the \n.
|
|
||||||
func ReadPassword(fd int) ([]byte, error) {
|
|
||||||
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build darwin dragonfly freebsd netbsd openbsd
|
//go:build darwin || dragonfly || freebsd || netbsd || openbsd
|
||||||
|
|
||||||
package terminal
|
package term
|
||||||
|
|
||||||
import "golang.org/x/sys/unix"
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
// Copyright 2018 The Go Authors. All rights reserved.
|
// Copyright 2021 The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build aix
|
//go:build aix || linux || solaris || zos
|
||||||
|
|
||||||
package terminal
|
package term
|
||||||
|
|
||||||
import "golang.org/x/sys/unix"
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
38
term_unsupported.go
Normal file
38
term_unsupported.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
// Copyright 2019 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !zos && !windows && !solaris && !plan9
|
||||||
|
|
||||||
|
package term
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
type state struct{}
|
||||||
|
|
||||||
|
func isTerminal(fd int) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeRaw(fd int) (*State, error) {
|
||||||
|
return nil, fmt.Errorf("terminal: MakeRaw not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getState(fd int) (*State, error) {
|
||||||
|
return nil, fmt.Errorf("terminal: GetState not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
||||||
|
|
||||||
|
func restore(fd int, state *State) error {
|
||||||
|
return fmt.Errorf("terminal: Restore not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSize(fd int) (width, height int, err error) {
|
||||||
|
return 0, 0, fmt.Errorf("terminal: GetSize not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readPassword(fd int) ([]byte, error) {
|
||||||
|
return nil, fmt.Errorf("terminal: ReadPassword not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
||||||
@@ -1,20 +1,8 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
// Copyright 2019 The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build windows
|
package term
|
||||||
|
|
||||||
// Package terminal provides support functions for dealing with terminals, as
|
|
||||||
// commonly found on UNIX systems.
|
|
||||||
//
|
|
||||||
// Putting a terminal into raw mode is the most common requirement:
|
|
||||||
//
|
|
||||||
// oldState, err := terminal.MakeRaw(0)
|
|
||||||
// if err != nil {
|
|
||||||
// panic(err)
|
|
||||||
// }
|
|
||||||
// defer terminal.Restore(0, oldState)
|
|
||||||
package terminal
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
@@ -22,52 +10,44 @@ import (
|
|||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
)
|
)
|
||||||
|
|
||||||
type State struct {
|
type state struct {
|
||||||
mode uint32
|
mode uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsTerminal returns whether the given file descriptor is a terminal.
|
func isTerminal(fd int) bool {
|
||||||
func IsTerminal(fd int) bool {
|
|
||||||
var st uint32
|
var st uint32
|
||||||
err := windows.GetConsoleMode(windows.Handle(fd), &st)
|
err := windows.GetConsoleMode(windows.Handle(fd), &st)
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeRaw put the terminal connected to the given file descriptor into raw
|
// This is intended to be used on a console input handle.
|
||||||
// mode and returns the previous state of the terminal so that it can be
|
// See https://learn.microsoft.com/en-us/windows/console/setconsolemode
|
||||||
// restored.
|
func makeRaw(fd int) (*State, error) {
|
||||||
func MakeRaw(fd int) (*State, error) {
|
|
||||||
var st uint32
|
var st uint32
|
||||||
if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil {
|
if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
raw := st &^ (windows.ENABLE_ECHO_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT | windows.ENABLE_PROCESSED_OUTPUT)
|
raw := st &^ (windows.ENABLE_ECHO_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT)
|
||||||
|
raw |= windows.ENABLE_VIRTUAL_TERMINAL_INPUT
|
||||||
if err := windows.SetConsoleMode(windows.Handle(fd), raw); err != nil {
|
if err := windows.SetConsoleMode(windows.Handle(fd), raw); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &State{st}, nil
|
return &State{state{st}}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetState returns the current state of a terminal which may be useful to
|
func getState(fd int) (*State, error) {
|
||||||
// restore the terminal after a signal.
|
|
||||||
func GetState(fd int) (*State, error) {
|
|
||||||
var st uint32
|
var st uint32
|
||||||
if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil {
|
if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &State{st}, nil
|
return &State{state{st}}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore restores the terminal connected to the given file descriptor to a
|
func restore(fd int, state *State) error {
|
||||||
// previous state.
|
|
||||||
func Restore(fd int, state *State) error {
|
|
||||||
return windows.SetConsoleMode(windows.Handle(fd), state.mode)
|
return windows.SetConsoleMode(windows.Handle(fd), state.mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSize returns the visible dimensions of the given terminal.
|
func getSize(fd int) (width, height int, err error) {
|
||||||
//
|
|
||||||
// These dimensions don't include any scrollback buffer height.
|
|
||||||
func GetSize(fd int) (width, height int, err error) {
|
|
||||||
var info windows.ConsoleScreenBufferInfo
|
var info windows.ConsoleScreenBufferInfo
|
||||||
if err := windows.GetConsoleScreenBufferInfo(windows.Handle(fd), &info); err != nil {
|
if err := windows.GetConsoleScreenBufferInfo(windows.Handle(fd), &info); err != nil {
|
||||||
return 0, 0, err
|
return 0, 0, err
|
||||||
@@ -75,10 +55,7 @@ func GetSize(fd int) (width, height int, err error) {
|
|||||||
return int(info.Window.Right - info.Window.Left + 1), int(info.Window.Bottom - info.Window.Top + 1), nil
|
return int(info.Window.Right - info.Window.Left + 1), int(info.Window.Bottom - info.Window.Top + 1), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadPassword reads a line of input from a terminal without local echo. This
|
func readPassword(fd int) ([]byte, error) {
|
||||||
// is commonly used for inputting passwords and other sensitive data. The slice
|
|
||||||
// returned does not include the \n.
|
|
||||||
func ReadPassword(fd int) ([]byte, error) {
|
|
||||||
var st uint32
|
var st uint32
|
||||||
if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil {
|
if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
123
terminal.go
123
terminal.go
@@ -2,10 +2,11 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package terminal
|
package term
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -36,6 +37,26 @@ var vt100EscapeCodes = EscapeCodes{
|
|||||||
Reset: []byte{keyEscape, '[', '0', 'm'},
|
Reset: []byte{keyEscape, '[', '0', 'm'},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A History provides a (possibly bounded) queue of input lines read by [Terminal.ReadLine].
|
||||||
|
type History interface {
|
||||||
|
// Add will be called by [Terminal.ReadLine] to add
|
||||||
|
// a new, most recent entry to the history.
|
||||||
|
// It is allowed to drop any entry, including
|
||||||
|
// the entry being added (e.g., if it's deemed an invalid entry),
|
||||||
|
// the least-recent entry (e.g., to keep the history bounded),
|
||||||
|
// or any other entry.
|
||||||
|
Add(entry string)
|
||||||
|
|
||||||
|
// Len returns the number of entries in the history.
|
||||||
|
Len() int
|
||||||
|
|
||||||
|
// At returns an entry from the history.
|
||||||
|
// Index 0 is the most-recently added entry and
|
||||||
|
// index Len()-1 is the least-recently added entry.
|
||||||
|
// If index is < 0 or >= Len(), it panics.
|
||||||
|
At(idx int) string
|
||||||
|
}
|
||||||
|
|
||||||
// Terminal contains the state for running a VT100 terminal that is capable of
|
// Terminal contains the state for running a VT100 terminal that is capable of
|
||||||
// reading lines of input.
|
// reading lines of input.
|
||||||
type Terminal struct {
|
type Terminal struct {
|
||||||
@@ -44,6 +65,8 @@ type Terminal struct {
|
|||||||
// bytes, as an index into |line|). If it returns ok=false, the key
|
// bytes, as an index into |line|). If it returns ok=false, the key
|
||||||
// press is processed normally. Otherwise it returns a replacement line
|
// press is processed normally. Otherwise it returns a replacement line
|
||||||
// and the new cursor position.
|
// and the new cursor position.
|
||||||
|
//
|
||||||
|
// This will be disabled during ReadPassword.
|
||||||
AutoCompleteCallback func(line string, pos int, key rune) (newLine string, newPos int, ok bool)
|
AutoCompleteCallback func(line string, pos int, key rune) (newLine string, newPos int, ok bool)
|
||||||
|
|
||||||
// Escape contains a pointer to the escape codes for this terminal.
|
// Escape contains a pointer to the escape codes for this terminal.
|
||||||
@@ -84,9 +107,14 @@ type Terminal struct {
|
|||||||
remainder []byte
|
remainder []byte
|
||||||
inBuf [256]byte
|
inBuf [256]byte
|
||||||
|
|
||||||
// history contains previously entered commands so that they can be
|
// History records and retrieves lines of input read by [ReadLine] which
|
||||||
// accessed with the up and down keys.
|
// a user can retrieve and navigate using the up and down arrow keys.
|
||||||
history stRingBuffer
|
//
|
||||||
|
// It is not safe to call ReadLine concurrently with any methods on History.
|
||||||
|
//
|
||||||
|
// [NewTerminal] sets this to a default implementation that records the
|
||||||
|
// last 100 lines of input.
|
||||||
|
History History
|
||||||
// historyIndex stores the currently accessed history entry, where zero
|
// historyIndex stores the currently accessed history entry, where zero
|
||||||
// means the immediately previous entry.
|
// means the immediately previous entry.
|
||||||
historyIndex int
|
historyIndex int
|
||||||
@@ -109,6 +137,7 @@ func NewTerminal(c io.ReadWriter, prompt string) *Terminal {
|
|||||||
termHeight: 24,
|
termHeight: 24,
|
||||||
echo: true,
|
echo: true,
|
||||||
historyIndex: -1,
|
historyIndex: -1,
|
||||||
|
History: &stRingBuffer{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,6 +146,7 @@ const (
|
|||||||
keyCtrlD = 4
|
keyCtrlD = 4
|
||||||
keyCtrlU = 21
|
keyCtrlU = 21
|
||||||
keyEnter = '\r'
|
keyEnter = '\r'
|
||||||
|
keyLF = '\n'
|
||||||
keyEscape = 27
|
keyEscape = 27
|
||||||
keyBackspace = 127
|
keyBackspace = 127
|
||||||
keyUnknown = 0xd800 /* UTF-16 surrogate area */ + iota
|
keyUnknown = 0xd800 /* UTF-16 surrogate area */ + iota
|
||||||
@@ -130,7 +160,9 @@ const (
|
|||||||
keyEnd
|
keyEnd
|
||||||
keyDeleteWord
|
keyDeleteWord
|
||||||
keyDeleteLine
|
keyDeleteLine
|
||||||
|
keyDelete
|
||||||
keyClearScreen
|
keyClearScreen
|
||||||
|
keyTranspose
|
||||||
keyPasteStart
|
keyPasteStart
|
||||||
keyPasteEnd
|
keyPasteEnd
|
||||||
)
|
)
|
||||||
@@ -164,6 +196,8 @@ func bytesToKey(b []byte, pasteActive bool) (rune, []byte) {
|
|||||||
return keyDeleteLine, b[1:]
|
return keyDeleteLine, b[1:]
|
||||||
case 12: // ^L
|
case 12: // ^L
|
||||||
return keyClearScreen, b[1:]
|
return keyClearScreen, b[1:]
|
||||||
|
case 20: // ^T
|
||||||
|
return keyTranspose, b[1:]
|
||||||
case 23: // ^W
|
case 23: // ^W
|
||||||
return keyDeleteWord, b[1:]
|
return keyDeleteWord, b[1:]
|
||||||
case 14: // ^N
|
case 14: // ^N
|
||||||
@@ -198,6 +232,10 @@ func bytesToKey(b []byte, pasteActive bool) (rune, []byte) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !pasteActive && len(b) >= 4 && b[0] == keyEscape && b[1] == '[' && b[2] == '3' && b[3] == '~' {
|
||||||
|
return keyDelete, b[4:]
|
||||||
|
}
|
||||||
|
|
||||||
if !pasteActive && len(b) >= 6 && b[0] == keyEscape && b[1] == '[' && b[2] == '1' && b[3] == ';' && b[4] == '3' {
|
if !pasteActive && len(b) >= 6 && b[0] == keyEscape && b[1] == '[' && b[2] == '1' && b[3] == ';' && b[4] == '3' {
|
||||||
switch b[5] {
|
switch b[5] {
|
||||||
case 'C':
|
case 'C':
|
||||||
@@ -233,7 +271,6 @@ func (t *Terminal) queue(data []rune) {
|
|||||||
t.outBuf = append(t.outBuf, []byte(string(data))...)
|
t.outBuf = append(t.outBuf, []byte(string(data))...)
|
||||||
}
|
}
|
||||||
|
|
||||||
var eraseUnderCursor = []rune{' ', keyEscape, '[', 'D'}
|
|
||||||
var space = []rune{' '}
|
var space = []rune{' '}
|
||||||
|
|
||||||
func isPrintable(key rune) bool {
|
func isPrintable(key rune) bool {
|
||||||
@@ -384,7 +421,7 @@ func (t *Terminal) eraseNPreviousChars(n int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// countToLeftWord returns then number of characters from the cursor to the
|
// countToLeftWord returns the number of characters from the cursor to the
|
||||||
// start of the previous word.
|
// start of the previous word.
|
||||||
func (t *Terminal) countToLeftWord() int {
|
func (t *Terminal) countToLeftWord() int {
|
||||||
if t.pos == 0 {
|
if t.pos == 0 {
|
||||||
@@ -409,7 +446,7 @@ func (t *Terminal) countToLeftWord() int {
|
|||||||
return t.pos - pos
|
return t.pos - pos
|
||||||
}
|
}
|
||||||
|
|
||||||
// countToRightWord returns then number of characters from the cursor to the
|
// countToRightWord returns the number of characters from the cursor to the
|
||||||
// start of the next word.
|
// start of the next word.
|
||||||
func (t *Terminal) countToRightWord() int {
|
func (t *Terminal) countToRightWord() int {
|
||||||
pos := t.pos
|
pos := t.pos
|
||||||
@@ -449,10 +486,27 @@ func visualLength(runes []rune) int {
|
|||||||
return length
|
return length
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// historyAt unlocks the terminal and relocks it while calling History.At.
|
||||||
|
func (t *Terminal) historyAt(idx int) (string, bool) {
|
||||||
|
t.lock.Unlock() // Unlock to avoid deadlock if History methods use the output writer.
|
||||||
|
defer t.lock.Lock() // panic in At (or Len) protection.
|
||||||
|
if idx < 0 || idx >= t.History.Len() {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return t.History.At(idx), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// historyAdd unlocks the terminal and relocks it while calling History.Add.
|
||||||
|
func (t *Terminal) historyAdd(entry string) {
|
||||||
|
t.lock.Unlock() // Unlock to avoid deadlock if History methods use the output writer.
|
||||||
|
defer t.lock.Lock() // panic in Add protection.
|
||||||
|
t.History.Add(entry)
|
||||||
|
}
|
||||||
|
|
||||||
// handleKey processes the given key and, optionally, returns a line of text
|
// handleKey processes the given key and, optionally, returns a line of text
|
||||||
// that the user has entered.
|
// that the user has entered.
|
||||||
func (t *Terminal) handleKey(key rune) (line string, ok bool) {
|
func (t *Terminal) handleKey(key rune) (line string, ok bool) {
|
||||||
if t.pasteActive && key != keyEnter {
|
if t.pasteActive && key != keyEnter && key != keyLF {
|
||||||
t.addKeyToLine(key)
|
t.addKeyToLine(key)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -496,7 +550,7 @@ func (t *Terminal) handleKey(key rune) (line string, ok bool) {
|
|||||||
t.pos = len(t.line)
|
t.pos = len(t.line)
|
||||||
t.moveCursorToPos(t.pos)
|
t.moveCursorToPos(t.pos)
|
||||||
case keyUp:
|
case keyUp:
|
||||||
entry, ok := t.history.NthPreviousEntry(t.historyIndex + 1)
|
entry, ok := t.historyAt(t.historyIndex + 1)
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
@@ -515,14 +569,14 @@ func (t *Terminal) handleKey(key rune) (line string, ok bool) {
|
|||||||
t.setLine(runes, len(runes))
|
t.setLine(runes, len(runes))
|
||||||
t.historyIndex--
|
t.historyIndex--
|
||||||
default:
|
default:
|
||||||
entry, ok := t.history.NthPreviousEntry(t.historyIndex - 1)
|
entry, ok := t.historyAt(t.historyIndex - 1)
|
||||||
if ok {
|
if ok {
|
||||||
t.historyIndex--
|
t.historyIndex--
|
||||||
runes := []rune(entry)
|
runes := []rune(entry)
|
||||||
t.setLine(runes, len(runes))
|
t.setLine(runes, len(runes))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case keyEnter:
|
case keyEnter, keyLF:
|
||||||
t.moveCursorToPos(len(t.line))
|
t.moveCursorToPos(len(t.line))
|
||||||
t.queue([]rune("\r\n"))
|
t.queue([]rune("\r\n"))
|
||||||
line = string(t.line)
|
line = string(t.line)
|
||||||
@@ -544,7 +598,7 @@ func (t *Terminal) handleKey(key rune) (line string, ok bool) {
|
|||||||
}
|
}
|
||||||
t.line = t.line[:t.pos]
|
t.line = t.line[:t.pos]
|
||||||
t.moveCursorToPos(t.pos)
|
t.moveCursorToPos(t.pos)
|
||||||
case keyCtrlD:
|
case keyCtrlD, keyDelete:
|
||||||
// Erase the character under the current position.
|
// Erase the character under the current position.
|
||||||
// The EOF case when the line is empty is handled in
|
// The EOF case when the line is empty is handled in
|
||||||
// readLine().
|
// readLine().
|
||||||
@@ -554,6 +608,24 @@ func (t *Terminal) handleKey(key rune) (line string, ok bool) {
|
|||||||
}
|
}
|
||||||
case keyCtrlU:
|
case keyCtrlU:
|
||||||
t.eraseNPreviousChars(t.pos)
|
t.eraseNPreviousChars(t.pos)
|
||||||
|
case keyTranspose:
|
||||||
|
// This transposes the two characters around the cursor and advances the cursor. Best-effort.
|
||||||
|
if len(t.line) < 2 || t.pos < 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
swap := t.pos
|
||||||
|
if swap == len(t.line) {
|
||||||
|
swap-- // special: at end of line, swap previous two chars
|
||||||
|
}
|
||||||
|
t.line[swap-1], t.line[swap] = t.line[swap], t.line[swap-1]
|
||||||
|
if t.pos < len(t.line) {
|
||||||
|
t.pos++
|
||||||
|
}
|
||||||
|
if t.echo {
|
||||||
|
t.moveCursorToPos(swap - 1)
|
||||||
|
t.writeLine(t.line[swap-1:])
|
||||||
|
t.moveCursorToPos(t.pos)
|
||||||
|
}
|
||||||
case keyClearScreen:
|
case keyClearScreen:
|
||||||
// Erases the screen and moves the cursor to the home position.
|
// Erases the screen and moves the cursor to the home position.
|
||||||
t.queue([]rune("\x1b[2J\x1b[H"))
|
t.queue([]rune("\x1b[2J\x1b[H"))
|
||||||
@@ -693,6 +765,8 @@ func (t *Terminal) Write(buf []byte) (n int, err error) {
|
|||||||
|
|
||||||
// ReadPassword temporarily changes the prompt and reads a password, without
|
// ReadPassword temporarily changes the prompt and reads a password, without
|
||||||
// echo, from the terminal.
|
// echo, from the terminal.
|
||||||
|
//
|
||||||
|
// The AutoCompleteCallback is disabled during this call.
|
||||||
func (t *Terminal) ReadPassword(prompt string) (line string, err error) {
|
func (t *Terminal) ReadPassword(prompt string) (line string, err error) {
|
||||||
t.lock.Lock()
|
t.lock.Lock()
|
||||||
defer t.lock.Unlock()
|
defer t.lock.Unlock()
|
||||||
@@ -700,6 +774,11 @@ func (t *Terminal) ReadPassword(prompt string) (line string, err error) {
|
|||||||
oldPrompt := t.prompt
|
oldPrompt := t.prompt
|
||||||
t.prompt = []rune(prompt)
|
t.prompt = []rune(prompt)
|
||||||
t.echo = false
|
t.echo = false
|
||||||
|
oldAutoCompleteCallback := t.AutoCompleteCallback
|
||||||
|
t.AutoCompleteCallback = nil
|
||||||
|
defer func() {
|
||||||
|
t.AutoCompleteCallback = oldAutoCompleteCallback
|
||||||
|
}()
|
||||||
|
|
||||||
line, err = t.readLine()
|
line, err = t.readLine()
|
||||||
|
|
||||||
@@ -760,6 +839,10 @@ func (t *Terminal) readLine() (line string, err error) {
|
|||||||
if !t.pasteActive {
|
if !t.pasteActive {
|
||||||
lineIsPasted = false
|
lineIsPasted = false
|
||||||
}
|
}
|
||||||
|
// If we have CR, consume LF if present (CRLF sequence) to avoid returning an extra empty line.
|
||||||
|
if key == keyEnter && len(rest) > 0 && rest[0] == keyLF {
|
||||||
|
rest = rest[1:]
|
||||||
|
}
|
||||||
line, lineOk = t.handleKey(key)
|
line, lineOk = t.handleKey(key)
|
||||||
}
|
}
|
||||||
if len(rest) > 0 {
|
if len(rest) > 0 {
|
||||||
@@ -773,7 +856,7 @@ func (t *Terminal) readLine() (line string, err error) {
|
|||||||
if lineOk {
|
if lineOk {
|
||||||
if t.echo {
|
if t.echo {
|
||||||
t.historyIndex = -1
|
t.historyIndex = -1
|
||||||
t.history.Add(line)
|
t.historyAdd(line)
|
||||||
}
|
}
|
||||||
if lineIsPasted {
|
if lineIsPasted {
|
||||||
err = ErrPasteIndicator
|
err = ErrPasteIndicator
|
||||||
@@ -930,19 +1013,23 @@ func (s *stRingBuffer) Add(a string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NthPreviousEntry returns the value passed to the nth previous call to Add.
|
func (s *stRingBuffer) Len() int {
|
||||||
|
return s.size
|
||||||
|
}
|
||||||
|
|
||||||
|
// At returns the value passed to the nth previous call to Add.
|
||||||
// If n is zero then the immediately prior value is returned, if one, then the
|
// If n is zero then the immediately prior value is returned, if one, then the
|
||||||
// next most recent, and so on. If such an element doesn't exist then ok is
|
// next most recent, and so on. If such an element doesn't exist then ok is
|
||||||
// false.
|
// false.
|
||||||
func (s *stRingBuffer) NthPreviousEntry(n int) (value string, ok bool) {
|
func (s *stRingBuffer) At(n int) string {
|
||||||
if n >= s.size {
|
if n < 0 || n >= s.size {
|
||||||
return "", false
|
panic(fmt.Sprintf("term: history index [%d] out of range [0,%d)", n, s.size))
|
||||||
}
|
}
|
||||||
index := s.head - n
|
index := s.head - n
|
||||||
if index < 0 {
|
if index < 0 {
|
||||||
index += s.max
|
index += s.max
|
||||||
}
|
}
|
||||||
return s.entries[index], true
|
return s.entries[index]
|
||||||
}
|
}
|
||||||
|
|
||||||
// readPasswordLine reads from reader until it finds \n or io.EOF.
|
// readPasswordLine reads from reader until it finds \n or io.EOF.
|
||||||
|
|||||||
165
terminal_test.go
165
terminal_test.go
@@ -2,12 +2,12 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build aix darwin dragonfly freebsd linux,!appengine netbsd openbsd windows plan9 solaris
|
package term
|
||||||
|
|
||||||
package terminal
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -210,12 +210,24 @@ var keyPressTests = []struct {
|
|||||||
line: "efgh",
|
line: "efgh",
|
||||||
throwAwayLines: 1,
|
throwAwayLines: 1,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// Newline in bracketed paste mode should still work.
|
||||||
|
in: "abc\x1b[200~d\nefg\x1b[201~h\r",
|
||||||
|
line: "efgh",
|
||||||
|
throwAwayLines: 1,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// Lines consisting entirely of pasted data should be indicated as such.
|
// Lines consisting entirely of pasted data should be indicated as such.
|
||||||
in: "\x1b[200~a\r",
|
in: "\x1b[200~a\r",
|
||||||
line: "a",
|
line: "a",
|
||||||
err: ErrPasteIndicator,
|
err: ErrPasteIndicator,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// Lines consisting entirely of pasted data should be indicated as such (\n paste).
|
||||||
|
in: "\x1b[200~a\n",
|
||||||
|
line: "a",
|
||||||
|
err: ErrPasteIndicator,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// Ctrl-C terminates readline
|
// Ctrl-C terminates readline
|
||||||
in: "\003",
|
in: "\003",
|
||||||
@@ -226,6 +238,91 @@ var keyPressTests = []struct {
|
|||||||
in: "a\003\r",
|
in: "a\003\r",
|
||||||
err: io.EOF,
|
err: io.EOF,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// Delete at EOL: nothing
|
||||||
|
in: "abc\x1b[3~\r",
|
||||||
|
line: "abc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Delete in empty string: nothing
|
||||||
|
in: "\x1b[3~\r",
|
||||||
|
line: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Move left, delete: delete 'c'
|
||||||
|
in: "abc\x1b[D\x1b[3~\r",
|
||||||
|
line: "ab",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Home, delete: delete 'a'
|
||||||
|
in: "abc\x1b[H\x1b[3~\r",
|
||||||
|
line: "bc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Home, delete twice: delete 'a' and 'b'
|
||||||
|
in: "abc\x1b[H\x1b[3~\x1b[3~\r",
|
||||||
|
line: "c",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Ctrl-T at end of line: transpose last two chars
|
||||||
|
in: "abc\x14\r",
|
||||||
|
line: "acb",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Ctrl-T at end then type: cursor stays at end
|
||||||
|
in: "abc\x14N\r",
|
||||||
|
line: "acbN",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Ctrl-T in middle: transpose chars before cursor, move cursor forward
|
||||||
|
in: "abc\x1b[D\x14\r",
|
||||||
|
line: "acb",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Ctrl-T in middle then type: cursor moved past swapped char
|
||||||
|
in: "abcd\x1b[D\x1b[D\x14N\r",
|
||||||
|
line: "acbNd",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Ctrl-T at pos 1 then type: cursor moves to pos 2
|
||||||
|
in: "abc\x1b[H\x1b[C\x14N\r",
|
||||||
|
line: "baNc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Ctrl-T with one char: do nothing
|
||||||
|
in: "a\x14\r",
|
||||||
|
line: "a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Ctrl-T with one char then type: cursor unchanged
|
||||||
|
in: "a\x14N\r",
|
||||||
|
line: "aN",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Ctrl-T at beginning: do nothing
|
||||||
|
in: "ab\x1b[H\x14\r",
|
||||||
|
line: "ab",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Ctrl-T at beginning then type: cursor unchanged, inserts at start
|
||||||
|
in: "ab\x1b[H\x14N\r",
|
||||||
|
line: "Nab",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Ctrl-T on empty line: do nothing
|
||||||
|
in: "\x14\r",
|
||||||
|
line: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Multiple Ctrl-T at end: keeps swapping last two
|
||||||
|
in: "abc\x14\x14\r",
|
||||||
|
line: "abc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Multiple Ctrl-T in middle: progresses through line
|
||||||
|
in: "abcd\x1b[D\x1b[D\x1b[D\x14\x14\x14\r",
|
||||||
|
line: "bcda",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestKeyPresses(t *testing.T) {
|
func TestKeyPresses(t *testing.T) {
|
||||||
@@ -298,6 +395,36 @@ func TestRender(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCRLF(t *testing.T) {
|
||||||
|
c := &MockTerminal{
|
||||||
|
toSend: []byte("line1\rline2\r\nline3\n"),
|
||||||
|
// bytesPerRead 0 in this test means read all at once
|
||||||
|
// CR+LF need to be in same read for ReadLine to not produce an extra empty line
|
||||||
|
// which is what terminals do for reasonably small paste. if way many lines are pasted
|
||||||
|
// and going over say 1k-16k buffer, readline current implementation will possibly generate 1
|
||||||
|
// extra empty line, if the CR is in chunk1 and LF in chunk2 (and that's fine).
|
||||||
|
}
|
||||||
|
|
||||||
|
ss := NewTerminal(c, "> ")
|
||||||
|
for i := range 3 {
|
||||||
|
line, err := ss.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read line %d: %v", i+1, err)
|
||||||
|
}
|
||||||
|
expected := fmt.Sprintf("line%d", i+1)
|
||||||
|
if line != expected {
|
||||||
|
t.Fatalf("expected '%s', got '%s'", expected, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
line, err := ss.ReadLine()
|
||||||
|
if !errors.Is(err, io.EOF) {
|
||||||
|
t.Fatalf("expected EOF after 3 lines, got '%s' with error %v", line, err)
|
||||||
|
}
|
||||||
|
if line != "" {
|
||||||
|
t.Fatalf("expected empty line after EOF, got '%s'", line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPasswordNotSaved(t *testing.T) {
|
func TestPasswordNotSaved(t *testing.T) {
|
||||||
c := &MockTerminal{
|
c := &MockTerminal{
|
||||||
toSend: []byte("password\r\x1b[A\r"),
|
toSend: []byte("password\r\x1b[A\r"),
|
||||||
@@ -345,7 +472,7 @@ func TestReadPasswordLineEnd(t *testing.T) {
|
|||||||
input string
|
input string
|
||||||
want string
|
want string
|
||||||
}
|
}
|
||||||
var tests = []testType{
|
tests := []testType{
|
||||||
{"\r\n", ""},
|
{"\r\n", ""},
|
||||||
{"test\r\n", "test"},
|
{"test\r\n", "test"},
|
||||||
{"test\r", "test"},
|
{"test\r", "test"},
|
||||||
@@ -398,6 +525,32 @@ func TestReadPasswordLineEnd(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func MockAutoCompleteCallback(line string, pos int, key rune) (newLine string, newPos int, ok bool) {
|
||||||
|
return "not-good", pos, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadPasswordDisabledAutoCompleteCallback(t *testing.T) {
|
||||||
|
input := "testgood\ranother line\r"
|
||||||
|
expectedPassword := "testgood"
|
||||||
|
terminal := NewTerminal(
|
||||||
|
&MockTerminal{
|
||||||
|
toSend: []byte(input),
|
||||||
|
bytesPerRead: 1,
|
||||||
|
},
|
||||||
|
"prompt")
|
||||||
|
terminal.AutoCompleteCallback = MockAutoCompleteCallback
|
||||||
|
password, err := terminal.ReadPassword("Password: ")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read password: %v", err)
|
||||||
|
}
|
||||||
|
if password != expectedPassword {
|
||||||
|
t.Fatalf("failed to read password, got %q", password)
|
||||||
|
}
|
||||||
|
if terminal.AutoCompleteCallback == nil {
|
||||||
|
t.Fatalf("AutoCompleteCallback should not be nil after ReadPassword")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMakeRawState(t *testing.T) {
|
func TestMakeRawState(t *testing.T) {
|
||||||
fd := int(os.Stdout.Fd())
|
fd := int(os.Stdout.Fd())
|
||||||
if !IsTerminal(fd) {
|
if !IsTerminal(fd) {
|
||||||
@@ -409,7 +562,7 @@ func TestMakeRawState(t *testing.T) {
|
|||||||
t.Fatalf("failed to get terminal state from GetState: %s", err)
|
t.Fatalf("failed to get terminal state from GetState: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if runtime.GOOS == "darwin" && (runtime.GOARCH == "arm" || runtime.GOARCH == "arm64") {
|
if runtime.GOOS == "ios" {
|
||||||
t.Skip("MakeRaw not allowed on iOS; skipping test")
|
t.Skip("MakeRaw not allowed on iOS; skipping test")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,7 +583,7 @@ func TestOutputNewlines(t *testing.T) {
|
|||||||
term := NewTerminal(buf, ">")
|
term := NewTerminal(buf, ">")
|
||||||
|
|
||||||
term.Write([]byte("1\n2\n"))
|
term.Write([]byte("1\n2\n"))
|
||||||
output := string(buf.Bytes())
|
output := buf.String()
|
||||||
const expected = "1\r\n2\r\n"
|
const expected = "1\r\n2\r\n"
|
||||||
|
|
||||||
if output != expected {
|
if output != expected {
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
// Copyright 2013 The Go 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 terminal
|
|
||||||
|
|
||||||
import "golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
const ioctlReadTermios = unix.TCGETS
|
|
||||||
const ioctlWriteTermios = unix.TCSETS
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
// Copyright 2016 The Go 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 terminal provides support functions for dealing with terminals, as
|
|
||||||
// commonly found on UNIX systems.
|
|
||||||
//
|
|
||||||
// Putting a terminal into raw mode is the most common requirement:
|
|
||||||
//
|
|
||||||
// oldState, err := terminal.MakeRaw(0)
|
|
||||||
// if err != nil {
|
|
||||||
// panic(err)
|
|
||||||
// }
|
|
||||||
// defer terminal.Restore(0, oldState)
|
|
||||||
package terminal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
type State struct{}
|
|
||||||
|
|
||||||
// IsTerminal returns whether the given file descriptor is a terminal.
|
|
||||||
func IsTerminal(fd int) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// MakeRaw put 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 int) (*State, error) {
|
|
||||||
return nil, fmt.Errorf("terminal: MakeRaw not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetState returns the current state of a terminal which may be useful to
|
|
||||||
// restore the terminal after a signal.
|
|
||||||
func GetState(fd int) (*State, error) {
|
|
||||||
return nil, fmt.Errorf("terminal: GetState not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore restores the terminal connected to the given file descriptor to a
|
|
||||||
// previous state.
|
|
||||||
func Restore(fd int, state *State) error {
|
|
||||||
return fmt.Errorf("terminal: Restore not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSize returns the dimensions of the given terminal.
|
|
||||||
func GetSize(fd int) (width, height int, err error) {
|
|
||||||
return 0, 0, fmt.Errorf("terminal: GetSize not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 int) ([]byte, error) {
|
|
||||||
return nil, fmt.Errorf("terminal: ReadPassword not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
|
||||||
}
|
|
||||||
124
util_solaris.go
124
util_solaris.go
@@ -1,124 +0,0 @@
|
|||||||
// Copyright 2015 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build solaris
|
|
||||||
|
|
||||||
package terminal // import "golang.org/x/crypto/ssh/terminal"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
"io"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
// State contains the state of a terminal.
|
|
||||||
type State struct {
|
|
||||||
termios unix.Termios
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsTerminal returns whether the given file descriptor is a terminal.
|
|
||||||
func IsTerminal(fd int) bool {
|
|
||||||
_, err := unix.IoctlGetTermio(fd, unix.TCGETA)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 int) ([]byte, error) {
|
|
||||||
// see also: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libast/common/uwin/getpass.c
|
|
||||||
val, err := unix.IoctlGetTermios(fd, unix.TCGETS)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
oldState := *val
|
|
||||||
|
|
||||||
newState := oldState
|
|
||||||
newState.Lflag &^= syscall.ECHO
|
|
||||||
newState.Lflag |= syscall.ICANON | syscall.ISIG
|
|
||||||
newState.Iflag |= syscall.ICRNL
|
|
||||||
err = unix.IoctlSetTermios(fd, unix.TCSETS, &newState)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer unix.IoctlSetTermios(fd, unix.TCSETS, &oldState)
|
|
||||||
|
|
||||||
var buf [16]byte
|
|
||||||
var ret []byte
|
|
||||||
for {
|
|
||||||
n, err := syscall.Read(fd, buf[:])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if n == 0 {
|
|
||||||
if len(ret) == 0 {
|
|
||||||
return nil, io.EOF
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if buf[n-1] == '\n' {
|
|
||||||
n--
|
|
||||||
}
|
|
||||||
ret = append(ret, buf[:n]...)
|
|
||||||
if n < len(buf) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
// see http://cr.illumos.org/~webrev/andy_js/1060/
|
|
||||||
func MakeRaw(fd int) (*State, error) {
|
|
||||||
termios, err := unix.IoctlGetTermios(fd, unix.TCGETS)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
oldState := State{termios: *termios}
|
|
||||||
|
|
||||||
termios.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON
|
|
||||||
termios.Oflag &^= unix.OPOST
|
|
||||||
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(fd, unix.TCSETS, termios); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &oldState, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore restores the terminal connected to the given file descriptor to a
|
|
||||||
// previous state.
|
|
||||||
func Restore(fd int, oldState *State) error {
|
|
||||||
return unix.IoctlSetTermios(fd, unix.TCSETS, &oldState.termios)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetState returns the current state of a terminal which may be useful to
|
|
||||||
// restore the terminal after a signal.
|
|
||||||
func GetState(fd int) (*State, error) {
|
|
||||||
termios, err := unix.IoctlGetTermios(fd, unix.TCGETS)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &State{termios: *termios}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSize returns the dimensions of the given terminal.
|
|
||||||
func GetSize(fd int) (width, height int, err error) {
|
|
||||||
ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
|
|
||||||
if err != nil {
|
|
||||||
return 0, 0, err
|
|
||||||
}
|
|
||||||
return int(ws.Col), int(ws.Row), nil
|
|
||||||
}
|
|
||||||
10
util_zos.go
10
util_zos.go
@@ -1,10 +0,0 @@
|
|||||||
// Copyright 2020 The Go 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 terminal
|
|
||||||
|
|
||||||
import "golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
const ioctlReadTermios = unix.TCGETS
|
|
||||||
const ioctlWriteTermios = unix.TCSETS
|
|
||||||
Reference in New Issue
Block a user