Compare commits

..

31 Commits

Author SHA1 Message Date
re 295ca315c5 fix repos 2022-12-12 16:55:00 +03:00
ChenYe 7f93d88cd5 fixing deadloop when input is filtered 2022-07-15 20:48:48 +08:00
Thomas O'Dowd 8e4bd417b9
Handle keypad mode cursor key escape sequences. (#203)
Normally the terminal uses CSI escape sequences when the UP, DOWN,
LEFT, RIGHT and HOME, END keys are pressed. These look like the
following ESC [ A etc, where ESC [ is the CSI sequence.

xterm and other terminals however can generate an alternative
escape sequence called SS3 if in the application keypad mode.
This sequence is ESC O A etc.

Bash readline understands both modes so nowadays you rarely
see OA being printed when you press the up arrow while the terminal
is using the keypad mode. readline currently does not understand
these sequences.

To test this fix, I used an xterm and put it in keypad mode
using the command "tput smkx". Then I started the readline-demo
and tried using arrow keys. Without this fix, OA is printed when
I press up. With this fix, readline fetches the previous command
as per regular mode. After testing you can escape back to
regular mode using "tput rmkx".
2022-05-20 21:29:21 +08:00
ChenYe 80e2d1961b
restore term when receive signal (#200)
* restore term when receive signal

add `CaptureExitSignal` to capture exit signals and exit gracefull(disabled by default)

* update deps
2022-04-24 21:25:55 +08:00
Derick Rethans c34436b973
Added 'os400' (#201)
As per https://github.com/chzyer/readline/pull/175#issuecomment-1100887727
2022-04-18 20:41:19 +08:00
Clément Chigot a5e9f81cc2
AIX support (#175)
This commit adds support for AIX operating system.

 - move term_solaris.go to term_nosyscall6.go. AIX like solaris doesn't
provide syscall.Syscall6 and must rely on x/sys/unix in order to perform
syscalls.

 - This patch won't work with versions prior to 1.13 because it needs
some constants added by https://go-review.googlesource.com/c/go/+/171339.
2022-04-17 16:50:25 +08:00
ChenYe a11d8f26cf add go.mod and remove unnecessary dep 2022-04-13 22:35:28 +08:00
Jim-wiselike 2972be24d4 Modify the display width for Chinese characters and so on (#145)
The display width will not be right if there have Chinese characters in the display. It will overflow the line, if the Chinese characters are at the end of a line. And makes "lines" value not the right number of line rows,  which makes "239: fmt.Fprintf" produce wrong output.
2018-06-03 21:26:55 +08:00
soopsio f6d7a1f6fb Fix ioloop groutine leaks bug. (#136)
* Fix ioloop groutine leaks bug.

* fix func (o *Operation) ioloop()  L:125 hangs
2017-12-08 09:17:16 +08:00
François-Xavier Aguessy 40d6036c33 Fix panic 'integer divide by zero' when terminal width too low (#137) 2017-12-08 09:16:05 +08:00
Abiola Ibrahim a4d5111b61 Fix panic for ReadPassword (#133) 2017-11-03 21:19:23 +08:00
Eugene Apollonsky 6a4bc7b4fe operation: fix SetConfig races (#131) 2017-10-03 22:59:50 +08:00
mrsinham 9cc74fe5ad Prefill user input (#101)
* Using a fillable stdin reader

* Adding some documentation to the new features
2017-10-02 20:43:15 +08:00
mrsinham af545c8af6 enabling disabling history (#102) 2017-10-02 20:42:28 +08:00
mrsinham aee0fa669f fix: on linux multine move was not working (#103)
on linux, if you want to change line, \b are not enough. You need to change line
and move to the right.
2017-10-02 20:39:53 +08:00
Saeed Rasooli aa9ed7db49 NewEx: fix panic if cfg.Painter is not given (#129) 2017-10-02 14:46:29 +08:00
Jelmer Snoeck c6c3e8d906 Add support for bold text. (#106)
Currently, bold text isn't supported. This adds the support for bold
text formatting.
2017-10-01 22:59:06 -05:00
Thomas Bradford 707fd8ecaa intercept line rendering using a 'Painter' (#116) 2017-10-01 22:58:28 -05:00
Sagar Mittal 9f56defe66 Add barebones support for yank/paste (#120) 2017-10-01 22:57:50 -05:00
Edward Betts 045fff973b correct spelling mistake (#127) 2017-10-01 22:57:10 -05:00
Davor Kapsa 8d510b4136 travis: update go version (#128) 2017-10-01 22:55:11 -05:00
Jordan Lewis 01c4e90c35 Bugfix in rewriteHistory (#121)
Previously, rewriteHistory was incorrect - it did not preserve the
newlines between history lines, which corrupts the history file.
2017-10-01 22:53:35 -05:00
Paul Tagliamonte 41eea22f71 Add two API methods to set RuneBuffer state before prompting the user (#105)
* Expose RuneBuffer.Set through Operation's API

This allows a user to prefill a line before the user is given a prompt,
so that you can provide a default, user-editable input line.

* Add a Instance.ReadlineWithDefault API method

This will pre-fill the Operation.RuneBuffer with text before prompting
the user, and provide her with a method to edit the provided default.
2017-03-14 07:49:21 +08:00
Gereon Frey 8a1389155f Solaris support (#110)
* Add support for solaris

* Change state to handle system dependent termios type.
* Move syscalls to get and set termios to functions in files only built
  for respective platforms.
* Create `term_unix.go` go file built on all supported unices except
  solaris with types and functions valid for all of them.
* Change `MakeRaw` to set VMIN and VTIME to default values.

Fixes #95.

* Fix error handling

Doing the string comparison could be improved, but at least we should
return an error, if it is not "errno 0".
2017-03-13 07:57:45 +08:00
Kenneth Shaw 784bd70fe0 Adding knq/usql to README.md (#107)
usql is a new project that is making extensive use of readline. usql is
roughly 3 days old, and already has over 1100 stars. This change adds
usql to the README.md and reorders projects listed in the "Repos using
readline" section.
2017-03-05 19:31:14 +08:00
Jiří Setnička f892600c35 Ability to filter out input runes (#104)
Could be used to mask/disable some actions (like Ctrl-Z, or searching by
Ctrl-R and Ctrl-S)
2017-02-20 09:51:01 +08:00
chzyer eef24db1d5 fix terminal data race (#99) 2017-01-25 11:55:32 +08:00
Remi Reuvekamp c914be64f0 Fix panic: runtime error: slice bounds out of range (#94)
Reslicing line[10:] went wrong if only 'setprompt' was entered, without a space after.
2016-11-06 12:23:43 +08:00
chzyer 25c2772d5f fix deadlock in cancelable stdin (#89)
* fix deadlock in cancelable stdin

* fix notify back

* [cancelable stdin] fix c.notify
2016-10-06 12:35:07 +08:00
chzyer 94eaec69a7 add HistorySearchFold (#86)
* add HistorySearchFold

* [history] fix test
2016-10-03 14:51:04 +08:00
Ben Browning bc5e387904 Fix column padding calculation to prevent wrapping (#83)
The calculation to determine how many spaces to use when padding
columns wasn't taking into account the length of the 'same' portion of
the completions and was only using the length of the completion
itself. This frequently caused the printed completions to wrap, making
the completion list much harder to read.
2016-09-29 09:01:11 +08:00
31 changed files with 584 additions and 145 deletions

1
.gitignore vendored Normal file
View File

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

View File

@ -1,9 +1,8 @@
language: go
go:
- 1.5
- 1.7
- 1.x
script:
- GOOS=windows go install github.com/chzyer/readline/example/...
- GOOS=linux go install github.com/chzyer/readline/example/...
- GOOS=darwin go install github.com/chzyer/readline/example/...
- GOOS=windows go install git.internal/re/readline/example/...
- GOOS=linux go install git.internal/re/readline/example/...
- GOOS=darwin go install git.internal/re/readline/example/...
- go test -race -v

View File

@ -11,7 +11,7 @@
* [#38][38] add SetChildren for prefix completer interface
* [#42][42] improve multiple lines compatibility
* [#43][43] remove sub-package(runes) for gopkg compatiblity
* [#43][43] remove sub-package(runes) for gopkg compatibility
* [#46][46] Auto complete with space prefixed line
* [#48][48] support suspend process (ctrl+Z)
* [#49][49] fix bug that check equals with previous command
@ -19,12 +19,12 @@
### 1.2 - 2016-03-05
* 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)
* 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)
* [#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.
* 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.
* 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.
* Supports performs even stdin/stdout is not a tty.
* 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.
* 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.
* [#28][28], fixes the history is not working as expected.
* [#33][33], vim mode now support `c`, `d`, `x (delete character)`, `r (replace character)`
@ -42,17 +42,17 @@
* Initial public release.
[12]: https://github.com/chzyer/readline/pull/12
[17]: https://github.com/chzyer/readline/pull/17
[23]: https://github.com/chzyer/readline/pull/23
[27]: https://github.com/chzyer/readline/pull/27
[28]: https://github.com/chzyer/readline/pull/28
[33]: https://github.com/chzyer/readline/pull/33
[38]: https://github.com/chzyer/readline/pull/38
[42]: https://github.com/chzyer/readline/pull/42
[43]: https://github.com/chzyer/readline/pull/43
[46]: https://github.com/chzyer/readline/pull/46
[48]: https://github.com/chzyer/readline/pull/48
[49]: https://github.com/chzyer/readline/pull/49
[53]: https://github.com/chzyer/readline/pull/53
[60]: https://github.com/chzyer/readline/pull/60
[12]: https://git.internal/re/readline/pull/12
[17]: https://git.internal/re/readline/pull/17
[23]: https://git.internal/re/readline/pull/23
[27]: https://git.internal/re/readline/pull/27
[28]: https://git.internal/re/readline/pull/28
[33]: https://git.internal/re/readline/pull/33
[38]: https://git.internal/re/readline/pull/38
[42]: https://git.internal/re/readline/pull/42
[43]: https://git.internal/re/readline/pull/43
[46]: https://git.internal/re/readline/pull/46
[48]: https://git.internal/re/readline/pull/48
[49]: https://git.internal/re/readline/pull/49
[53]: https://git.internal/re/readline/pull/53
[60]: https://git.internal/re/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)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE.md)
[![Version](https://img.shields.io/github/tag/chzyer/readline.svg)](https://github.com/chzyer/readline/releases)
[![GoDoc](https://godoc.org/github.com/chzyer/readline?status.svg)](https://godoc.org/github.com/chzyer/readline)
[![Version](https://img.shields.io/github/tag/chzyer/readline.svg)](https://git.internal/re/readline/releases)
[![GoDoc](https://godoc.org/git.internal/re/readline?status.svg)](https://godoc.org/git.internal/re/readline)
[![OpenCollective](https://opencollective.com/readline/badge/backers.svg)](#backers)
[![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" />
</p>
A powerful readline library in `Linux` `macOS` `Windows`
A powerful readline library in `Linux` `macOS` `Windows` `Solaris` `AIX`
## Guide
@ -21,14 +21,15 @@ A powerful readline library in `Linux` `macOS` `Windows`
## Repos using readline
[![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)
[![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)
[![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)
[![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)
[![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

View File

@ -25,6 +25,7 @@ const (
COLOR_BINTENSITY = 0x0080
COMMON_LVB_UNDERSCORE = 0x8000
COMMON_LVB_BOLD = 0x0007
)
var ColorTableFg = []word{
@ -163,6 +164,8 @@ func (a *ANSIWriterCtx) ioloopEscSeq(w *bufio.Writer, r rune, argptr *[]string)
color |= ColorTableBg[c-40]
} else if c == 4 {
color |= COMMON_LVB_UNDERSCORE | ColorTableFg[7]
} else if c == 1 {
color |= COMMON_LVB_BOLD | COLOR_FINTENSITY
} else { // unknown code treat as reset
color = ColorTableFg[7]
}

View File

@ -203,7 +203,9 @@ func (o *opCompleter) CompleteRefresh() {
// -1 to avoid reach the end of line
width := o.width - 1
colNum := width / colWidth
colWidth += (width - (colWidth * colNum)) / colNum
if colNum != 0 {
colWidth += (width - (colWidth * colNum)) / colNum
}
o.candidateColNum = colNum
buf := bufio.NewWriter(o.w)
@ -219,7 +221,7 @@ func (o *opCompleter) CompleteRefresh() {
}
buf.WriteString(string(same))
buf.WriteString(string(c))
buf.Write(bytes.Repeat([]byte(" "), colWidth-len(c)))
buf.Write(bytes.Repeat([]byte(" "), colWidth-runes.WidthAll(c)-runes.WidthAll(same)))
if inSelect {
buf.WriteString("\033[0m")

View File

@ -9,7 +9,7 @@ import (
"strings"
"time"
"github.com/chzyer/readline"
"git.internal/re/readline"
)
func usage(w io.Writer) {
@ -61,6 +61,15 @@ var completer = readline.NewPrefixCompleter(
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() {
l, err := readline.NewEx(&readline.Config{
Prompt: "\033[31m»\033[0m ",
@ -69,12 +78,14 @@ func main() {
InterruptPrompt: "^C",
EOFPrompt: "exit",
HistorySearchFold: true,
HistorySearchFold: true,
FuncFilterInputRune: filterInput,
})
if err != nil {
panic(err)
}
defer l.Close()
l.CaptureExitSignal()
setPasswordCfg := l.GenPasswordConfig()
setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {
@ -127,12 +138,11 @@ func main() {
println("you set:", strconv.Quote(string(pswd)))
}
case strings.HasPrefix(line, "setprompt"):
prompt := line[10:]
if prompt == "" {
if len(line) <= 10 {
log.Println("setprompt <prompt>")
break
}
l.SetPrompt(prompt)
l.SetPrompt(line[10:])
case strings.HasPrefix(line, "say"):
line := strings.TrimSpace(line[3:])
if len(line) == 0 {

View File

@ -2,12 +2,12 @@ package main
import (
"fmt"
"log"
"math/rand"
"time"
"github.com/chzyer/readline"
"git.internal/re/readline"
)
import "log"
func main() {
rl, err := readline.NewEx(&readline.Config{

View File

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

View File

@ -4,26 +4,25 @@
//
// This file is licensed under the WTFPL:
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// 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
// copies of this license document, and changing it is allowed as long
// as the name is changed.
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// 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
import (
"fmt"
"github.com/chzyer/readline"
zxcvbn "github.com/nbutton23/zxcvbn-go"
"git.internal/re/readline"
)
const (
@ -50,30 +49,24 @@ func Colorize(msg string, color int) string {
func createStrengthPrompt(password []rune) string {
symbol, color := "", Red
strength := zxcvbn.PasswordStrength(string(password), nil)
switch {
case strength.Score <= 1:
case len(password) <= 1:
symbol = "✗"
color = Red
case strength.Score <= 2:
case len(password) <= 3:
symbol = "⚡"
color = Magenta
case strength.Score <= 3:
case len(password) <= 5:
symbol = "⚠"
color = Yellow
case strength.Score <= 4:
default:
symbol = "✔"
color = Green
}
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)
return prompt

View File

@ -1,6 +1,6 @@
package main
import "github.com/chzyer/readline"
import "git.internal/re/readline"
func main() {
if err := readline.DialRemote("tcp", ":12344"); err != nil {

View File

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

8
go.mod Normal file
View File

@ -0,0 +1,8 @@
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 Normal file
View File

@ -0,0 +1,6 @@
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,12 +27,14 @@ type opHistory struct {
current *list.Element
fd *os.File
fdLock sync.Mutex
enable bool
}
func newOpHistory(cfg *Config) (o *opHistory) {
o = &opHistory{
cfg: cfg,
history: list.New(),
enable: true,
}
return o
}
@ -117,7 +119,7 @@ func (o *opHistory) rewriteLocked() {
buf := bufio.NewWriter(fd)
for elem := o.history.Front(); elem != nil; elem = elem.Next() {
buf.WriteString(string(elem.Value.(*hisItem).Source))
buf.WriteString(string(elem.Value.(*hisItem).Source) + "\n")
}
buf.Flush()
@ -223,6 +225,16 @@ func (o *opHistory) Next() ([]rune, bool) {
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() {
Debug("-------")
for item := o.history.Front(); item != nil; item = item.Next() {
@ -232,6 +244,12 @@ func (o *opHistory) debug() {
// save history
func (o *opHistory) New(current []rune) (err error) {
// history deactivated
if !o.enable {
return nil
}
current = runes.Copy(current)
// if just use last command without modify

View File

@ -3,6 +3,7 @@ package readline
import (
"errors"
"io"
"sync"
)
var (
@ -18,6 +19,7 @@ func (*InterruptError) Error() string {
}
type Operation struct {
m sync.Mutex
cfg *Config
t *Terminal
buf *RuneBuffer
@ -32,6 +34,10 @@ type Operation struct {
*opVim
}
func (o *Operation) SetBuffer(what string) {
o.buf.Set([]rune(what))
}
type wrapWriter struct {
r *Operation
t *Terminal
@ -66,7 +72,7 @@ func NewOperation(t *Terminal, cfg *Config) *Operation {
t: t,
buf: NewRuneBuffer(t, cfg.Prompt, cfg, width),
outchan: make(chan []rune),
errchan: make(chan error),
errchan: make(chan error, 1),
}
op.w = op.buf.w
op.SetConfig(cfg)
@ -91,11 +97,29 @@ func (o *Operation) SetMaskRune(r rune) {
o.buf.SetMask(r)
}
func (o *Operation) GetConfig() *Config {
o.m.Lock()
cfg := *o.cfg
o.m.Unlock()
return &cfg
}
func (o *Operation) ioloop() {
for {
keepInSearchMode := false
keepInCompleteMode := false
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 o.buf.Len() == 0 {
o.buf.Clean()
@ -149,7 +173,7 @@ func (o *Operation) ioloop() {
o.buf.Refresh(nil)
}
case CharTab:
if o.cfg.AutoComplete == nil {
if o.GetConfig().AutoComplete == nil {
o.t.Bell()
break
}
@ -213,13 +237,15 @@ func (o *Operation) ioloop() {
o.Refresh()
case MetaBackspace, CharCtrlW:
o.buf.BackEscapeWord()
case CharCtrlY:
o.buf.Yank()
case CharEnter, CharCtrlJ:
if o.IsSearchMode() {
o.ExitSearchMode(false)
}
o.buf.MoveToLineEnd()
var data []rune
if !o.cfg.UniqueEditLine {
if !o.GetConfig().UniqueEditLine {
o.buf.WriteRune('\n')
data = o.buf.Reset()
data = data[:len(data)-1] // trim \n
@ -228,7 +254,7 @@ func (o *Operation) ioloop() {
data = o.buf.Reset()
}
o.outchan <- data
if !o.cfg.DisableAutoSaveHistory {
if !o.GetConfig().DisableAutoSaveHistory {
// ignore IO error
_ = o.history.New(data)
} else {
@ -262,14 +288,14 @@ func (o *Operation) ioloop() {
}
// treat as EOF
if !o.cfg.UniqueEditLine {
o.buf.WriteString(o.cfg.EOFPrompt + "\n")
if !o.GetConfig().UniqueEditLine {
o.buf.WriteString(o.GetConfig().EOFPrompt + "\n")
}
o.buf.Reset()
isUpdateHistory = false
o.history.Revert()
o.errchan <- io.EOF
if o.cfg.UniqueEditLine {
if o.GetConfig().UniqueEditLine {
o.buf.Clean()
}
case CharInterrupt:
@ -286,12 +312,12 @@ func (o *Operation) ioloop() {
}
o.buf.MoveToLineEnd()
o.buf.Refresh(nil)
hint := o.cfg.InterruptPrompt + "\n"
if !o.cfg.UniqueEditLine {
hint := o.GetConfig().InterruptPrompt + "\n"
if !o.GetConfig().UniqueEditLine {
o.buf.WriteString(hint)
}
remain := o.buf.Reset()
if !o.cfg.UniqueEditLine {
if !o.GetConfig().UniqueEditLine {
remain = remain[:len(remain)-len([]rune(hint))]
}
isUpdateHistory = false
@ -310,13 +336,15 @@ func (o *Operation) ioloop() {
}
}
if o.cfg.Listener != nil {
newLine, newPos, ok := o.cfg.Listener.OnChange(o.buf.Runes(), o.buf.Pos(), r)
listener := o.GetConfig().Listener
if listener != nil {
newLine, newPos, ok := listener.OnChange(o.buf.Runes(), o.buf.Pos(), r)
if ok {
o.buf.SetWithIdx(newPos, newLine)
}
}
o.m.Lock()
if !keepInSearchMode && o.IsSearchMode() {
o.ExitSearchMode(false)
o.buf.Refresh(nil)
@ -333,15 +361,16 @@ func (o *Operation) ioloop() {
// it will cause null history
o.history.Update(o.buf.Runes(), false)
}
o.m.Unlock()
}
}
func (o *Operation) Stderr() io.Writer {
return &wrapWriter{target: o.cfg.Stderr, r: o, t: o.t}
return &wrapWriter{target: o.GetConfig().Stderr, r: o, t: o.t}
}
func (o *Operation) Stdout() io.Writer {
return &wrapWriter{target: o.cfg.Stdout, r: o, t: o.t}
return &wrapWriter{target: o.GetConfig().Stdout, r: o, t: o.t}
}
func (o *Operation) String() (string, error) {
@ -353,8 +382,9 @@ func (o *Operation) Runes() ([]rune, error) {
o.t.EnterRawMode()
defer o.t.ExitRawMode()
if o.cfg.Listener != nil {
o.cfg.Listener.OnChange(nil, 0, 0)
listener := o.GetConfig().Listener
if listener != nil {
listener.OnChange(nil, 0, 0)
}
o.buf.Refresh(nil) // print prompt
@ -406,6 +436,10 @@ func (o *Operation) Slice() ([]byte, error) {
}
func (o *Operation) Close() {
select {
case o.errchan <- io.EOF:
default:
}
o.history.Close()
}
@ -422,6 +456,8 @@ func (o *Operation) IsNormalMode() bool {
}
func (op *Operation) SetConfig(cfg *Config) (*Config, error) {
op.m.Lock()
defer op.m.Unlock()
if op.cfg == cfg {
return op.cfg, nil
}
@ -489,3 +525,13 @@ func (d *DumpListener) OnChange(line []rune, pos int, key rune) (newLine []rune,
type Listener interface {
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,6 +25,7 @@ func (o *opPassword) PasswordConfig() *Config {
InterruptPrompt: "\n",
EOFPrompt: "\n",
HistoryLimit: -1,
Painter: &defaultPainter{},
Stdout: o.o.cfg.Stdout,
Stderr: o.o.cfg.Stderr,

View File

@ -17,7 +17,9 @@
//
package readline
import "io"
import (
"io"
)
type Instance struct {
Config *Config
@ -44,6 +46,8 @@ type Config struct {
// NOTE: Listener will be triggered by (nil, 0, 0) immediately
Listener Listener
Painter Painter
// If VimMode is true, readline will in vim.insert mode by default
VimMode bool
@ -52,9 +56,10 @@ type Config struct {
FuncGetWidth func() int
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
Stdin io.ReadCloser
StdinWriter io.Writer
Stdout io.Writer
Stderr io.Writer
EnableMask bool
MaskRune rune
@ -63,6 +68,10 @@ type Config struct {
// it use in IM usually.
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
FuncIsTerminal func() bool
FuncMakeRaw func() error
@ -91,6 +100,9 @@ func (c *Config) Init() error {
if c.Stdin == nil {
c.Stdin = NewCancelableStdin(Stdin)
}
c.Stdin, c.StdinWriter = NewFillableStdin(c.Stdin)
if c.Stdout == nil {
c.Stdout = Stdout
}
@ -145,12 +157,19 @@ func (c *Config) SetListener(f func(line []rune, pos int, key rune) (newLine []r
c.Listener = FuncListener(f)
}
func (c *Config) SetPainter(p Painter) {
c.Painter = p
}
func NewEx(cfg *Config) (*Instance, error) {
t, err := NewTerminal(cfg)
if err != nil {
return nil, err
}
rl := t.Readline()
if cfg.Painter == nil {
cfg.Painter = &defaultPainter{}
}
return &Instance{
Config: cfg,
Terminal: t,
@ -238,6 +257,11 @@ func (i *Instance) Readline() (string, error) {
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 {
return i.Operation.SaveHistory(content)
}
@ -248,13 +272,24 @@ func (i *Instance) ReadSlice() ([]byte, error) {
}
// 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 {
i.Config.Stdin.Close()
i.Operation.Close()
if err := i.Terminal.Close(); err != nil {
return err
}
i.Operation.Close()
return nil
}
// call CaptureExitSignal when you want readline exit gracefully.
func (i *Instance) CaptureExitSignal() {
CaptureExitSignal(func() {
i.Close()
})
}
func (i *Instance) Clean() {
i.Operation.Clean()
}
@ -263,6 +298,20 @@ func (i *Instance) Write(b []byte) (int, error) {
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 {
if i.Config == cfg {
return cfg
@ -277,3 +326,13 @@ func (i *Instance) SetConfig(cfg *Config) *Config {
func (i *Instance) 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,11 +189,12 @@ loop:
}
}
func (r *RemoteSvr) Close() {
func (r *RemoteSvr) Close() error {
if atomic.CompareAndSwapInt32(&r.closed, 0, 1) {
close(r.stopChan)
r.conn.Close()
}
return nil
}
func (r *RemoteSvr) readLoop(buf *bufio.Reader) {

View File

@ -4,6 +4,7 @@ import (
"bufio"
"bytes"
"io"
"strconv"
"strings"
"sync"
)
@ -29,9 +30,15 @@ type RuneBuffer struct {
offset string
lastKill []rune
sync.Mutex
}
func (r *RuneBuffer) pushKill(text []rune) {
r.lastKill = append([]rune{}, text...)
}
func (r *RuneBuffer) OnWidthChange(newWidth int) {
r.Lock()
r.width = newWidth
@ -187,6 +194,7 @@ func (r *RuneBuffer) Replace(ch rune) {
func (r *RuneBuffer) Erase() {
r.Refresh(func() {
r.idx = 0
r.pushKill(r.buf[:])
r.buf = r.buf[:0]
})
}
@ -196,6 +204,7 @@ func (r *RuneBuffer) Delete() (success bool) {
if r.idx == len(r.buf) {
return
}
r.pushKill(r.buf[r.idx : r.idx+1])
r.buf = append(r.buf[:r.idx], r.buf[r.idx+1:]...)
success = true
})
@ -212,6 +221,7 @@ func (r *RuneBuffer) DeleteWord() {
}
for i := init + 1; i < len(r.buf); i++ {
if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) {
r.pushKill(r.buf[r.idx : i-1])
r.Refresh(func() {
r.buf = append(r.buf[:r.idx], r.buf[i-1:]...)
})
@ -247,6 +257,7 @@ func (r *RuneBuffer) KillFront() {
}
length := len(r.buf) - r.idx
r.pushKill(r.buf[:r.idx])
copy(r.buf[:length], r.buf[r.idx:])
r.idx = 0
r.buf = r.buf[:length]
@ -255,6 +266,7 @@ func (r *RuneBuffer) KillFront() {
func (r *RuneBuffer) Kill() {
r.Refresh(func() {
r.pushKill(r.buf[r.idx:])
r.buf = r.buf[:r.idx]
})
}
@ -321,6 +333,7 @@ func (r *RuneBuffer) BackEscapeWord() {
}
for i := r.idx - 1; i > 0; i-- {
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.idx = i
return
@ -332,6 +345,20 @@ 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() {
r.Refresh(func() {
if r.idx == 0 {
@ -460,28 +487,58 @@ func (r *RuneBuffer) output() []byte {
buf.Write([]byte(string(r.cfg.MaskRune)))
}
if len(r.buf) > r.idx {
buf.Write(runes.Backspace(r.buf[r.idx:]))
buf.Write(r.getBackspaceSequence())
}
} else {
for idx := range r.buf {
if r.buf[idx] == '\t' {
for _, e := range r.cfg.Painter.Paint(r.buf, r.idx) {
if e == '\t' {
buf.WriteString(strings.Repeat(" ", TabWidth))
} else {
buf.WriteRune(r.buf[idx])
buf.WriteRune(e)
}
}
if r.isInLineEdge() {
buf.Write([]byte(" \b"))
}
}
// cursor position
if len(r.buf) > r.idx {
buf.Write(runes.Backspace(r.buf[r.idx:]))
buf.Write(r.getBackspaceSequence())
}
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 {
ret := runes.Copy(r.buf)
r.buf = r.buf[:0]

View File

@ -1,6 +1,6 @@
// deprecated.
// see https://github.com/chzyer/readline/issues/43
// use github.com/chzyer/readline/runes.go
// see https://git.internal/re/readline/issues/43
// use git.internal/re/readline/runes.go
package runes
import (

85
std.go
View File

@ -4,6 +4,7 @@ import (
"io"
"os"
"sync"
"sync/atomic"
)
var (
@ -69,6 +70,7 @@ type CancelableStdin struct {
r io.Reader
mutex sync.Mutex
stop chan struct{}
closed int32
notify chan struct{}
data []byte
read int
@ -91,7 +93,11 @@ loop:
select {
case <-c.notify:
c.read, c.err = c.r.Read(c.data)
c.notify <- struct{}{}
select {
case c.notify <- struct{}{}:
case <-c.stop:
break loop
}
case <-c.stop:
break loop
}
@ -101,9 +107,16 @@ loop:
func (c *CancelableStdin) Read(b []byte) (n int, err error) {
c.mutex.Lock()
defer c.mutex.Unlock()
if atomic.LoadInt32(&c.closed) == 1 {
return 0, io.EOF
}
c.data = b
c.notify <- struct{}{}
select {
case c.notify <- struct{}{}:
case <-c.stop:
return 0, io.EOF
}
select {
case <-c.notify:
return c.read, c.err
@ -113,6 +126,72 @@ func (c *CancelableStdin) Read(b []byte) (n int, err error) {
}
func (c *CancelableStdin) Close() error {
close(c.stop)
if atomic.CompareAndSwapInt32(&c.closed, 0, 1) {
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
}

49
term.go
View File

@ -2,7 +2,7 @@
// 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
// +build aix darwin dragonfly freebsd linux,!appengine netbsd openbsd os400 solaris
// Package terminal provides support functions for dealing with terminals, as
// commonly found on UNIX systems.
@ -19,19 +19,17 @@ package readline
import (
"io"
"syscall"
"unsafe"
)
// State contains the state of a terminal.
type State struct {
termios syscall.Termios
termios Termios
}
// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal(fd int) bool {
var termios syscall.Termios
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
return err == 0
_, err := getTermios(fd)
return err == nil
}
// MakeRaw put the terminal connected to the given file descriptor into raw
@ -39,8 +37,11 @@ func IsTerminal(fd int) bool {
// restored.
func MakeRaw(fd int) (*State, error) {
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
} else {
oldState.termios = *termios
}
newState := oldState.termios
@ -52,47 +53,35 @@ func MakeRaw(fd int) (*State, error) {
newState.Cflag &^= syscall.CSIZE | syscall.PARENB
newState.Cflag |= syscall.CS8
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 {
return nil, err
}
newState.Cc[syscall.VMIN] = 1
newState.Cc[syscall.VTIME] = 0
return &oldState, nil
return &oldState, setTermios(fd, &newState)
}
// GetState returns the current state of a terminal which may be useful to
// restore the terminal after a signal.
func GetState(fd int) (*State, error) {
var oldState State
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 {
termios, err := getTermios(fd)
if err != nil {
return nil, err
}
return &oldState, nil
return &State{termios: *termios}, nil
}
// Restore restores the terminal connected to the given file descriptor to a
// previous state.
func restoreTerm(fd int, state *State) error {
_, _, 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
return setTermios(fd, &state.termios)
}
// ReadPassword reads a line of input from a terminal without local echo. This
// is commonly used for inputting passwords and other sensitive data. The slice
// returned does not include the \n.
func ReadPassword(fd int) ([]byte, error) {
var oldState syscall.Termios
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0); err != 0 {
oldState, err := getTermios(fd)
if err != nil {
return nil, err
}
@ -100,12 +89,12 @@ func ReadPassword(fd int) ([]byte, error) {
newState.Lflag &^= syscall.ECHO
newState.Lflag |= syscall.ICANON | syscall.ISIG
newState.Iflag |= syscall.ICRNL
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 {
if err := setTermios(fd, newState); err != nil {
return nil, err
}
defer func() {
syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0)
setTermios(fd, oldState)
}()
var buf [16]byte

View File

@ -6,7 +6,24 @@
package readline
import "syscall"
import (
"syscall"
"unsafe"
)
const ioctlReadTermios = syscall.TIOCGETA
const ioctlWriteTermios = syscall.TIOCSETA
func getTermios(fd int) (*Termios, error) {
termios := new(Termios)
_, _, 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,8 +4,30 @@
package readline
import (
"syscall"
"unsafe"
)
// These constants are declared here, rather than importing
// them from the syscall package as some syscall packages, even
// on linux, for example gccgo, do not declare them.
const ioctlReadTermios = 0x5401 // syscall.TCGETS
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
}

32
term_nosyscall6.go Normal file
View File

@ -0,0 +1,32 @@
// 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 Normal file
View File

@ -0,0 +1,24 @@
// 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,6 +10,7 @@ import (
)
type Terminal struct {
m sync.Mutex
cfg *Config
outchan chan rune
closed int32
@ -64,6 +65,12 @@ func (t *Terminal) Write(b []byte) (int, error) {
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 {
left int
top int
@ -118,10 +125,11 @@ func (t *Terminal) ioloop() {
var (
isEscape bool
isEscapeEx bool
isEscapeSS3 bool
expectNextChar bool
)
buf := bufio.NewReader(t.cfg.Stdin)
buf := bufio.NewReader(t.getStdin())
for {
if !expectNextChar {
atomic.StoreInt32(&t.isReading, 0)
@ -145,9 +153,15 @@ func (t *Terminal) ioloop() {
if isEscape {
isEscape = false
if r == CharEscapeEx {
// ^][
expectNextChar = true
isEscapeEx = true
continue
} else if r == CharO {
// ^]O
expectNextChar = true
isEscapeSS3 = true
continue
}
r = escapeKey(r, buf)
} else if isEscapeEx {
@ -170,6 +184,15 @@ func (t *Terminal) ioloop() {
expectNextChar = true
continue
}
} else if isEscapeSS3 {
isEscapeSS3 = false
if key := readEscKey(r, buf); key != nil {
r = escapeSS3Key(key)
}
if r == 0 {
expectNextChar = true
continue
}
}
expectNextChar = true
@ -206,10 +229,26 @@ func (t *Terminal) Close() error {
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 {
if err := c.Init(); err != nil {
return err
}
t.m.Lock()
t.cfg = c
t.m.Unlock()
return nil
}

View File

@ -6,9 +6,11 @@ import (
"container/list"
"fmt"
"os"
"os/signal"
"strconv"
"strings"
"sync"
"syscall"
"time"
"unicode"
)
@ -38,8 +40,10 @@ const (
CharTranspose = 20
CharCtrlU = 21
CharCtrlW = 23
CharCtrlY = 25
CharCtrlZ = 26
CharEsc = 27
CharO = 79
CharEscapeEx = 91
CharBackspace = 127
)
@ -82,7 +86,9 @@ func Restore(fd int, state *State) error {
if err != nil {
// errno 0 means everything is ok :)
if err.Error() == "errno 0" {
err = nil
return nil
} else {
return err
}
}
return nil
@ -118,6 +124,27 @@ func escapeExKey(key *escapeKeyPair) rune {
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 {
attr string
typ rune
@ -272,3 +299,13 @@ func Debug(o ...interface{}) {
fmt.Fprintln(f, o...)
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 darwin dragonfly freebsd linux,!appengine netbsd openbsd
// +build aix darwin dragonfly freebsd linux,!appengine netbsd openbsd os400 solaris
package readline
@ -8,7 +8,6 @@ import (
"os/signal"
"sync"
"syscall"
"unsafe"
)
type winsize struct {
@ -30,17 +29,11 @@ func SuspendMe() {
// get width of the terminal
func getWidth(stdoutFd int) int {
ws := &winsize{}
retCode, _, errno := syscall.Syscall(syscall.SYS_IOCTL,
uintptr(stdoutFd),
uintptr(syscall.TIOCGWINSZ),
uintptr(unsafe.Pointer(ws)))
if int(retCode) == -1 {
_ = errno
cols, _, err := GetSize(stdoutFd)
if err != nil {
return -1
}
return int(ws.Col)
return cols
}
func GetScreenWidth() int {

2
vim.go
View File

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