forked from mirror/readline
Compare commits
2 Commits
master
...
feature/ad
Author | SHA1 | Date |
---|---|---|
chzyer | 5ac508ded8 | |
chzyer | 5073debd7e |
|
@ -1 +0,0 @@
|
||||||
.vscode/*
|
|
|
@ -1,8 +1,9 @@
|
||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
- 1.x
|
- 1.5
|
||||||
|
- 1.7
|
||||||
script:
|
script:
|
||||||
- GOOS=windows go install git.internal/re/readline/example/...
|
- GOOS=windows go install github.com/chzyer/readline/example/...
|
||||||
- GOOS=linux go install git.internal/re/readline/example/...
|
- GOOS=linux go install github.com/chzyer/readline/example/...
|
||||||
- GOOS=darwin go install git.internal/re/readline/example/...
|
- GOOS=darwin go install github.com/chzyer/readline/example/...
|
||||||
- go test -race -v
|
- go test -race -v
|
||||||
|
|
36
CHANGELOG.md
36
CHANGELOG.md
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
* [#38][38] add SetChildren for prefix completer interface
|
* [#38][38] add SetChildren for prefix completer interface
|
||||||
* [#42][42] improve multiple lines compatibility
|
* [#42][42] improve multiple lines compatibility
|
||||||
* [#43][43] remove sub-package(runes) for gopkg compatibility
|
* [#43][43] remove sub-package(runes) for gopkg compatiblity
|
||||||
* [#46][46] Auto complete with space prefixed line
|
* [#46][46] Auto complete with space prefixed line
|
||||||
* [#48][48] support suspend process (ctrl+Z)
|
* [#48][48] support suspend process (ctrl+Z)
|
||||||
* [#49][49] fix bug that check equals with previous command
|
* [#49][49] fix bug that check equals with previous command
|
||||||
|
@ -19,12 +19,12 @@
|
||||||
|
|
||||||
### 1.2 - 2016-03-05
|
### 1.2 - 2016-03-05
|
||||||
|
|
||||||
* Add a demo for checking password strength [example/readline-pass-strength](https://git.internal/re/readline/blob/master/example/readline-pass-strength/readline-pass-strength.go), , written by [@sahib](https://github.com/sahib)
|
* Add a demo for checking password strength [example/readline-pass-strength](https://github.com/chzyer/readline/blob/master/example/readline-pass-strength/readline-pass-strength.go), , written by [@sahib](https://github.com/sahib)
|
||||||
* [#23][23], support stdin remapping
|
* [#23][23], support stdin remapping
|
||||||
* [#27][27], add a `UniqueEditLine` to `Config`, which will erase the editing line after user submited it, usually use in IM.
|
* [#27][27], add a `UniqueEditLine` to `Config`, which will erase the editing line after user submited it, usually use in IM.
|
||||||
* Add a demo for multiline [example/readline-multiline](https://git.internal/re/readline/blob/master/example/readline-multiline/readline-multiline.go) which can submit one SQL by multiple lines.
|
* Add a demo for multiline [example/readline-multiline](https://github.com/chzyer/readline/blob/master/example/readline-multiline/readline-multiline.go) which can submit one SQL by multiple lines.
|
||||||
* Supports performs even stdin/stdout is not a tty.
|
* Supports performs even stdin/stdout is not a tty.
|
||||||
* Add a new simple apis for single instance, check by [here](https://git.internal/re/readline/blob/master/std.go). It need to save history manually if using this api.
|
* Add a new simple apis for single instance, check by [here](https://github.com/chzyer/readline/blob/master/std.go). It need to save history manually if using this api.
|
||||||
* [#28][28], fixes the history is not working as expected.
|
* [#28][28], fixes the history is not working as expected.
|
||||||
* [#33][33], vim mode now support `c`, `d`, `x (delete character)`, `r (replace character)`
|
* [#33][33], vim mode now support `c`, `d`, `x (delete character)`, `r (replace character)`
|
||||||
|
|
||||||
|
@ -42,17 +42,17 @@
|
||||||
|
|
||||||
* Initial public release.
|
* Initial public release.
|
||||||
|
|
||||||
[12]: https://git.internal/re/readline/pull/12
|
[12]: https://github.com/chzyer/readline/pull/12
|
||||||
[17]: https://git.internal/re/readline/pull/17
|
[17]: https://github.com/chzyer/readline/pull/17
|
||||||
[23]: https://git.internal/re/readline/pull/23
|
[23]: https://github.com/chzyer/readline/pull/23
|
||||||
[27]: https://git.internal/re/readline/pull/27
|
[27]: https://github.com/chzyer/readline/pull/27
|
||||||
[28]: https://git.internal/re/readline/pull/28
|
[28]: https://github.com/chzyer/readline/pull/28
|
||||||
[33]: https://git.internal/re/readline/pull/33
|
[33]: https://github.com/chzyer/readline/pull/33
|
||||||
[38]: https://git.internal/re/readline/pull/38
|
[38]: https://github.com/chzyer/readline/pull/38
|
||||||
[42]: https://git.internal/re/readline/pull/42
|
[42]: https://github.com/chzyer/readline/pull/42
|
||||||
[43]: https://git.internal/re/readline/pull/43
|
[43]: https://github.com/chzyer/readline/pull/43
|
||||||
[46]: https://git.internal/re/readline/pull/46
|
[46]: https://github.com/chzyer/readline/pull/46
|
||||||
[48]: https://git.internal/re/readline/pull/48
|
[48]: https://github.com/chzyer/readline/pull/48
|
||||||
[49]: https://git.internal/re/readline/pull/49
|
[49]: https://github.com/chzyer/readline/pull/49
|
||||||
[53]: https://git.internal/re/readline/pull/53
|
[53]: https://github.com/chzyer/readline/pull/53
|
||||||
[60]: https://git.internal/re/readline/pull/60
|
[60]: https://github.com/chzyer/readline/pull/60
|
||||||
|
|
11
README.md
11
README.md
|
@ -1,7 +1,7 @@
|
||||||
[![Build Status](https://travis-ci.org/chzyer/readline.svg?branch=master)](https://travis-ci.org/chzyer/readline)
|
[![Build Status](https://travis-ci.org/chzyer/readline.svg?branch=master)](https://travis-ci.org/chzyer/readline)
|
||||||
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE.md)
|
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE.md)
|
||||||
[![Version](https://img.shields.io/github/tag/chzyer/readline.svg)](https://git.internal/re/readline/releases)
|
[![Version](https://img.shields.io/github/tag/chzyer/readline.svg)](https://github.com/chzyer/readline/releases)
|
||||||
[![GoDoc](https://godoc.org/git.internal/re/readline?status.svg)](https://godoc.org/git.internal/re/readline)
|
[![GoDoc](https://godoc.org/github.com/chzyer/readline?status.svg)](https://godoc.org/github.com/chzyer/readline)
|
||||||
[![OpenCollective](https://opencollective.com/readline/badge/backers.svg)](#backers)
|
[![OpenCollective](https://opencollective.com/readline/badge/backers.svg)](#backers)
|
||||||
[![OpenCollective](https://opencollective.com/readline/badge/sponsors.svg)](#sponsors)
|
[![OpenCollective](https://opencollective.com/readline/badge/sponsors.svg)](#sponsors)
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@
|
||||||
<img src="https://raw.githubusercontent.com/chzyer/readline/assets/logo_f.png" />
|
<img src="https://raw.githubusercontent.com/chzyer/readline/assets/logo_f.png" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
A powerful readline library in `Linux` `macOS` `Windows` `Solaris` `AIX`
|
A powerful readline library in `Linux` `macOS` `Windows`
|
||||||
|
|
||||||
## Guide
|
## Guide
|
||||||
|
|
||||||
|
@ -21,15 +21,14 @@ A powerful readline library in `Linux` `macOS` `Windows` `Solaris` `AIX`
|
||||||
## Repos using readline
|
## Repos using readline
|
||||||
|
|
||||||
[![cockroachdb](https://img.shields.io/github/stars/cockroachdb/cockroach.svg?label=cockroachdb/cockroach)](https://github.com/cockroachdb/cockroach)
|
[![cockroachdb](https://img.shields.io/github/stars/cockroachdb/cockroach.svg?label=cockroachdb/cockroach)](https://github.com/cockroachdb/cockroach)
|
||||||
[![robertkrimen/otto](https://img.shields.io/github/stars/robertkrimen/otto.svg?label=robertkrimen/otto)](https://github.com/robertkrimen/otto)
|
|
||||||
[![empire](https://img.shields.io/github/stars/remind101/empire.svg?label=remind101/empire)](https://github.com/remind101/empire)
|
[![empire](https://img.shields.io/github/stars/remind101/empire.svg?label=remind101/empire)](https://github.com/remind101/empire)
|
||||||
[![mehrdadrad/mylg](https://img.shields.io/github/stars/mehrdadrad/mylg.svg?label=mehrdadrad/mylg)](https://github.com/mehrdadrad/mylg)
|
|
||||||
[![knq/usql](https://img.shields.io/github/stars/knq/usql.svg?label=knq/usql)](https://github.com/knq/usql)
|
|
||||||
[![youtube/doorman](https://img.shields.io/github/stars/youtube/doorman.svg?label=youtube/doorman)](https://github.com/youtube/doorman)
|
[![youtube/doorman](https://img.shields.io/github/stars/youtube/doorman.svg?label=youtube/doorman)](https://github.com/youtube/doorman)
|
||||||
[![bom-d-van/harp](https://img.shields.io/github/stars/bom-d-van/harp.svg?label=bom-d-van/harp)](https://github.com/bom-d-van/harp)
|
[![bom-d-van/harp](https://img.shields.io/github/stars/bom-d-van/harp.svg?label=bom-d-van/harp)](https://github.com/bom-d-van/harp)
|
||||||
[![abiosoft/ishell](https://img.shields.io/github/stars/abiosoft/ishell.svg?label=abiosoft/ishell)](https://github.com/abiosoft/ishell)
|
[![abiosoft/ishell](https://img.shields.io/github/stars/abiosoft/ishell.svg?label=abiosoft/ishell)](https://github.com/abiosoft/ishell)
|
||||||
|
[![robertkrimen/otto](https://img.shields.io/github/stars/robertkrimen/otto.svg?label=robertkrimen/otto)](https://github.com/robertkrimen/otto)
|
||||||
[![Netflix/hal-9001](https://img.shields.io/github/stars/Netflix/hal-9001.svg?label=Netflix/hal-9001)](https://github.com/Netflix/hal-9001)
|
[![Netflix/hal-9001](https://img.shields.io/github/stars/Netflix/hal-9001.svg?label=Netflix/hal-9001)](https://github.com/Netflix/hal-9001)
|
||||||
[![docker/go-p9p](https://img.shields.io/github/stars/docker/go-p9p.svg?label=docker/go-p9p)](https://github.com/docker/go-p9p)
|
[![docker/go-p9p](https://img.shields.io/github/stars/docker/go-p9p.svg?label=docker/go-p9p)](https://github.com/docker/go-p9p)
|
||||||
|
[![mehrdadrad/mylg](https://img.shields.io/github/stars/mehrdadrad/mylg.svg?label=mehrdadrad/mylg)](https://github.com/mehrdadrad/mylg)
|
||||||
|
|
||||||
|
|
||||||
## Feedback
|
## Feedback
|
||||||
|
|
|
@ -25,7 +25,6 @@ const (
|
||||||
COLOR_BINTENSITY = 0x0080
|
COLOR_BINTENSITY = 0x0080
|
||||||
|
|
||||||
COMMON_LVB_UNDERSCORE = 0x8000
|
COMMON_LVB_UNDERSCORE = 0x8000
|
||||||
COMMON_LVB_BOLD = 0x0007
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var ColorTableFg = []word{
|
var ColorTableFg = []word{
|
||||||
|
@ -164,8 +163,6 @@ func (a *ANSIWriterCtx) ioloopEscSeq(w *bufio.Writer, r rune, argptr *[]string)
|
||||||
color |= ColorTableBg[c-40]
|
color |= ColorTableBg[c-40]
|
||||||
} else if c == 4 {
|
} else if c == 4 {
|
||||||
color |= COMMON_LVB_UNDERSCORE | ColorTableFg[7]
|
color |= COMMON_LVB_UNDERSCORE | ColorTableFg[7]
|
||||||
} else if c == 1 {
|
|
||||||
color |= COMMON_LVB_BOLD | COLOR_FINTENSITY
|
|
||||||
} else { // unknown code treat as reset
|
} else { // unknown code treat as reset
|
||||||
color = ColorTableFg[7]
|
color = ColorTableFg[7]
|
||||||
}
|
}
|
||||||
|
|
|
@ -203,9 +203,7 @@ func (o *opCompleter) CompleteRefresh() {
|
||||||
// -1 to avoid reach the end of line
|
// -1 to avoid reach the end of line
|
||||||
width := o.width - 1
|
width := o.width - 1
|
||||||
colNum := width / colWidth
|
colNum := width / colWidth
|
||||||
if colNum != 0 {
|
|
||||||
colWidth += (width - (colWidth * colNum)) / colNum
|
colWidth += (width - (colWidth * colNum)) / colNum
|
||||||
}
|
|
||||||
|
|
||||||
o.candidateColNum = colNum
|
o.candidateColNum = colNum
|
||||||
buf := bufio.NewWriter(o.w)
|
buf := bufio.NewWriter(o.w)
|
||||||
|
@ -221,7 +219,7 @@ func (o *opCompleter) CompleteRefresh() {
|
||||||
}
|
}
|
||||||
buf.WriteString(string(same))
|
buf.WriteString(string(same))
|
||||||
buf.WriteString(string(c))
|
buf.WriteString(string(c))
|
||||||
buf.Write(bytes.Repeat([]byte(" "), colWidth-runes.WidthAll(c)-runes.WidthAll(same)))
|
buf.Write(bytes.Repeat([]byte(" "), colWidth-len(c)))
|
||||||
|
|
||||||
if inSelect {
|
if inSelect {
|
||||||
buf.WriteString("\033[0m")
|
buf.WriteString("\033[0m")
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.internal/re/readline"
|
"github.com/chzyer/readline"
|
||||||
)
|
)
|
||||||
|
|
||||||
func usage(w io.Writer) {
|
func usage(w io.Writer) {
|
||||||
|
@ -61,15 +61,6 @@ var completer = readline.NewPrefixCompleter(
|
||||||
readline.PcItem("sleep"),
|
readline.PcItem("sleep"),
|
||||||
)
|
)
|
||||||
|
|
||||||
func filterInput(r rune) (rune, bool) {
|
|
||||||
switch r {
|
|
||||||
// block CtrlZ feature
|
|
||||||
case readline.CharCtrlZ:
|
|
||||||
return r, false
|
|
||||||
}
|
|
||||||
return r, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
l, err := readline.NewEx(&readline.Config{
|
l, err := readline.NewEx(&readline.Config{
|
||||||
Prompt: "\033[31m»\033[0m ",
|
Prompt: "\033[31m»\033[0m ",
|
||||||
|
@ -79,13 +70,11 @@ func main() {
|
||||||
EOFPrompt: "exit",
|
EOFPrompt: "exit",
|
||||||
|
|
||||||
HistorySearchFold: true,
|
HistorySearchFold: true,
|
||||||
FuncFilterInputRune: filterInput,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
defer l.Close()
|
defer l.Close()
|
||||||
l.CaptureExitSignal()
|
|
||||||
|
|
||||||
setPasswordCfg := l.GenPasswordConfig()
|
setPasswordCfg := l.GenPasswordConfig()
|
||||||
setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {
|
setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {
|
||||||
|
@ -138,11 +127,12 @@ func main() {
|
||||||
println("you set:", strconv.Quote(string(pswd)))
|
println("you set:", strconv.Quote(string(pswd)))
|
||||||
}
|
}
|
||||||
case strings.HasPrefix(line, "setprompt"):
|
case strings.HasPrefix(line, "setprompt"):
|
||||||
if len(line) <= 10 {
|
prompt := line[10:]
|
||||||
|
if prompt == "" {
|
||||||
log.Println("setprompt <prompt>")
|
log.Println("setprompt <prompt>")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
l.SetPrompt(line[10:])
|
l.SetPrompt(prompt)
|
||||||
case strings.HasPrefix(line, "say"):
|
case strings.HasPrefix(line, "say"):
|
||||||
line := strings.TrimSpace(line[3:])
|
line := strings.TrimSpace(line[3:])
|
||||||
if len(line) == 0 {
|
if len(line) == 0 {
|
||||||
|
|
|
@ -2,12 +2,12 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.internal/re/readline"
|
"github.com/chzyer/readline"
|
||||||
)
|
)
|
||||||
|
import "log"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
rl, err := readline.NewEx(&readline.Config{
|
rl, err := readline.NewEx(&readline.Config{
|
||||||
|
|
|
@ -3,7 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.internal/re/readline"
|
"github.com/chzyer/readline"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
|
@ -22,7 +22,8 @@ package main
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.internal/re/readline"
|
"github.com/chzyer/readline"
|
||||||
|
zxcvbn "github.com/nbutton23/zxcvbn-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -49,24 +50,30 @@ func Colorize(msg string, color int) string {
|
||||||
|
|
||||||
func createStrengthPrompt(password []rune) string {
|
func createStrengthPrompt(password []rune) string {
|
||||||
symbol, color := "", Red
|
symbol, color := "", Red
|
||||||
|
strength := zxcvbn.PasswordStrength(string(password), nil)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case len(password) <= 1:
|
case strength.Score <= 1:
|
||||||
symbol = "✗"
|
symbol = "✗"
|
||||||
color = Red
|
color = Red
|
||||||
case len(password) <= 3:
|
case strength.Score <= 2:
|
||||||
symbol = "⚡"
|
symbol = "⚡"
|
||||||
color = Magenta
|
color = Magenta
|
||||||
case len(password) <= 5:
|
case strength.Score <= 3:
|
||||||
symbol = "⚠"
|
symbol = "⚠"
|
||||||
color = Yellow
|
color = Yellow
|
||||||
default:
|
case strength.Score <= 4:
|
||||||
symbol = "✔"
|
symbol = "✔"
|
||||||
color = Green
|
color = Green
|
||||||
}
|
}
|
||||||
|
|
||||||
prompt := Colorize(symbol, color)
|
prompt := Colorize(symbol, color)
|
||||||
|
if strength.Entropy > 0 {
|
||||||
|
entropy := fmt.Sprintf(" %3.0f", strength.Entropy)
|
||||||
|
prompt += Colorize(entropy, Cyan)
|
||||||
|
} else {
|
||||||
prompt += Colorize(" ENT", Cyan)
|
prompt += Colorize(" ENT", Cyan)
|
||||||
|
}
|
||||||
|
|
||||||
prompt += Colorize(" New Password: ", color)
|
prompt += Colorize(" New Password: ", color)
|
||||||
return prompt
|
return prompt
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import "git.internal/re/readline"
|
import "github.com/chzyer/readline"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if err := readline.DialRemote("tcp", ":12344"); err != nil {
|
if err := readline.DialRemote("tcp", ":12344"); err != nil {
|
||||||
|
|
|
@ -3,7 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.internal/re/readline"
|
"github.com/chzyer/readline"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
8
go.mod
8
go.mod
|
@ -1,8 +0,0 @@
|
||||||
module git.internal/re/readline
|
|
||||||
|
|
||||||
go 1.15
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/chzyer/test v1.0.0
|
|
||||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5
|
|
||||||
)
|
|
6
go.sum
6
go.sum
|
@ -1,6 +0,0 @@
|
||||||
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
|
|
||||||
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
|
|
||||||
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
|
|
||||||
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
|
||||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng=
|
|
||||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
20
history.go
20
history.go
|
@ -27,14 +27,12 @@ type opHistory struct {
|
||||||
current *list.Element
|
current *list.Element
|
||||||
fd *os.File
|
fd *os.File
|
||||||
fdLock sync.Mutex
|
fdLock sync.Mutex
|
||||||
enable bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newOpHistory(cfg *Config) (o *opHistory) {
|
func newOpHistory(cfg *Config) (o *opHistory) {
|
||||||
o = &opHistory{
|
o = &opHistory{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
history: list.New(),
|
history: list.New(),
|
||||||
enable: true,
|
|
||||||
}
|
}
|
||||||
return o
|
return o
|
||||||
}
|
}
|
||||||
|
@ -119,7 +117,7 @@ func (o *opHistory) rewriteLocked() {
|
||||||
|
|
||||||
buf := bufio.NewWriter(fd)
|
buf := bufio.NewWriter(fd)
|
||||||
for elem := o.history.Front(); elem != nil; elem = elem.Next() {
|
for elem := o.history.Front(); elem != nil; elem = elem.Next() {
|
||||||
buf.WriteString(string(elem.Value.(*hisItem).Source) + "\n")
|
buf.WriteString(string(elem.Value.(*hisItem).Source))
|
||||||
}
|
}
|
||||||
buf.Flush()
|
buf.Flush()
|
||||||
|
|
||||||
|
@ -225,16 +223,6 @@ func (o *opHistory) Next() ([]rune, bool) {
|
||||||
return runes.Copy(o.showItem(current.Value)), true
|
return runes.Copy(o.showItem(current.Value)), true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable the current history
|
|
||||||
func (o *opHistory) Disable() {
|
|
||||||
o.enable = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enable the current history
|
|
||||||
func (o *opHistory) Enable() {
|
|
||||||
o.enable = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *opHistory) debug() {
|
func (o *opHistory) debug() {
|
||||||
Debug("-------")
|
Debug("-------")
|
||||||
for item := o.history.Front(); item != nil; item = item.Next() {
|
for item := o.history.Front(); item != nil; item = item.Next() {
|
||||||
|
@ -244,12 +232,6 @@ func (o *opHistory) debug() {
|
||||||
|
|
||||||
// save history
|
// save history
|
||||||
func (o *opHistory) New(current []rune) (err error) {
|
func (o *opHistory) New(current []rune) (err error) {
|
||||||
|
|
||||||
// history deactivated
|
|
||||||
if !o.enable {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
current = runes.Copy(current)
|
current = runes.Copy(current)
|
||||||
|
|
||||||
// if just use last command without modify
|
// if just use last command without modify
|
||||||
|
|
78
operation.go
78
operation.go
|
@ -3,7 +3,6 @@ package readline
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -19,7 +18,6 @@ func (*InterruptError) Error() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Operation struct {
|
type Operation struct {
|
||||||
m sync.Mutex
|
|
||||||
cfg *Config
|
cfg *Config
|
||||||
t *Terminal
|
t *Terminal
|
||||||
buf *RuneBuffer
|
buf *RuneBuffer
|
||||||
|
@ -34,10 +32,6 @@ type Operation struct {
|
||||||
*opVim
|
*opVim
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Operation) SetBuffer(what string) {
|
|
||||||
o.buf.Set([]rune(what))
|
|
||||||
}
|
|
||||||
|
|
||||||
type wrapWriter struct {
|
type wrapWriter struct {
|
||||||
r *Operation
|
r *Operation
|
||||||
t *Terminal
|
t *Terminal
|
||||||
|
@ -72,7 +66,7 @@ func NewOperation(t *Terminal, cfg *Config) *Operation {
|
||||||
t: t,
|
t: t,
|
||||||
buf: NewRuneBuffer(t, cfg.Prompt, cfg, width),
|
buf: NewRuneBuffer(t, cfg.Prompt, cfg, width),
|
||||||
outchan: make(chan []rune),
|
outchan: make(chan []rune),
|
||||||
errchan: make(chan error, 1),
|
errchan: make(chan error),
|
||||||
}
|
}
|
||||||
op.w = op.buf.w
|
op.w = op.buf.w
|
||||||
op.SetConfig(cfg)
|
op.SetConfig(cfg)
|
||||||
|
@ -97,29 +91,11 @@ func (o *Operation) SetMaskRune(r rune) {
|
||||||
o.buf.SetMask(r)
|
o.buf.SetMask(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Operation) GetConfig() *Config {
|
|
||||||
o.m.Lock()
|
|
||||||
cfg := *o.cfg
|
|
||||||
o.m.Unlock()
|
|
||||||
return &cfg
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Operation) ioloop() {
|
func (o *Operation) ioloop() {
|
||||||
for {
|
for {
|
||||||
keepInSearchMode := false
|
keepInSearchMode := false
|
||||||
keepInCompleteMode := false
|
keepInCompleteMode := false
|
||||||
r := o.t.ReadRune()
|
r := o.t.ReadRune()
|
||||||
|
|
||||||
if o.GetConfig().FuncFilterInputRune != nil {
|
|
||||||
var process bool
|
|
||||||
r, process = o.GetConfig().FuncFilterInputRune(r)
|
|
||||||
if !process {
|
|
||||||
o.t.KickRead()
|
|
||||||
o.buf.Refresh(nil) // to refresh the line
|
|
||||||
continue // ignore this rune
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if r == 0 { // io.EOF
|
if r == 0 { // io.EOF
|
||||||
if o.buf.Len() == 0 {
|
if o.buf.Len() == 0 {
|
||||||
o.buf.Clean()
|
o.buf.Clean()
|
||||||
|
@ -173,7 +149,7 @@ func (o *Operation) ioloop() {
|
||||||
o.buf.Refresh(nil)
|
o.buf.Refresh(nil)
|
||||||
}
|
}
|
||||||
case CharTab:
|
case CharTab:
|
||||||
if o.GetConfig().AutoComplete == nil {
|
if o.cfg.AutoComplete == nil {
|
||||||
o.t.Bell()
|
o.t.Bell()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -237,15 +213,13 @@ func (o *Operation) ioloop() {
|
||||||
o.Refresh()
|
o.Refresh()
|
||||||
case MetaBackspace, CharCtrlW:
|
case MetaBackspace, CharCtrlW:
|
||||||
o.buf.BackEscapeWord()
|
o.buf.BackEscapeWord()
|
||||||
case CharCtrlY:
|
|
||||||
o.buf.Yank()
|
|
||||||
case CharEnter, CharCtrlJ:
|
case CharEnter, CharCtrlJ:
|
||||||
if o.IsSearchMode() {
|
if o.IsSearchMode() {
|
||||||
o.ExitSearchMode(false)
|
o.ExitSearchMode(false)
|
||||||
}
|
}
|
||||||
o.buf.MoveToLineEnd()
|
o.buf.MoveToLineEnd()
|
||||||
var data []rune
|
var data []rune
|
||||||
if !o.GetConfig().UniqueEditLine {
|
if !o.cfg.UniqueEditLine {
|
||||||
o.buf.WriteRune('\n')
|
o.buf.WriteRune('\n')
|
||||||
data = o.buf.Reset()
|
data = o.buf.Reset()
|
||||||
data = data[:len(data)-1] // trim \n
|
data = data[:len(data)-1] // trim \n
|
||||||
|
@ -254,7 +228,7 @@ func (o *Operation) ioloop() {
|
||||||
data = o.buf.Reset()
|
data = o.buf.Reset()
|
||||||
}
|
}
|
||||||
o.outchan <- data
|
o.outchan <- data
|
||||||
if !o.GetConfig().DisableAutoSaveHistory {
|
if !o.cfg.DisableAutoSaveHistory {
|
||||||
// ignore IO error
|
// ignore IO error
|
||||||
_ = o.history.New(data)
|
_ = o.history.New(data)
|
||||||
} else {
|
} else {
|
||||||
|
@ -288,14 +262,14 @@ func (o *Operation) ioloop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// treat as EOF
|
// treat as EOF
|
||||||
if !o.GetConfig().UniqueEditLine {
|
if !o.cfg.UniqueEditLine {
|
||||||
o.buf.WriteString(o.GetConfig().EOFPrompt + "\n")
|
o.buf.WriteString(o.cfg.EOFPrompt + "\n")
|
||||||
}
|
}
|
||||||
o.buf.Reset()
|
o.buf.Reset()
|
||||||
isUpdateHistory = false
|
isUpdateHistory = false
|
||||||
o.history.Revert()
|
o.history.Revert()
|
||||||
o.errchan <- io.EOF
|
o.errchan <- io.EOF
|
||||||
if o.GetConfig().UniqueEditLine {
|
if o.cfg.UniqueEditLine {
|
||||||
o.buf.Clean()
|
o.buf.Clean()
|
||||||
}
|
}
|
||||||
case CharInterrupt:
|
case CharInterrupt:
|
||||||
|
@ -312,12 +286,12 @@ func (o *Operation) ioloop() {
|
||||||
}
|
}
|
||||||
o.buf.MoveToLineEnd()
|
o.buf.MoveToLineEnd()
|
||||||
o.buf.Refresh(nil)
|
o.buf.Refresh(nil)
|
||||||
hint := o.GetConfig().InterruptPrompt + "\n"
|
hint := o.cfg.InterruptPrompt + "\n"
|
||||||
if !o.GetConfig().UniqueEditLine {
|
if !o.cfg.UniqueEditLine {
|
||||||
o.buf.WriteString(hint)
|
o.buf.WriteString(hint)
|
||||||
}
|
}
|
||||||
remain := o.buf.Reset()
|
remain := o.buf.Reset()
|
||||||
if !o.GetConfig().UniqueEditLine {
|
if !o.cfg.UniqueEditLine {
|
||||||
remain = remain[:len(remain)-len([]rune(hint))]
|
remain = remain[:len(remain)-len([]rune(hint))]
|
||||||
}
|
}
|
||||||
isUpdateHistory = false
|
isUpdateHistory = false
|
||||||
|
@ -336,15 +310,13 @@ func (o *Operation) ioloop() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
listener := o.GetConfig().Listener
|
if o.cfg.Listener != nil {
|
||||||
if listener != nil {
|
newLine, newPos, ok := o.cfg.Listener.OnChange(o.buf.Runes(), o.buf.Pos(), r)
|
||||||
newLine, newPos, ok := listener.OnChange(o.buf.Runes(), o.buf.Pos(), r)
|
|
||||||
if ok {
|
if ok {
|
||||||
o.buf.SetWithIdx(newPos, newLine)
|
o.buf.SetWithIdx(newPos, newLine)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
o.m.Lock()
|
|
||||||
if !keepInSearchMode && o.IsSearchMode() {
|
if !keepInSearchMode && o.IsSearchMode() {
|
||||||
o.ExitSearchMode(false)
|
o.ExitSearchMode(false)
|
||||||
o.buf.Refresh(nil)
|
o.buf.Refresh(nil)
|
||||||
|
@ -361,16 +333,15 @@ func (o *Operation) ioloop() {
|
||||||
// it will cause null history
|
// it will cause null history
|
||||||
o.history.Update(o.buf.Runes(), false)
|
o.history.Update(o.buf.Runes(), false)
|
||||||
}
|
}
|
||||||
o.m.Unlock()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Operation) Stderr() io.Writer {
|
func (o *Operation) Stderr() io.Writer {
|
||||||
return &wrapWriter{target: o.GetConfig().Stderr, r: o, t: o.t}
|
return &wrapWriter{target: o.cfg.Stderr, r: o, t: o.t}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Operation) Stdout() io.Writer {
|
func (o *Operation) Stdout() io.Writer {
|
||||||
return &wrapWriter{target: o.GetConfig().Stdout, r: o, t: o.t}
|
return &wrapWriter{target: o.cfg.Stdout, r: o, t: o.t}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Operation) String() (string, error) {
|
func (o *Operation) String() (string, error) {
|
||||||
|
@ -382,9 +353,8 @@ func (o *Operation) Runes() ([]rune, error) {
|
||||||
o.t.EnterRawMode()
|
o.t.EnterRawMode()
|
||||||
defer o.t.ExitRawMode()
|
defer o.t.ExitRawMode()
|
||||||
|
|
||||||
listener := o.GetConfig().Listener
|
if o.cfg.Listener != nil {
|
||||||
if listener != nil {
|
o.cfg.Listener.OnChange(nil, 0, 0)
|
||||||
listener.OnChange(nil, 0, 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
o.buf.Refresh(nil) // print prompt
|
o.buf.Refresh(nil) // print prompt
|
||||||
|
@ -436,10 +406,6 @@ func (o *Operation) Slice() ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Operation) Close() {
|
func (o *Operation) Close() {
|
||||||
select {
|
|
||||||
case o.errchan <- io.EOF:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
o.history.Close()
|
o.history.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -456,8 +422,6 @@ func (o *Operation) IsNormalMode() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (op *Operation) SetConfig(cfg *Config) (*Config, error) {
|
func (op *Operation) SetConfig(cfg *Config) (*Config, error) {
|
||||||
op.m.Lock()
|
|
||||||
defer op.m.Unlock()
|
|
||||||
if op.cfg == cfg {
|
if op.cfg == cfg {
|
||||||
return op.cfg, nil
|
return op.cfg, nil
|
||||||
}
|
}
|
||||||
|
@ -525,13 +489,3 @@ func (d *DumpListener) OnChange(line []rune, pos int, key rune) (newLine []rune,
|
||||||
type Listener interface {
|
type Listener interface {
|
||||||
OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)
|
OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Painter interface {
|
|
||||||
Paint(line []rune, pos int) []rune
|
|
||||||
}
|
|
||||||
|
|
||||||
type defaultPainter struct{}
|
|
||||||
|
|
||||||
func (p *defaultPainter) Paint(line []rune, _ int) []rune {
|
|
||||||
return line
|
|
||||||
}
|
|
||||||
|
|
|
@ -25,7 +25,6 @@ func (o *opPassword) PasswordConfig() *Config {
|
||||||
InterruptPrompt: "\n",
|
InterruptPrompt: "\n",
|
||||||
EOFPrompt: "\n",
|
EOFPrompt: "\n",
|
||||||
HistoryLimit: -1,
|
HistoryLimit: -1,
|
||||||
Painter: &defaultPainter{},
|
|
||||||
|
|
||||||
Stdout: o.o.cfg.Stdout,
|
Stdout: o.o.cfg.Stdout,
|
||||||
Stderr: o.o.cfg.Stderr,
|
Stderr: o.o.cfg.Stderr,
|
||||||
|
|
65
readline.go
65
readline.go
|
@ -17,9 +17,7 @@
|
||||||
//
|
//
|
||||||
package readline
|
package readline
|
||||||
|
|
||||||
import (
|
import "io"
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Instance struct {
|
type Instance struct {
|
||||||
Config *Config
|
Config *Config
|
||||||
|
@ -46,8 +44,6 @@ type Config struct {
|
||||||
// NOTE: Listener will be triggered by (nil, 0, 0) immediately
|
// NOTE: Listener will be triggered by (nil, 0, 0) immediately
|
||||||
Listener Listener
|
Listener Listener
|
||||||
|
|
||||||
Painter Painter
|
|
||||||
|
|
||||||
// If VimMode is true, readline will in vim.insert mode by default
|
// If VimMode is true, readline will in vim.insert mode by default
|
||||||
VimMode bool
|
VimMode bool
|
||||||
|
|
||||||
|
@ -56,8 +52,7 @@ type Config struct {
|
||||||
|
|
||||||
FuncGetWidth func() int
|
FuncGetWidth func() int
|
||||||
|
|
||||||
Stdin io.ReadCloser
|
Stdin io.Reader
|
||||||
StdinWriter io.Writer
|
|
||||||
Stdout io.Writer
|
Stdout io.Writer
|
||||||
Stderr io.Writer
|
Stderr io.Writer
|
||||||
|
|
||||||
|
@ -68,10 +63,6 @@ type Config struct {
|
||||||
// it use in IM usually.
|
// it use in IM usually.
|
||||||
UniqueEditLine bool
|
UniqueEditLine bool
|
||||||
|
|
||||||
// filter input runes (may be used to disable CtrlZ or for translating some keys to different actions)
|
|
||||||
// -> output = new (translated) rune and true/false if continue with processing this one
|
|
||||||
FuncFilterInputRune func(rune) (rune, bool)
|
|
||||||
|
|
||||||
// force use interactive even stdout is not a tty
|
// force use interactive even stdout is not a tty
|
||||||
FuncIsTerminal func() bool
|
FuncIsTerminal func() bool
|
||||||
FuncMakeRaw func() error
|
FuncMakeRaw func() error
|
||||||
|
@ -100,9 +91,6 @@ func (c *Config) Init() error {
|
||||||
if c.Stdin == nil {
|
if c.Stdin == nil {
|
||||||
c.Stdin = NewCancelableStdin(Stdin)
|
c.Stdin = NewCancelableStdin(Stdin)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Stdin, c.StdinWriter = NewFillableStdin(c.Stdin)
|
|
||||||
|
|
||||||
if c.Stdout == nil {
|
if c.Stdout == nil {
|
||||||
c.Stdout = Stdout
|
c.Stdout = Stdout
|
||||||
}
|
}
|
||||||
|
@ -157,19 +145,12 @@ func (c *Config) SetListener(f func(line []rune, pos int, key rune) (newLine []r
|
||||||
c.Listener = FuncListener(f)
|
c.Listener = FuncListener(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) SetPainter(p Painter) {
|
|
||||||
c.Painter = p
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewEx(cfg *Config) (*Instance, error) {
|
func NewEx(cfg *Config) (*Instance, error) {
|
||||||
t, err := NewTerminal(cfg)
|
t, err := NewTerminal(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
rl := t.Readline()
|
rl := t.Readline()
|
||||||
if cfg.Painter == nil {
|
|
||||||
cfg.Painter = &defaultPainter{}
|
|
||||||
}
|
|
||||||
return &Instance{
|
return &Instance{
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
Terminal: t,
|
Terminal: t,
|
||||||
|
@ -257,11 +238,6 @@ func (i *Instance) Readline() (string, error) {
|
||||||
return i.Operation.String()
|
return i.Operation.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Instance) ReadlineWithDefault(what string) (string, error) {
|
|
||||||
i.Operation.SetBuffer(what)
|
|
||||||
return i.Operation.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) SaveHistory(content string) error {
|
func (i *Instance) SaveHistory(content string) error {
|
||||||
return i.Operation.SaveHistory(content)
|
return i.Operation.SaveHistory(content)
|
||||||
}
|
}
|
||||||
|
@ -272,24 +248,13 @@ func (i *Instance) ReadSlice() ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// we must make sure that call Close() before process exit.
|
// we must make sure that call Close() before process exit.
|
||||||
// if there has a pending reading operation, that reading will be interrupted.
|
|
||||||
// so you can capture the signal and call Instance.Close(), it's thread-safe.
|
|
||||||
func (i *Instance) Close() error {
|
func (i *Instance) Close() error {
|
||||||
i.Config.Stdin.Close()
|
|
||||||
i.Operation.Close()
|
|
||||||
if err := i.Terminal.Close(); err != nil {
|
if err := i.Terminal.Close(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
i.Operation.Close()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// call CaptureExitSignal when you want readline exit gracefully.
|
|
||||||
func (i *Instance) CaptureExitSignal() {
|
|
||||||
CaptureExitSignal(func() {
|
|
||||||
i.Close()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) Clean() {
|
func (i *Instance) Clean() {
|
||||||
i.Operation.Clean()
|
i.Operation.Clean()
|
||||||
}
|
}
|
||||||
|
@ -298,20 +263,6 @@ func (i *Instance) Write(b []byte) (int, error) {
|
||||||
return i.Stdout().Write(b)
|
return i.Stdout().Write(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteStdin prefill the next Stdin fetch
|
|
||||||
// Next time you call ReadLine() this value will be writen before the user input
|
|
||||||
// ie :
|
|
||||||
// i := readline.New()
|
|
||||||
// i.WriteStdin([]byte("test"))
|
|
||||||
// _, _= i.Readline()
|
|
||||||
//
|
|
||||||
// gives
|
|
||||||
//
|
|
||||||
// > test[cursor]
|
|
||||||
func (i *Instance) WriteStdin(val []byte) (int, error) {
|
|
||||||
return i.Terminal.WriteStdin(val)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Instance) SetConfig(cfg *Config) *Config {
|
func (i *Instance) SetConfig(cfg *Config) *Config {
|
||||||
if i.Config == cfg {
|
if i.Config == cfg {
|
||||||
return cfg
|
return cfg
|
||||||
|
@ -326,13 +277,3 @@ func (i *Instance) SetConfig(cfg *Config) *Config {
|
||||||
func (i *Instance) Refresh() {
|
func (i *Instance) Refresh() {
|
||||||
i.Operation.Refresh()
|
i.Operation.Refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
// HistoryDisable the save of the commands into the history
|
|
||||||
func (i *Instance) HistoryDisable() {
|
|
||||||
i.Operation.history.Disable()
|
|
||||||
}
|
|
||||||
|
|
||||||
// HistoryEnable the save of the commands into the history (default on)
|
|
||||||
func (i *Instance) HistoryEnable() {
|
|
||||||
i.Operation.history.Enable()
|
|
||||||
}
|
|
||||||
|
|
|
@ -189,12 +189,11 @@ loop:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RemoteSvr) Close() error {
|
func (r *RemoteSvr) Close() {
|
||||||
if atomic.CompareAndSwapInt32(&r.closed, 0, 1) {
|
if atomic.CompareAndSwapInt32(&r.closed, 0, 1) {
|
||||||
close(r.stopChan)
|
close(r.stopChan)
|
||||||
r.conn.Close()
|
r.conn.Close()
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RemoteSvr) readLoop(buf *bufio.Reader) {
|
func (r *RemoteSvr) readLoop(buf *bufio.Reader) {
|
||||||
|
|
69
runebuf.go
69
runebuf.go
|
@ -4,7 +4,6 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"io"
|
"io"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
@ -30,15 +29,9 @@ type RuneBuffer struct {
|
||||||
|
|
||||||
offset string
|
offset string
|
||||||
|
|
||||||
lastKill []rune
|
|
||||||
|
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuneBuffer) pushKill(text []rune) {
|
|
||||||
r.lastKill = append([]rune{}, text...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RuneBuffer) OnWidthChange(newWidth int) {
|
func (r *RuneBuffer) OnWidthChange(newWidth int) {
|
||||||
r.Lock()
|
r.Lock()
|
||||||
r.width = newWidth
|
r.width = newWidth
|
||||||
|
@ -194,7 +187,6 @@ func (r *RuneBuffer) Replace(ch rune) {
|
||||||
func (r *RuneBuffer) Erase() {
|
func (r *RuneBuffer) Erase() {
|
||||||
r.Refresh(func() {
|
r.Refresh(func() {
|
||||||
r.idx = 0
|
r.idx = 0
|
||||||
r.pushKill(r.buf[:])
|
|
||||||
r.buf = r.buf[:0]
|
r.buf = r.buf[:0]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -204,7 +196,6 @@ func (r *RuneBuffer) Delete() (success bool) {
|
||||||
if r.idx == len(r.buf) {
|
if r.idx == len(r.buf) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
r.pushKill(r.buf[r.idx : r.idx+1])
|
|
||||||
r.buf = append(r.buf[:r.idx], r.buf[r.idx+1:]...)
|
r.buf = append(r.buf[:r.idx], r.buf[r.idx+1:]...)
|
||||||
success = true
|
success = true
|
||||||
})
|
})
|
||||||
|
@ -221,7 +212,6 @@ func (r *RuneBuffer) DeleteWord() {
|
||||||
}
|
}
|
||||||
for i := init + 1; i < len(r.buf); i++ {
|
for i := init + 1; i < len(r.buf); i++ {
|
||||||
if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) {
|
if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) {
|
||||||
r.pushKill(r.buf[r.idx : i-1])
|
|
||||||
r.Refresh(func() {
|
r.Refresh(func() {
|
||||||
r.buf = append(r.buf[:r.idx], r.buf[i-1:]...)
|
r.buf = append(r.buf[:r.idx], r.buf[i-1:]...)
|
||||||
})
|
})
|
||||||
|
@ -257,7 +247,6 @@ func (r *RuneBuffer) KillFront() {
|
||||||
}
|
}
|
||||||
|
|
||||||
length := len(r.buf) - r.idx
|
length := len(r.buf) - r.idx
|
||||||
r.pushKill(r.buf[:r.idx])
|
|
||||||
copy(r.buf[:length], r.buf[r.idx:])
|
copy(r.buf[:length], r.buf[r.idx:])
|
||||||
r.idx = 0
|
r.idx = 0
|
||||||
r.buf = r.buf[:length]
|
r.buf = r.buf[:length]
|
||||||
|
@ -266,7 +255,6 @@ func (r *RuneBuffer) KillFront() {
|
||||||
|
|
||||||
func (r *RuneBuffer) Kill() {
|
func (r *RuneBuffer) Kill() {
|
||||||
r.Refresh(func() {
|
r.Refresh(func() {
|
||||||
r.pushKill(r.buf[r.idx:])
|
|
||||||
r.buf = r.buf[:r.idx]
|
r.buf = r.buf[:r.idx]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -333,7 +321,6 @@ func (r *RuneBuffer) BackEscapeWord() {
|
||||||
}
|
}
|
||||||
for i := r.idx - 1; i > 0; i-- {
|
for i := r.idx - 1; i > 0; i-- {
|
||||||
if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) {
|
if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) {
|
||||||
r.pushKill(r.buf[i:r.idx])
|
|
||||||
r.buf = append(r.buf[:i], r.buf[r.idx:]...)
|
r.buf = append(r.buf[:i], r.buf[r.idx:]...)
|
||||||
r.idx = i
|
r.idx = i
|
||||||
return
|
return
|
||||||
|
@ -345,20 +332,6 @@ func (r *RuneBuffer) BackEscapeWord() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuneBuffer) Yank() {
|
|
||||||
if len(r.lastKill) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r.Refresh(func() {
|
|
||||||
buf := make([]rune, 0, len(r.buf)+len(r.lastKill))
|
|
||||||
buf = append(buf, r.buf[:r.idx]...)
|
|
||||||
buf = append(buf, r.lastKill...)
|
|
||||||
buf = append(buf, r.buf[r.idx:]...)
|
|
||||||
r.buf = buf
|
|
||||||
r.idx += len(r.lastKill)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RuneBuffer) Backspace() {
|
func (r *RuneBuffer) Backspace() {
|
||||||
r.Refresh(func() {
|
r.Refresh(func() {
|
||||||
if r.idx == 0 {
|
if r.idx == 0 {
|
||||||
|
@ -487,58 +460,28 @@ func (r *RuneBuffer) output() []byte {
|
||||||
buf.Write([]byte(string(r.cfg.MaskRune)))
|
buf.Write([]byte(string(r.cfg.MaskRune)))
|
||||||
}
|
}
|
||||||
if len(r.buf) > r.idx {
|
if len(r.buf) > r.idx {
|
||||||
buf.Write(r.getBackspaceSequence())
|
buf.Write(runes.Backspace(r.buf[r.idx:]))
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
for _, e := range r.cfg.Painter.Paint(r.buf, r.idx) {
|
for idx := range r.buf {
|
||||||
if e == '\t' {
|
if r.buf[idx] == '\t' {
|
||||||
buf.WriteString(strings.Repeat(" ", TabWidth))
|
buf.WriteString(strings.Repeat(" ", TabWidth))
|
||||||
} else {
|
} else {
|
||||||
buf.WriteRune(e)
|
buf.WriteRune(r.buf[idx])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if r.isInLineEdge() {
|
if r.isInLineEdge() {
|
||||||
buf.Write([]byte(" \b"))
|
buf.Write([]byte(" \b"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// cursor position
|
|
||||||
if len(r.buf) > r.idx {
|
if len(r.buf) > r.idx {
|
||||||
buf.Write(r.getBackspaceSequence())
|
buf.Write(runes.Backspace(r.buf[r.idx:]))
|
||||||
}
|
}
|
||||||
return buf.Bytes()
|
return buf.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuneBuffer) getBackspaceSequence() []byte {
|
|
||||||
var sep = map[int]bool{}
|
|
||||||
|
|
||||||
var i int
|
|
||||||
for {
|
|
||||||
if i >= runes.WidthAll(r.buf) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if i == 0 {
|
|
||||||
i -= r.promptLen()
|
|
||||||
}
|
|
||||||
i += r.width
|
|
||||||
|
|
||||||
sep[i] = true
|
|
||||||
}
|
|
||||||
var buf []byte
|
|
||||||
for i := len(r.buf); i > r.idx; i-- {
|
|
||||||
// move input to the left of one
|
|
||||||
buf = append(buf, '\b')
|
|
||||||
if sep[i] {
|
|
||||||
// up one line, go to the start of the line and move cursor right to the end (r.width)
|
|
||||||
buf = append(buf, "\033[A\r"+"\033["+strconv.Itoa(r.width)+"C"...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RuneBuffer) Reset() []rune {
|
func (r *RuneBuffer) Reset() []rune {
|
||||||
ret := runes.Copy(r.buf)
|
ret := runes.Copy(r.buf)
|
||||||
r.buf = r.buf[:0]
|
r.buf = r.buf[:0]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// deprecated.
|
// deprecated.
|
||||||
// see https://git.internal/re/readline/issues/43
|
// see https://github.com/chzyer/readline/issues/43
|
||||||
// use git.internal/re/readline/runes.go
|
// use github.com/chzyer/readline/runes.go
|
||||||
package runes
|
package runes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
83
std.go
83
std.go
|
@ -4,7 +4,6 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -70,7 +69,6 @@ type CancelableStdin struct {
|
||||||
r io.Reader
|
r io.Reader
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
stop chan struct{}
|
stop chan struct{}
|
||||||
closed int32
|
|
||||||
notify chan struct{}
|
notify chan struct{}
|
||||||
data []byte
|
data []byte
|
||||||
read int
|
read int
|
||||||
|
@ -93,11 +91,7 @@ loop:
|
||||||
select {
|
select {
|
||||||
case <-c.notify:
|
case <-c.notify:
|
||||||
c.read, c.err = c.r.Read(c.data)
|
c.read, c.err = c.r.Read(c.data)
|
||||||
select {
|
c.notify <- struct{}{}
|
||||||
case c.notify <- struct{}{}:
|
|
||||||
case <-c.stop:
|
|
||||||
break loop
|
|
||||||
}
|
|
||||||
case <-c.stop:
|
case <-c.stop:
|
||||||
break loop
|
break loop
|
||||||
}
|
}
|
||||||
|
@ -107,16 +101,9 @@ loop:
|
||||||
func (c *CancelableStdin) Read(b []byte) (n int, err error) {
|
func (c *CancelableStdin) Read(b []byte) (n int, err error) {
|
||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
||||||
defer c.mutex.Unlock()
|
defer c.mutex.Unlock()
|
||||||
if atomic.LoadInt32(&c.closed) == 1 {
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
c.data = b
|
c.data = b
|
||||||
select {
|
c.notify <- struct{}{}
|
||||||
case c.notify <- struct{}{}:
|
|
||||||
case <-c.stop:
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
select {
|
select {
|
||||||
case <-c.notify:
|
case <-c.notify:
|
||||||
return c.read, c.err
|
return c.read, c.err
|
||||||
|
@ -126,72 +113,6 @@ func (c *CancelableStdin) Read(b []byte) (n int, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CancelableStdin) Close() error {
|
func (c *CancelableStdin) Close() error {
|
||||||
if atomic.CompareAndSwapInt32(&c.closed, 0, 1) {
|
|
||||||
close(c.stop)
|
close(c.stop)
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FillableStdin is a stdin reader which can prepend some data before
|
|
||||||
// reading into the real stdin
|
|
||||||
type FillableStdin struct {
|
|
||||||
sync.Mutex
|
|
||||||
stdin io.Reader
|
|
||||||
stdinBuffer io.ReadCloser
|
|
||||||
buf []byte
|
|
||||||
bufErr error
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewFillableStdin gives you FillableStdin
|
|
||||||
func NewFillableStdin(stdin io.Reader) (io.ReadCloser, io.Writer) {
|
|
||||||
r, w := io.Pipe()
|
|
||||||
s := &FillableStdin{
|
|
||||||
stdinBuffer: r,
|
|
||||||
stdin: stdin,
|
|
||||||
}
|
|
||||||
s.ioloop()
|
|
||||||
return s, w
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FillableStdin) ioloop() {
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
bufR := make([]byte, 100)
|
|
||||||
var n int
|
|
||||||
n, s.bufErr = s.stdinBuffer.Read(bufR)
|
|
||||||
if s.bufErr != nil {
|
|
||||||
if s.bufErr == io.ErrClosedPipe {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.Lock()
|
|
||||||
s.buf = append(s.buf, bufR[:n]...)
|
|
||||||
s.Unlock()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read will read from the local buffer and if no data, read from stdin
|
|
||||||
func (s *FillableStdin) Read(p []byte) (n int, err error) {
|
|
||||||
s.Lock()
|
|
||||||
i := len(s.buf)
|
|
||||||
if len(p) < i {
|
|
||||||
i = len(p)
|
|
||||||
}
|
|
||||||
if i > 0 {
|
|
||||||
n := copy(p, s.buf)
|
|
||||||
s.buf = s.buf[:0]
|
|
||||||
cerr := s.bufErr
|
|
||||||
s.bufErr = nil
|
|
||||||
s.Unlock()
|
|
||||||
return n, cerr
|
|
||||||
}
|
|
||||||
s.Unlock()
|
|
||||||
n, err = s.stdin.Read(p)
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *FillableStdin) Close() error {
|
|
||||||
s.stdinBuffer.Close()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
49
term.go
49
term.go
|
@ -2,7 +2,7 @@
|
||||||
// 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 os400 solaris
|
// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd
|
||||||
|
|
||||||
// Package terminal provides support functions for dealing with terminals, as
|
// Package terminal provides support functions for dealing with terminals, as
|
||||||
// commonly found on UNIX systems.
|
// commonly found on UNIX systems.
|
||||||
|
@ -19,17 +19,19 @@ package readline
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
// State contains the state of a terminal.
|
// State contains the state of a terminal.
|
||||||
type State struct {
|
type State struct {
|
||||||
termios Termios
|
termios syscall.Termios
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||||
func IsTerminal(fd int) bool {
|
func IsTerminal(fd int) bool {
|
||||||
_, err := getTermios(fd)
|
var termios syscall.Termios
|
||||||
return err == nil
|
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
|
||||||
|
return err == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeRaw put the terminal connected to the given file descriptor into raw
|
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||||
|
@ -37,11 +39,8 @@ func IsTerminal(fd int) bool {
|
||||||
// restored.
|
// restored.
|
||||||
func MakeRaw(fd int) (*State, error) {
|
func MakeRaw(fd int) (*State, error) {
|
||||||
var oldState State
|
var oldState State
|
||||||
|
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 {
|
||||||
if termios, err := getTermios(fd); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
|
||||||
oldState.termios = *termios
|
|
||||||
}
|
}
|
||||||
|
|
||||||
newState := oldState.termios
|
newState := oldState.termios
|
||||||
|
@ -53,35 +52,47 @@ func MakeRaw(fd int) (*State, error) {
|
||||||
newState.Cflag &^= syscall.CSIZE | syscall.PARENB
|
newState.Cflag &^= syscall.CSIZE | syscall.PARENB
|
||||||
newState.Cflag |= syscall.CS8
|
newState.Cflag |= syscall.CS8
|
||||||
|
|
||||||
newState.Cc[syscall.VMIN] = 1
|
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 {
|
||||||
newState.Cc[syscall.VTIME] = 0
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &oldState, setTermios(fd, &newState)
|
return &oldState, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetState returns the current state of a terminal which may be useful to
|
// GetState returns the current state of a terminal which may be useful to
|
||||||
// restore the terminal after a signal.
|
// restore the terminal after a signal.
|
||||||
func GetState(fd int) (*State, error) {
|
func GetState(fd int) (*State, error) {
|
||||||
termios, err := getTermios(fd)
|
var oldState State
|
||||||
if err != nil {
|
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &State{termios: *termios}, nil
|
return &oldState, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore restores the terminal connected to the given file descriptor to a
|
// Restore restores the terminal connected to the given file descriptor to a
|
||||||
// previous state.
|
// previous state.
|
||||||
func restoreTerm(fd int, state *State) error {
|
func restoreTerm(fd int, state *State) error {
|
||||||
return setTermios(fd, &state.termios)
|
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&state.termios)), 0, 0, 0)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSize returns the dimensions of the given terminal.
|
||||||
|
func GetSize(fd int) (width, height int, err error) {
|
||||||
|
var dimensions [4]uint16
|
||||||
|
|
||||||
|
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&dimensions)), 0, 0, 0); err != 0 {
|
||||||
|
return -1, -1, err
|
||||||
|
}
|
||||||
|
return int(dimensions[1]), int(dimensions[0]), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadPassword reads a line of input from a terminal without local echo. This
|
// 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
|
// is commonly used for inputting passwords and other sensitive data. The slice
|
||||||
// returned does not include the \n.
|
// returned does not include the \n.
|
||||||
func ReadPassword(fd int) ([]byte, error) {
|
func ReadPassword(fd int) ([]byte, error) {
|
||||||
oldState, err := getTermios(fd)
|
var oldState syscall.Termios
|
||||||
if err != nil {
|
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0); err != 0 {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,12 +100,12 @@ func ReadPassword(fd int) ([]byte, error) {
|
||||||
newState.Lflag &^= syscall.ECHO
|
newState.Lflag &^= syscall.ECHO
|
||||||
newState.Lflag |= syscall.ICANON | syscall.ISIG
|
newState.Lflag |= syscall.ICANON | syscall.ISIG
|
||||||
newState.Iflag |= syscall.ICRNL
|
newState.Iflag |= syscall.ICRNL
|
||||||
if err := setTermios(fd, newState); err != nil {
|
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
setTermios(fd, oldState)
|
syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var buf [16]byte
|
var buf [16]byte
|
||||||
|
|
23
term_bsd.go
23
term_bsd.go
|
@ -6,24 +6,7 @@
|
||||||
|
|
||||||
package readline
|
package readline
|
||||||
|
|
||||||
import (
|
import "syscall"
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getTermios(fd int) (*Termios, error) {
|
const ioctlReadTermios = syscall.TIOCGETA
|
||||||
termios := new(Termios)
|
const ioctlWriteTermios = syscall.TIOCSETA
|
||||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), syscall.TIOCGETA, uintptr(unsafe.Pointer(termios)), 0, 0, 0)
|
|
||||||
if err != 0 {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return termios, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setTermios(fd int, termios *Termios) error {
|
|
||||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), syscall.TIOCSETA, uintptr(unsafe.Pointer(termios)), 0, 0, 0)
|
|
||||||
if err != 0 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,30 +4,8 @@
|
||||||
|
|
||||||
package readline
|
package readline
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// These constants are declared here, rather than importing
|
// These constants are declared here, rather than importing
|
||||||
// them from the syscall package as some syscall packages, even
|
// them from the syscall package as some syscall packages, even
|
||||||
// on linux, for example gccgo, do not declare them.
|
// on linux, for example gccgo, do not declare them.
|
||||||
const ioctlReadTermios = 0x5401 // syscall.TCGETS
|
const ioctlReadTermios = 0x5401 // syscall.TCGETS
|
||||||
const ioctlWriteTermios = 0x5402 // syscall.TCSETS
|
const ioctlWriteTermios = 0x5402 // syscall.TCSETS
|
||||||
|
|
||||||
func getTermios(fd int) (*Termios, error) {
|
|
||||||
termios := new(Termios)
|
|
||||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(termios)), 0, 0, 0)
|
|
||||||
if err != 0 {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return termios, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setTermios(fd int, termios *Termios) error {
|
|
||||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(termios)), 0, 0, 0)
|
|
||||||
if err != 0 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,32 +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.
|
|
||||||
|
|
||||||
// +build aix os400 solaris
|
|
||||||
|
|
||||||
package readline
|
|
||||||
|
|
||||||
import "golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
// GetSize returns the dimensions of the given terminal.
|
|
||||||
func GetSize(fd int) (int, int, error) {
|
|
||||||
ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
|
|
||||||
if err != nil {
|
|
||||||
return 0, 0, err
|
|
||||||
}
|
|
||||||
return int(ws.Col), int(ws.Row), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Termios unix.Termios
|
|
||||||
|
|
||||||
func getTermios(fd int) (*Termios, error) {
|
|
||||||
termios, err := unix.IoctlGetTermios(fd, unix.TCGETS)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return (*Termios)(termios), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setTermios(fd int, termios *Termios) error {
|
|
||||||
return unix.IoctlSetTermios(fd, unix.TCSETSF, (*unix.Termios)(termios))
|
|
||||||
}
|
|
24
term_unix.go
24
term_unix.go
|
@ -1,24 +0,0 @@
|
||||||
// Copyright 2011 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 darwin dragonfly freebsd linux,!appengine netbsd openbsd
|
|
||||||
|
|
||||||
package readline
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Termios syscall.Termios
|
|
||||||
|
|
||||||
// GetSize returns the dimensions of the given terminal.
|
|
||||||
func GetSize(fd int) (int, int, error) {
|
|
||||||
var dimensions [4]uint16
|
|
||||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&dimensions)), 0, 0, 0)
|
|
||||||
if err != 0 {
|
|
||||||
return 0, 0, err
|
|
||||||
}
|
|
||||||
return int(dimensions[1]), int(dimensions[0]), nil
|
|
||||||
}
|
|
41
terminal.go
41
terminal.go
|
@ -10,7 +10,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Terminal struct {
|
type Terminal struct {
|
||||||
m sync.Mutex
|
|
||||||
cfg *Config
|
cfg *Config
|
||||||
outchan chan rune
|
outchan chan rune
|
||||||
closed int32
|
closed int32
|
||||||
|
@ -65,12 +64,6 @@ func (t *Terminal) Write(b []byte) (int, error) {
|
||||||
return t.cfg.Stdout.Write(b)
|
return t.cfg.Stdout.Write(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteStdin prefill the next Stdin fetch
|
|
||||||
// Next time you call ReadLine() this value will be writen before the user input
|
|
||||||
func (t *Terminal) WriteStdin(b []byte) (int, error) {
|
|
||||||
return t.cfg.StdinWriter.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
type termSize struct {
|
type termSize struct {
|
||||||
left int
|
left int
|
||||||
top int
|
top int
|
||||||
|
@ -125,11 +118,10 @@ func (t *Terminal) ioloop() {
|
||||||
var (
|
var (
|
||||||
isEscape bool
|
isEscape bool
|
||||||
isEscapeEx bool
|
isEscapeEx bool
|
||||||
isEscapeSS3 bool
|
|
||||||
expectNextChar bool
|
expectNextChar bool
|
||||||
)
|
)
|
||||||
|
|
||||||
buf := bufio.NewReader(t.getStdin())
|
buf := bufio.NewReader(t.cfg.Stdin)
|
||||||
for {
|
for {
|
||||||
if !expectNextChar {
|
if !expectNextChar {
|
||||||
atomic.StoreInt32(&t.isReading, 0)
|
atomic.StoreInt32(&t.isReading, 0)
|
||||||
|
@ -153,15 +145,9 @@ func (t *Terminal) ioloop() {
|
||||||
if isEscape {
|
if isEscape {
|
||||||
isEscape = false
|
isEscape = false
|
||||||
if r == CharEscapeEx {
|
if r == CharEscapeEx {
|
||||||
// ^][
|
|
||||||
expectNextChar = true
|
expectNextChar = true
|
||||||
isEscapeEx = true
|
isEscapeEx = true
|
||||||
continue
|
continue
|
||||||
} else if r == CharO {
|
|
||||||
// ^]O
|
|
||||||
expectNextChar = true
|
|
||||||
isEscapeSS3 = true
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
r = escapeKey(r, buf)
|
r = escapeKey(r, buf)
|
||||||
} else if isEscapeEx {
|
} else if isEscapeEx {
|
||||||
|
@ -184,15 +170,6 @@ func (t *Terminal) ioloop() {
|
||||||
expectNextChar = true
|
expectNextChar = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} else if isEscapeSS3 {
|
|
||||||
isEscapeSS3 = false
|
|
||||||
if key := readEscKey(r, buf); key != nil {
|
|
||||||
r = escapeSS3Key(key)
|
|
||||||
}
|
|
||||||
if r == 0 {
|
|
||||||
expectNextChar = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
expectNextChar = true
|
expectNextChar = true
|
||||||
|
@ -229,26 +206,10 @@ func (t *Terminal) Close() error {
|
||||||
return t.ExitRawMode()
|
return t.ExitRawMode()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) GetConfig() *Config {
|
|
||||||
t.m.Lock()
|
|
||||||
cfg := *t.cfg
|
|
||||||
t.m.Unlock()
|
|
||||||
return &cfg
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Terminal) getStdin() io.Reader {
|
|
||||||
t.m.Lock()
|
|
||||||
r := t.cfg.Stdin
|
|
||||||
t.m.Unlock()
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Terminal) SetConfig(c *Config) error {
|
func (t *Terminal) SetConfig(c *Config) error {
|
||||||
if err := c.Init(); err != nil {
|
if err := c.Init(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
t.m.Lock()
|
|
||||||
t.cfg = c
|
t.cfg = c
|
||||||
t.m.Unlock()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
39
utils.go
39
utils.go
|
@ -6,11 +6,9 @@ import (
|
||||||
"container/list"
|
"container/list"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
|
||||||
"time"
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
)
|
)
|
||||||
|
@ -40,10 +38,8 @@ const (
|
||||||
CharTranspose = 20
|
CharTranspose = 20
|
||||||
CharCtrlU = 21
|
CharCtrlU = 21
|
||||||
CharCtrlW = 23
|
CharCtrlW = 23
|
||||||
CharCtrlY = 25
|
|
||||||
CharCtrlZ = 26
|
CharCtrlZ = 26
|
||||||
CharEsc = 27
|
CharEsc = 27
|
||||||
CharO = 79
|
|
||||||
CharEscapeEx = 91
|
CharEscapeEx = 91
|
||||||
CharBackspace = 127
|
CharBackspace = 127
|
||||||
)
|
)
|
||||||
|
@ -86,9 +82,7 @@ func Restore(fd int, state *State) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// errno 0 means everything is ok :)
|
// errno 0 means everything is ok :)
|
||||||
if err.Error() == "errno 0" {
|
if err.Error() == "errno 0" {
|
||||||
return nil
|
err = nil
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -124,27 +118,6 @@ func escapeExKey(key *escapeKeyPair) rune {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// translate EscOX SS3 codes for up/down/etc.
|
|
||||||
func escapeSS3Key(key *escapeKeyPair) rune {
|
|
||||||
var r rune
|
|
||||||
switch key.typ {
|
|
||||||
case 'D':
|
|
||||||
r = CharBackward
|
|
||||||
case 'C':
|
|
||||||
r = CharForward
|
|
||||||
case 'A':
|
|
||||||
r = CharPrev
|
|
||||||
case 'B':
|
|
||||||
r = CharNext
|
|
||||||
case 'H':
|
|
||||||
r = CharLineStart
|
|
||||||
case 'F':
|
|
||||||
r = CharLineEnd
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
type escapeKeyPair struct {
|
type escapeKeyPair struct {
|
||||||
attr string
|
attr string
|
||||||
typ rune
|
typ rune
|
||||||
|
@ -299,13 +272,3 @@ func Debug(o ...interface{}) {
|
||||||
fmt.Fprintln(f, o...)
|
fmt.Fprintln(f, o...)
|
||||||
f.Close()
|
f.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func CaptureExitSignal(f func()) {
|
|
||||||
cSignal := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(cSignal, os.Interrupt, syscall.SIGTERM)
|
|
||||||
go func() {
|
|
||||||
for range cSignal {
|
|
||||||
f()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// +build aix darwin dragonfly freebsd linux,!appengine netbsd openbsd os400 solaris
|
// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd
|
||||||
|
|
||||||
package readline
|
package readline
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import (
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
type winsize struct {
|
type winsize struct {
|
||||||
|
@ -29,11 +30,17 @@ func SuspendMe() {
|
||||||
|
|
||||||
// get width of the terminal
|
// get width of the terminal
|
||||||
func getWidth(stdoutFd int) int {
|
func getWidth(stdoutFd int) int {
|
||||||
cols, _, err := GetSize(stdoutFd)
|
ws := &winsize{}
|
||||||
if err != nil {
|
retCode, _, errno := syscall.Syscall(syscall.SYS_IOCTL,
|
||||||
|
uintptr(stdoutFd),
|
||||||
|
uintptr(syscall.TIOCGWINSZ),
|
||||||
|
uintptr(unsafe.Pointer(ws)))
|
||||||
|
|
||||||
|
if int(retCode) == -1 {
|
||||||
|
_ = errno
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
return cols
|
return int(ws.Col)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetScreenWidth() int {
|
func GetScreenWidth() int {
|
||||||
|
|
Loading…
Reference in New Issue