Compare commits

..

3 Commits

Author SHA1 Message Date
chzyer 5197e2b2ba [cancelable stdin] fix c.notify 2016-10-06 12:33:21 +08:00
chzyer 17b536725b fix notify back 2016-10-06 12:31:47 +08:00
chzyer fa8d922787 fix deadlock in cancelable stdin 2016-10-06 12:30:22 +08:00
31 changed files with 142 additions and 566 deletions

1
.gitignore vendored
View File

@ -1 +0,0 @@
.vscode/*

View File

@ -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

View File

@ -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

View File

@ -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
@ -19,25 +19,24 @@ A powerful readline library in `Linux` `macOS` `Windows` `Solaris` `AIX`
* [Shortcut](doc/shortcut.md) * [Shortcut](doc/shortcut.md)
## 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
If you have any questions, please submit a github issue and any pull requests is welcomed :) If you have any questions, please submit a github issue and any pull requests is welcomed :)
* [https://twitter.com/chzyer](https://twitter.com/chzyer) * [https://twitter.com/chzyer](https://twitter.com/chzyer)
* [http://weibo.com/2145262190](http://weibo.com/2145262190) * [http://weibo.com/2145262190](http://weibo.com/2145262190)
## Backers ## Backers

View File

@ -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]
} }

View File

@ -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)-len(same)))
if inSelect { if inSelect {
buf.WriteString("\033[0m") buf.WriteString("\033[0m")

View File

@ -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 ",
@ -78,14 +69,12 @@ func main() {
InterruptPrompt: "^C", InterruptPrompt: "^C",
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 {

View File

@ -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{

View File

@ -3,7 +3,7 @@ package main
import ( import (
"strings" "strings"
"git.internal/re/readline" "github.com/chzyer/readline"
) )
func main() { func main() {

View File

@ -4,25 +4,26 @@
// //
// This file is licensed under the WTFPL: // This file is licensed under the WTFPL:
// //
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE // DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004 // Version 2, December 2004
// //
// Copyright (C) 2004 Sam Hocevar <sam@hocevar.net> // Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
// //
// Everyone is permitted to copy and distribute verbatim or modified // Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long // copies of this license document, and changing it is allowed as long
// as the name is changed. // as the name is changed.
// //
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE // DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION // TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
// //
// 0. You just DO WHAT THE FUCK YOU WANT TO. // 0. You just DO WHAT THE FUCK YOU WANT TO.
package main 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)
prompt += Colorize(" ENT", Cyan) if strength.Entropy > 0 {
entropy := fmt.Sprintf(" %3.0f", strength.Entropy)
prompt += Colorize(entropy, Cyan)
} else {
prompt += Colorize(" ENT", Cyan)
}
prompt += Colorize(" New Password: ", color) prompt += Colorize(" New Password: ", color)
return prompt return prompt

View File

@ -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 {

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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

View File

@ -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
}

View File

@ -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,

View File

@ -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,10 +52,9 @@ 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
EnableMask bool EnableMask bool
MaskRune rune MaskRune rune
@ -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()
}

View File

@ -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) {

View File

@ -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]

View File

@ -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 (

64
std.go
View File

@ -131,67 +131,3 @@ func (c *CancelableStdin) Close() error {
} }
return nil 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
}

49
term.go
View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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))
}

View File

@ -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
}

View File

@ -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
} }

View File

@ -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()
}
}()
}

View File

@ -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 {

2
vim.go
View File

@ -72,8 +72,6 @@ func (o *opVim) handleVimNormalMovement(r rune, readNext func() rune) (t rune, h
case 'l': case 'l':
rb.Delete() rb.Delete()
} }
case 'p':
rb.Yank()
case 'b', 'B': case 'b', 'B':
rb.MoveToPrevWord() rb.MoveToPrevWord()
case 'w', 'W': case 'w', 'W':