diff -Nru golang-github-chzyer-readline-1.4/ansi_windows.go golang-github-chzyer-readline-1.4+git20171103.a4d5111/ansi_windows.go --- golang-github-chzyer-readline-1.4/ansi_windows.go 2016-07-26 13:51:17.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/ansi_windows.go 2017-11-30 20:35:43.000000000 +0000 @@ -25,6 +25,7 @@ COLOR_BINTENSITY = 0x0080 COMMON_LVB_UNDERSCORE = 0x8000 + COMMON_LVB_BOLD = 0x0007 ) var ColorTableFg = []word{ @@ -163,6 +164,8 @@ 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] } diff -Nru golang-github-chzyer-readline-1.4/CHANGELOG.md golang-github-chzyer-readline-1.4+git20171103.a4d5111/CHANGELOG.md --- golang-github-chzyer-readline-1.4/CHANGELOG.md 2016-07-26 13:51:17.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/CHANGELOG.md 2017-11-30 20:35:43.000000000 +0000 @@ -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 diff -Nru golang-github-chzyer-readline-1.4/char.go golang-github-chzyer-readline-1.4+git20171103.a4d5111/char.go --- golang-github-chzyer-readline-1.4/char.go 2016-07-26 13:51:17.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/char.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,36 +0,0 @@ -package readline - -const ( - CharLineStart = 1 - CharBackward = 2 - CharInterrupt = 3 - CharDelete = 4 - CharLineEnd = 5 - CharForward = 6 - CharBell = 7 - CharCtrlH = 8 - CharTab = 9 - CharCtrlJ = 10 - CharKill = 11 - CharCtrlL = 12 - CharEnter = 13 - CharNext = 14 - CharPrev = 16 - CharBckSearch = 18 - CharFwdSearch = 19 - CharTranspose = 20 - CharCtrlU = 21 - CharCtrlW = 23 - CharCtrlZ = 26 - CharEsc = 27 - CharEscapeEx = 91 - CharBackspace = 127 -) - -const ( - MetaBackward rune = -iota - 1 - MetaForward - MetaDelete - MetaBackspace - MetaTranspose -) diff -Nru golang-github-chzyer-readline-1.4/complete.go golang-github-chzyer-readline-1.4+git20171103.a4d5111/complete.go --- golang-github-chzyer-readline-1.4/complete.go 2016-07-26 13:51:17.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/complete.go 2017-11-30 20:35:43.000000000 +0000 @@ -18,6 +18,12 @@ Do(line []rune, pos int) (newLine [][]rune, length int) } +type TabCompleter struct{} + +func (t *TabCompleter) Do([]rune, int) ([][]rune, int) { + return [][]rune{[]rune("\t")}, 0 +} + type opCompleter struct { w io.Writer op *Operation @@ -58,10 +64,13 @@ } } -func (o *opCompleter) OnComplete() { +func (o *opCompleter) OnComplete() bool { + if o.width == 0 { + return false + } if o.IsInCompleteSelectMode() { o.doSelect() - return + return true } buf := o.op.buf @@ -70,7 +79,7 @@ if o.IsInCompleteMode() && o.candidateSource != nil && runes.Equal(rs, o.candidateSource) { o.EnterCompleteSelectMode() o.doSelect() - return + return true } o.ExitCompleteSelectMode() @@ -78,7 +87,7 @@ newLines, offset := o.op.cfg.AutoComplete.Do(rs, buf.idx) if len(newLines) == 0 { o.ExitCompleteMode(false) - return + return true } // only Aggregate candidates in non-complete mode @@ -86,18 +95,19 @@ if len(newLines) == 1 { buf.WriteRunes(newLines[0]) o.ExitCompleteMode(false) - return + return true } same, size := runes.Aggregate(newLines) if size > 0 { buf.WriteRunes(same) o.ExitCompleteMode(false) - return + return true } } o.EnterCompleteMode(offset, newLines) + return true } func (o *opCompleter) IsInCompleteSelectMode() bool { @@ -209,7 +219,7 @@ } buf.WriteString(string(same)) buf.WriteString(string(c)) - buf.Write(bytes.Repeat([]byte(" "), colWidth-len(c))) + buf.Write(bytes.Repeat([]byte(" "), colWidth-len(c)-len(same))) if inSelect { buf.WriteString("\033[0m") diff -Nru golang-github-chzyer-readline-1.4/debian/changelog golang-github-chzyer-readline-1.4+git20171103.a4d5111/debian/changelog --- golang-github-chzyer-readline-1.4/debian/changelog 2017-07-24 21:42:07.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/debian/changelog 2017-11-30 22:14:52.000000000 +0000 @@ -1,3 +1,14 @@ +golang-github-chzyer-readline (1.4+git20171103.a4d5111-1) unstable; urgency=medium + + * Team upload. + * New upstream version 1.4+git20171103.a4d5111 + - Remove patch, has been applied upstream + * Update to Standards-Version 4.1.1 + - Use HTTPS URL for d/copyright + - Use Priority: optional + + -- Dr. Tobias Quathamer Thu, 30 Nov 2017 23:14:52 +0100 + golang-github-chzyer-readline (1.4-1) unstable; urgency=medium * Initial release diff -Nru golang-github-chzyer-readline-1.4/debian/control golang-github-chzyer-readline-1.4+git20171103.a4d5111/debian/control --- golang-github-chzyer-readline-1.4/debian/control 2017-07-21 05:00:25.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/debian/control 2017-11-30 20:39:32.000000000 +0000 @@ -1,13 +1,13 @@ Source: golang-github-chzyer-readline Section: devel -Priority: extra +Priority: optional Maintainer: Debian Go Packaging Team Uploaders: Paul Tagliamonte Build-Depends: debhelper (>= 10), dh-golang, golang-any, golang-github-nbutton23-zxcvbn-go-dev -Standards-Version: 4.0.0 +Standards-Version: 4.1.1 Homepage: https://github.com/chzyer/readline Vcs-Browser: https://anonscm.debian.org/cgit/pkg-go/packages/golang-github-chzyer-readline.git Vcs-Git: https://anonscm.debian.org/git/pkg-go/packages/golang-github-chzyer-readline.git diff -Nru golang-github-chzyer-readline-1.4/debian/copyright golang-github-chzyer-readline-1.4+git20171103.a4d5111/debian/copyright --- golang-github-chzyer-readline-1.4/debian/copyright 2017-07-24 21:30:33.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/debian/copyright 2017-11-30 20:39:29.000000000 +0000 @@ -1,4 +1,4 @@ -Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: readline Source: https://github.com/chzyer/readline diff -Nru golang-github-chzyer-readline-1.4/debian/patches/105.patch golang-github-chzyer-readline-1.4+git20171103.a4d5111/debian/patches/105.patch --- golang-github-chzyer-readline-1.4/debian/patches/105.patch 2017-07-21 05:00:25.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/debian/patches/105.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,36 +0,0 @@ -Description: Add two API methods to set RuneBuffer state before prompting the user -Author: Paul Tagliamonte -Forwarded: https://github.com/chzyer/readline/pull/105 -Applied-Upstream: 1.5, commit:41eea22f717c616615e1e59aa06cf831f9901f35 - -diff --git a/operation.go b/operation.go -index 14ade31..2c93561 100644 ---- a/operation.go -+++ b/operation.go -@@ -32,6 +32,10 @@ type Operation struct { - *opVim - } - -+func (o *Operation) SetBuffer(what string) { -+ o.buf.Set([]rune(what)) -+} -+ - type wrapWriter struct { - r *Operation - t *Terminal -diff --git a/readline.go b/readline.go -index 3f02a83..b0242f7 100644 ---- a/readline.go -+++ b/readline.go -@@ -242,6 +242,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) - } diff -Nru golang-github-chzyer-readline-1.4/debian/patches/series golang-github-chzyer-readline-1.4+git20171103.a4d5111/debian/patches/series --- golang-github-chzyer-readline-1.4/debian/patches/series 2017-07-21 05:00:25.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/debian/patches/series 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -105.patch diff -Nru golang-github-chzyer-readline-1.4/debug.go golang-github-chzyer-readline-1.4+git20171103.a4d5111/debug.go --- golang-github-chzyer-readline-1.4/debug.go 2016-07-26 13:51:17.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/debug.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,29 +0,0 @@ -package readline - -import ( - "container/list" - "fmt" - "os" - "time" -) - -func sleep(n int) { - Debug(n) - time.Sleep(2000 * time.Millisecond) -} - -// print a linked list to Debug() -func debugList(l *list.List) { - idx := 0 - for e := l.Front(); e != nil; e = e.Next() { - Debug(idx, fmt.Sprintf("%+v", e.Value)) - idx++ - } -} - -// append log info to another file -func Debug(o ...interface{}) { - f, _ := os.OpenFile("debug.tmp", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) - fmt.Fprintln(f, o...) - f.Close() -} diff -Nru golang-github-chzyer-readline-1.4/doc/shortcut.md golang-github-chzyer-readline-1.4+git20171103.a4d5111/doc/shortcut.md --- golang-github-chzyer-readline-1.4/doc/shortcut.md 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/doc/shortcut.md 2017-11-30 20:35:43.000000000 +0000 @@ -0,0 +1,62 @@ +## Readline Shortcut + +`Meta`+`B` means press `Esc` and `n` separately. +Users can change that in terminal simulator(i.e. iTerm2) to `Alt`+`B` +Notice: `Meta`+`B` is equals with `Alt`+`B` in windows. + +* Shortcut in normal mode + +| Shortcut | Comment | +| ------------------ | --------------------------------- | +| `Ctrl`+`A` | Beginning of line | +| `Ctrl`+`B` / `←` | Backward one character | +| `Meta`+`B` | Backward one word | +| `Ctrl`+`C` | Send io.EOF | +| `Ctrl`+`D` | Delete one character | +| `Meta`+`D` | Delete one word | +| `Ctrl`+`E` | End of line | +| `Ctrl`+`F` / `→` | Forward one character | +| `Meta`+`F` | Forward one word | +| `Ctrl`+`G` | Cancel | +| `Ctrl`+`H` | Delete previous character | +| `Ctrl`+`I` / `Tab` | Command line completion | +| `Ctrl`+`J` | Line feed | +| `Ctrl`+`K` | Cut text to the end of line | +| `Ctrl`+`L` | Clear screen | +| `Ctrl`+`M` | Same as Enter key | +| `Ctrl`+`N` / `↓` | Next line (in history) | +| `Ctrl`+`P` / `↑` | Prev line (in history) | +| `Ctrl`+`R` | Search backwards in history | +| `Ctrl`+`S` | Search forwards in history | +| `Ctrl`+`T` | Transpose characters | +| `Meta`+`T` | Transpose words (TODO) | +| `Ctrl`+`U` | Cut text to the beginning of line | +| `Ctrl`+`W` | Cut previous word | +| `Backspace` | Delete previous character | +| `Meta`+`Backspace` | Cut previous word | +| `Enter` | Line feed | + + +* Shortcut in Search Mode (`Ctrl`+`S` or `Ctrl`+`r` to enter this mode) + +| Shortcut | Comment | +| ----------------------- | --------------------------------------- | +| `Ctrl`+`S` | Search forwards in history | +| `Ctrl`+`R` | Search backwards in history | +| `Ctrl`+`C` / `Ctrl`+`G` | Exit Search Mode and revert the history | +| `Backspace` | Delete previous character | +| Other | Exit Search Mode | + +* Shortcut in Complete Select Mode (double `Tab` to enter this mode) + +| Shortcut | Comment | +| ----------------------- | ---------------------------------------- | +| `Ctrl`+`F` | Move Forward | +| `Ctrl`+`B` | Move Backward | +| `Ctrl`+`N` | Move to next line | +| `Ctrl`+`P` | Move to previous line | +| `Ctrl`+`A` | Move to the first candicate in current line | +| `Ctrl`+`E` | Move to the last candicate in current line | +| `Tab` / `Enter` | Use the word on cursor to complete | +| `Ctrl`+`C` / `Ctrl`+`G` | Exit Complete Select Mode | +| Other | Exit Complete Select Mode | \ No newline at end of file diff -Nru golang-github-chzyer-readline-1.4/doc.go golang-github-chzyer-readline-1.4+git20171103.a4d5111/doc.go --- golang-github-chzyer-readline-1.4/doc.go 2016-07-26 13:51:17.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/doc.go 1970-01-01 00:00:00.000000000 +0000 @@ -1,20 +0,0 @@ -// Readline is a pure go implementation for GNU-Readline kind library. -// -// WHY: Readline will support most of features which GNU Readline is supported, and provide a pure go environment and a MIT license. -// -// example: -// rl, err := readline.New("> ") -// if err != nil { -// panic(err) -// } -// defer rl.Close() -// -// for { -// line, err := rl.Readline() -// if err != nil { // io.EOF -// break -// } -// println(line) -// } -// -package readline diff -Nru golang-github-chzyer-readline-1.4/example/readline-demo/readline-demo.go golang-github-chzyer-readline-1.4+git20171103.a4d5111/example/readline-demo/readline-demo.go --- golang-github-chzyer-readline-1.4/example/readline-demo/readline-demo.go 2016-07-26 13:51:17.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/example/readline-demo/readline-demo.go 2017-11-30 20:35:43.000000000 +0000 @@ -61,6 +61,15 @@ 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 ", @@ -68,6 +77,9 @@ AutoComplete: completer, InterruptPrompt: "^C", EOFPrompt: "exit", + + HistorySearchFold: true, + FuncFilterInputRune: filterInput, }) if err != nil { panic(err) @@ -125,12 +137,11 @@ println("you set:", strconv.Quote(string(pswd))) } case strings.HasPrefix(line, "setprompt"): - prompt := line[10:] - if prompt == "" { + if len(line) <= 10 { log.Println("setprompt ") break } - l.SetPrompt(prompt) + l.SetPrompt(line[10:]) case strings.HasPrefix(line, "say"): line := strings.TrimSpace(line[3:]) if len(line) == 0 { diff -Nru golang-github-chzyer-readline-1.4/.gitignore golang-github-chzyer-readline-1.4+git20171103.a4d5111/.gitignore --- golang-github-chzyer-readline-1.4/.gitignore 2016-07-26 13:51:17.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/.gitignore 1970-01-01 00:00:00.000000000 +0000 @@ -1,2 +0,0 @@ -*.exe -*.tmp diff -Nru golang-github-chzyer-readline-1.4/history.go golang-github-chzyer-readline-1.4+git20171103.a4d5111/history.go --- golang-github-chzyer-readline-1.4/history.go 2016-07-26 13:51:17.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/history.go 2017-11-30 20:35:43.000000000 +0000 @@ -6,6 +6,7 @@ "fmt" "os" "strings" + "sync" ) type hisItem struct { @@ -25,12 +26,15 @@ historyVer int64 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 } @@ -41,6 +45,8 @@ } func (o *opHistory) IsHistoryClosed() bool { + o.fdLock.Lock() + defer o.fdLock.Unlock() return o.fd.Fd() == ^(uintptr(0)) } @@ -58,6 +64,8 @@ // only called by newOpHistory func (o *opHistory) historyUpdatePath(path string) { + o.fdLock.Lock() + defer o.fdLock.Unlock() f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666) if err != nil { return @@ -79,7 +87,7 @@ o.Compact() } if total > o.cfg.HistoryLimit { - o.Rewrite() + o.rewriteLocked() } o.historyVer++ o.Push(nil) @@ -93,6 +101,12 @@ } func (o *opHistory) Rewrite() { + o.fdLock.Lock() + defer o.fdLock.Unlock() + o.rewriteLocked() +} + +func (o *opHistory) rewriteLocked() { if o.cfg.HistoryFile == "" { return } @@ -105,7 +119,7 @@ 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() @@ -123,6 +137,8 @@ } func (o *opHistory) Close() { + o.fdLock.Lock() + defer o.fdLock.Unlock() if o.fd != nil { o.fd.Close() } @@ -139,7 +155,7 @@ item = item[:start] } } - idx := runes.IndexAllBck(item, rs) + idx := runes.IndexAllBckEx(item, rs, o.cfg.HistorySearchFold) if idx < 0 { continue } @@ -164,7 +180,7 @@ continue } } - idx := runes.IndexAll(item, rs) + idx := runes.IndexAllEx(item, rs, o.cfg.HistorySearchFold) if idx < 0 { continue } @@ -209,6 +225,16 @@ 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() { @@ -218,6 +244,12 @@ // 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 @@ -267,6 +299,8 @@ } func (o *opHistory) Update(s []rune, commit bool) (err error) { + o.fdLock.Lock() + defer o.fdLock.Unlock() s = runes.Copy(s) if o.current == nil { o.Push(s) diff -Nru golang-github-chzyer-readline-1.4/operation.go golang-github-chzyer-readline-1.4+git20171103.a4d5111/operation.go --- golang-github-chzyer-readline-1.4/operation.go 2016-07-26 13:51:17.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/operation.go 2017-11-30 20:35:43.000000000 +0000 @@ -3,6 +3,7 @@ import ( "errors" "io" + "sync" ) var ( @@ -18,6 +19,7 @@ } type Operation struct { + m sync.Mutex cfg *Config t *Terminal buf *RuneBuffer @@ -32,6 +34,10 @@ *opVim } +func (o *Operation) SetBuffer(what string) { + o.buf.Set([]rune(what)) +} + type wrapWriter struct { r *Operation t *Terminal @@ -91,11 +97,27 @@ 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.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,19 +171,30 @@ o.buf.Refresh(nil) } case CharTab: - if o.cfg.AutoComplete == nil { + if o.GetConfig().AutoComplete == nil { o.t.Bell() break } - o.OnComplete() - keepInCompleteMode = true + if o.OnComplete() { + keepInCompleteMode = true + } else { + o.t.Bell() + break + } + case CharBckSearch: - o.SearchMode(S_DIR_BCK) + if !o.SearchMode(S_DIR_BCK) { + o.t.Bell() + break + } keepInSearchMode = true case CharCtrlU: o.buf.KillFront() case CharFwdSearch: - o.SearchMode(S_DIR_FWD) + if !o.SearchMode(S_DIR_FWD) { + o.t.Bell() + break + } keepInSearchMode = true case CharKill: o.buf.Kill() @@ -202,13 +235,15 @@ 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 @@ -217,7 +252,7 @@ data = o.buf.Reset() } o.outchan <- data - if !o.cfg.DisableAutoSaveHistory { + if !o.GetConfig().DisableAutoSaveHistory { // ignore IO error _ = o.history.New(data) } else { @@ -251,14 +286,14 @@ } // 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: @@ -275,12 +310,12 @@ } 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 @@ -299,13 +334,15 @@ } } - 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) @@ -322,15 +359,16 @@ // 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) { @@ -342,9 +380,11 @@ 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 o.t.KickRead() select { @@ -410,6 +450,8 @@ } func (op *Operation) SetConfig(cfg *Config) (*Config, error) { + op.m.Lock() + defer op.m.Unlock() if op.cfg == cfg { return op.cfg, nil } @@ -477,3 +519,13 @@ 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 +} diff -Nru golang-github-chzyer-readline-1.4/password.go golang-github-chzyer-readline-1.4+git20171103.a4d5111/password.go --- golang-github-chzyer-readline-1.4/password.go 2016-07-26 13:51:17.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/password.go 2017-11-30 20:35:43.000000000 +0000 @@ -25,6 +25,7 @@ InterruptPrompt: "\n", EOFPrompt: "\n", HistoryLimit: -1, + Painter: &defaultPainter{}, Stdout: o.o.cfg.Stdout, Stderr: o.o.cfg.Stderr, diff -Nru golang-github-chzyer-readline-1.4/readline.go golang-github-chzyer-readline-1.4+git20171103.a4d5111/readline.go --- golang-github-chzyer-readline-1.4/readline.go 2016-07-26 13:51:17.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/readline.go 2017-11-30 20:35:43.000000000 +0000 @@ -1,3 +1,20 @@ +// Readline is a pure go implementation for GNU-Readline kind library. +// +// example: +// rl, err := readline.New("> ") +// if err != nil { +// panic(err) +// } +// defer rl.Close() +// +// for { +// line, err := rl.Readline() +// if err != nil { // io.EOF +// break +// } +// println(line) +// } +// package readline import "io" @@ -17,6 +34,8 @@ // specify the max length of historys, it's 500 by default, set it to -1 to disable history HistoryLimit int DisableAutoSaveHistory bool + // enable case-insensitive history searching + HistorySearchFold bool // AutoCompleter will called once user press TAB AutoComplete AutoCompleter @@ -25,6 +44,8 @@ // 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 @@ -33,9 +54,10 @@ FuncGetWidth func() int - Stdin io.Reader - Stdout io.Writer - Stderr io.Writer + Stdin io.Reader + StdinWriter io.Writer + Stdout io.Writer + Stderr io.Writer EnableMask bool MaskRune rune @@ -44,6 +66,10 @@ // 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 @@ -70,8 +96,11 @@ } c.inited = true if c.Stdin == nil { - c.Stdin = Stdin + c.Stdin = NewCancelableStdin(Stdin) } + + c.Stdin, c.StdinWriter = NewFillableStdin(c.Stdin) + if c.Stdout == nil { c.Stdout = Stdout } @@ -93,6 +122,9 @@ c.EOFPrompt = "" } + if c.AutoComplete == nil { + c.AutoComplete = &TabCompleter{} + } if c.FuncGetWidth == nil { c.FuncGetWidth = GetScreenWidth } @@ -123,12 +155,19 @@ 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, @@ -216,6 +255,11 @@ 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) } @@ -241,6 +285,20 @@ 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 @@ -255,3 +313,13 @@ 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() +} diff -Nru golang-github-chzyer-readline-1.4/readline_test.go golang-github-chzyer-readline-1.4+git20171103.a4d5111/readline_test.go --- golang-github-chzyer-readline-1.4/readline_test.go 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/readline_test.go 2017-11-30 20:35:43.000000000 +0000 @@ -0,0 +1,27 @@ +package readline + +import ( + "testing" + "time" +) + +func TestRace(t *testing.T) { + rl, err := NewEx(&Config{}) + if err != nil { + t.Fatal(err) + return + } + + go func() { + for range time.Tick(time.Millisecond) { + rl.SetPrompt("hello") + } + }() + + go func() { + time.Sleep(100 * time.Millisecond) + rl.Close() + }() + + rl.Readline() +} diff -Nru golang-github-chzyer-readline-1.4/README.md golang-github-chzyer-readline-1.4+git20171103.a4d5111/README.md --- golang-github-chzyer-readline-1.4/README.md 2016-07-26 13:51:17.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/README.md 2017-11-30 20:35:43.000000000 +0000 @@ -1,238 +1,46 @@ -# readline - -[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE.md) [![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) -[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/chzyer/readline?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![OpenCollective](https://opencollective.com/readline/badge/backers.svg)](#backers) [![OpenCollective](https://opencollective.com/readline/badge/sponsors.svg)](#sponsors) -Readline is A Pure Go Implementation of a libreadline-style Library. -The goal is to be a powerful alternater for GNU-Readline. - - -**WHY:** -Readline will support most of features which GNU Readline is supported, and provide a pure go environment and a MIT license. - -It can also provides shell-like interactives by using [flagly](https://github.com/chzyer/flagly) (demo: [flagly-shell](https://github.com/chzyer/flagly/blob/master/demo/flagly-shell/flagly-shell.go)) +

+ + + +

+ +A powerful readline library in `Linux` `macOS` `Windows` `Solaris` + +## Guide + +* [Demo](example/readline-demo/readline-demo.go) +* [Shortcut](doc/shortcut.md) + +## 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) +[![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) -# Demo - -![demo](https://raw.githubusercontent.com/chzyer/readline/assets/demo.gif) - -Also works fine in windows - -![demo windows](https://raw.githubusercontent.com/chzyer/readline/assets/windows.gif) - - -* [example/readline-demo](https://github.com/chzyer/readline/blob/master/example/readline-demo/readline-demo.go) The source code about the demo above - -* [example/readline-im](https://github.com/chzyer/readline/blob/master/example/readline-im/readline-im.go) Example for how to write a IM program. - -* [example/readline-multiline](https://github.com/chzyer/readline/blob/master/example/readline-multiline/readline-multiline.go) Example for how to parse command which can submit by multiple time. - -* [example/readline-pass-strength](https://github.com/chzyer/readline/blob/master/example/readline-pass-strength/readline-pass-strength.go) A example about checking password strength, written by [@sahib](https://github.com/sahib) - -# Todo -* Vi Mode is not completely finish -* More funny examples -* Support dumb/eterm-color terminal in emacs - -# Features -* Support emacs/vi mode, almost all basic features that GNU-Readline is supported -* zsh-style backward/forward history search -* zsh-style completion -* Readline auto refresh when others write to Stdout while editing (it needs specify the Stdout/Stderr provided by *readline.Instance to others). -* Support colourful prompt in all platforms. - -# Usage - -* Import package - -``` -go get gopkg.in/readline.v1 -``` - -or - -``` -go get github.com/chzyer/readline -``` - -* Simplest example - -```go -import "gopkg.in/readline.v1" - -rl, err := readline.New("> ") -if err != nil { - panic(err) -} -defer rl.Close() - -for { - line, err := rl.Readline() - if err != nil { // io.EOF, readline.ErrInterrupt - break - } - println(line) -} -``` - -* Example with durable history - -```go -rl, err := readline.NewEx(&readline.Config{ - Prompt: "> ", - HistoryFile: "/tmp/readline.tmp", -}) -if err != nil { - panic(err) -} -defer rl.Close() - -for { - line, err := rl.Readline() - if err != nil { // io.EOF, readline.ErrInterrupt - break - } - println(line) -} -``` - -* Example with auto completion - -```go -import ( - "gopkg.in/readline.v1" -) - -var completer = readline.NewPrefixCompleter( - readline.PcItem("say", - readline.PcItem("hello"), - readline.PcItem("bye"), - ), - readline.PcItem("help"), -) - -rl, err := readline.NewEx(&readline.Config{ - Prompt: "> ", - AutoComplete: completer, -}) -if err != nil { - panic(err) -} -defer rl.Close() - -for { - line, err := rl.Readline() - if err != nil { // io.EOF, readline.ErrInterrupt - break - } - println(line) -} -``` - - -# Shortcut - -`Meta`+`B` means press `Esc` and `n` separately. -Users can change that in terminal simulator(i.e. iTerm2) to `Alt`+`B` -Notice: `Meta`+`B` is equals with `Alt`+`B` in windows. - -* Shortcut in normal mode - -| Shortcut | Comment | -|--------------------|------------------------------------------| -| `Ctrl`+`A` | Beginning of line | -| `Ctrl`+`B` / `←` | Backward one character | -| `Meta`+`B` | Backward one word | -| `Ctrl`+`C` | Send io.EOF | -| `Ctrl`+`D` | Delete one character | -| `Meta`+`D` | Delete one word | -| `Ctrl`+`E` | End of line | -| `Ctrl`+`F` / `→` | Forward one character | -| `Meta`+`F` | Forward one word | -| `Ctrl`+`G` | Cancel | -| `Ctrl`+`H` | Delete previous character | -| `Ctrl`+`I` / `Tab` | Command line completion | -| `Ctrl`+`J` | Line feed | -| `Ctrl`+`K` | Cut text to the end of line | -| `Ctrl`+`L` | Clear screen | -| `Ctrl`+`M` | Same as Enter key | -| `Ctrl`+`N` / `↓` | Next line (in history) | -| `Ctrl`+`P` / `↑` | Prev line (in history) | -| `Ctrl`+`R` | Search backwards in history | -| `Ctrl`+`S` | Search forwards in history | -| `Ctrl`+`T` | Transpose characters | -| `Meta`+`T` | Transpose words (TODO) | -| `Ctrl`+`U` | Cut text to the beginning of line | -| `Ctrl`+`W` | Cut previous word | -| `Backspace` | Delete previous character | -| `Meta`+`Backspace` | Cut previous word | -| `Enter` | Line feed | - - -* Shortcut in Search Mode (`Ctrl`+`S` or `Ctrl`+`r` to enter this mode) - -| Shortcut | Comment | -|-------------------------|---------------------------------------------| -| `Ctrl`+`S` | Search forwards in history | -| `Ctrl`+`R` | Search backwards in history | -| `Ctrl`+`C` / `Ctrl`+`G` | Exit Search Mode and revert the history | -| `Backspace` | Delete previous character | -| Other | Exit Search Mode | - -* Shortcut in Complete Select Mode (double `Tab` to enter this mode) - -| Shortcut | Comment | -|-------------------------|---------------------------------------------| -| `Ctrl`+`F` | Move Forward | -| `Ctrl`+`B` | Move Backward | -| `Ctrl`+`N` | Move to next line | -| `Ctrl`+`P` | Move to previous line | -| `Ctrl`+`A` | Move to the first candicate in current line | -| `Ctrl`+`E` | Move to the last candicate in current line | -| `Tab` / `Enter` | Use the word on cursor to complete | -| `Ctrl`+`C` / `Ctrl`+`G` | Exit Complete Select Mode | -| Other | Exit Complete Select Mode | - -# Tested with - -| Environment | $TERM | -|-------------------------------|--------| -| Mac OS X iTerm2 | xterm | -| Mac OS X default Terminal.app | xterm | -| Mac OS X iTerm2 Screen | screen | -| Mac OS X iTerm2 Tmux | screen | -| Ubuntu Server 14.04 LTS | linux | -| Centos 7 | linux | -| Windows 10 | - | - -### Notice: -* `Ctrl`+`A` is not working in `screen` because it used as a control command by default - -If you test it otherwhere, whether it works fine or not, please let me know! - -## Who is using Readline - -* [cockroachdb/cockroach](https://github.com/cockroachdb/cockroach) -* [youtube/doorman](https://github.com/youtube/doorman) -* [bom-d-van/harp](https://github.com/bom-d-van/harp) -* [abiosoft/ishell](https://github.com/abiosoft/ishell) -* [robertkrimen/otto](https://github.com/robertkrimen/otto) -* [Netflix/hal-9001](https://github.com/Netflix/hal-9001) -* [docker/go-p9p](https://github.com/docker/go-p9p) -# Feedback +## Feedback If you have any questions, please submit a github issue and any pull requests is welcomed :) -* [https://twitter.com/chzyer](https://twitter.com/chzyer) -* [http://weibo.com/2145262190](http://weibo.com/2145262190) +* [https://twitter.com/chzyer](https://twitter.com/chzyer) +* [http://weibo.com/2145262190](http://weibo.com/2145262190) -# Backers +## Backers Love Readline? Help me keep it alive by donating funds to cover project expenses!
[[Become a backer](https://opencollective.com/readline#backer)] @@ -269,7 +77,7 @@ -# Sponsors +## Sponsors Become a sponsor and get your logo here on our Github page. [[Become a sponsor](https://opencollective.com/readline#sponsor)] diff -Nru golang-github-chzyer-readline-1.4/runebuf.go golang-github-chzyer-readline-1.4+git20171103.a4d5111/runebuf.go --- golang-github-chzyer-readline-1.4/runebuf.go 2016-07-26 13:51:17.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/runebuf.go 2017-11-30 20:35:43.000000000 +0000 @@ -4,7 +4,9 @@ "bufio" "bytes" "io" + "strconv" "strings" + "sync" ) type runeBufferBck struct { @@ -25,14 +27,28 @@ width int bck *runeBufferBck + + 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 + r.Unlock() } func (r *RuneBuffer) Backup() { + r.Lock() r.bck = &runeBufferBck{r.buf, r.idx} + r.Unlock() } func (r *RuneBuffer) Restore() { @@ -57,23 +73,39 @@ } func (r *RuneBuffer) SetConfig(cfg *Config) { + r.Lock() r.cfg = cfg r.interactive = cfg.useInteractive() + r.Unlock() } func (r *RuneBuffer) SetMask(m rune) { + r.Lock() r.cfg.MaskRune = m + r.Unlock() } func (r *RuneBuffer) CurrentWidth(x int) int { + r.Lock() + defer r.Unlock() return runes.WidthAll(r.buf[:x]) } func (r *RuneBuffer) PromptLen() int { + r.Lock() + width := r.promptLen() + r.Unlock() + return width +} + +func (r *RuneBuffer) promptLen() int { return runes.WidthAll(runes.ColorFilter(r.prompt)) } func (r *RuneBuffer) RuneSlice(i int) []rune { + r.Lock() + defer r.Unlock() + if i > 0 { rs := make([]rune, i) copy(rs, r.buf[r.idx:r.idx+i]) @@ -85,16 +117,22 @@ } func (r *RuneBuffer) Runes() []rune { + r.Lock() newr := make([]rune, len(r.buf)) copy(newr, r.buf) + r.Unlock() return newr } func (r *RuneBuffer) Pos() int { + r.Lock() + defer r.Unlock() return r.idx } func (r *RuneBuffer) Len() int { + r.Lock() + defer r.Unlock() return len(r.buf) } @@ -142,6 +180,8 @@ } func (r *RuneBuffer) IsCursorInEnd() bool { + r.Lock() + defer r.Unlock() return r.idx == len(r.buf) } @@ -154,6 +194,7 @@ func (r *RuneBuffer) Erase() { r.Refresh(func() { r.idx = 0 + r.pushKill(r.buf[:]) r.buf = r.buf[:0] }) } @@ -163,6 +204,7 @@ 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 }) @@ -179,6 +221,7 @@ } 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:]...) }) @@ -214,6 +257,7 @@ } 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] @@ -222,6 +266,7 @@ func (r *RuneBuffer) Kill() { r.Refresh(func() { + r.pushKill(r.buf[r.idx:]) r.buf = r.buf[:r.idx] }) } @@ -288,6 +333,7 @@ } 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 @@ -299,6 +345,20 @@ }) } +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 { @@ -366,10 +426,19 @@ } func (r *RuneBuffer) getSplitByLine(rs []rune) []string { - return SplitByLine(r.PromptLen(), r.width, rs) + return SplitByLine(r.promptLen(), r.width, rs) } func (r *RuneBuffer) IdxLine(width int) int { + r.Lock() + defer r.Unlock() + return r.idxLine(width) +} + +func (r *RuneBuffer) idxLine(width int) int { + if width == 0 { + return 0 + } sp := r.getSplitByLine(r.buf[:r.idx]) return len(sp) - 1 } @@ -379,19 +448,29 @@ } func (r *RuneBuffer) Refresh(f func()) { + r.Lock() + defer r.Unlock() + if !r.interactive { if f != nil { f() } return } - r.Clean() + + r.clean() if f != nil { f() } r.print() } +func (r *RuneBuffer) SetOffset(offset string) { + r.Lock() + r.offset = offset + r.Unlock() +} + func (r *RuneBuffer) print() { r.w.Write(r.output()) r.hadClean = false @@ -408,22 +487,58 @@ 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 { - buf.Write([]byte(string(r.buf))) + for _, e := range r.cfg.Painter.Paint(r.buf, r.idx) { + if e == '\t' { + buf.WriteString(strings.Repeat(" ", TabWidth)) + } else { + 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] @@ -468,30 +583,44 @@ } func (r *RuneBuffer) SetPrompt(prompt string) { + r.Lock() r.prompt = []rune(prompt) + r.Unlock() } func (r *RuneBuffer) cleanOutput(w io.Writer, idxLine int) { buf := bufio.NewWriter(w) - buf.Write([]byte("\033[J")) // just like ^k :) - if idxLine == 0 { - io.WriteString(buf, "\033[2K\r") + if r.width == 0 { + buf.WriteString(strings.Repeat("\r\b", len(r.buf)+r.promptLen())) + buf.Write([]byte("\033[J")) } else { - for i := 0; i < idxLine; i++ { - io.WriteString(buf, "\033[2K\r\033[A") + buf.Write([]byte("\033[J")) // just like ^k :) + if idxLine == 0 { + buf.WriteString("\033[2K") + buf.WriteString("\r") + } else { + for i := 0; i < idxLine; i++ { + io.WriteString(buf, "\033[2K\r\033[A") + } + io.WriteString(buf, "\033[2K\r") } - io.WriteString(buf, "\033[2K\r") } buf.Flush() return } func (r *RuneBuffer) Clean() { - r.clean(r.IdxLine(r.width)) + r.Lock() + r.clean() + r.Unlock() +} + +func (r *RuneBuffer) clean() { + r.cleanWithIdxLine(r.idxLine(r.width)) } -func (r *RuneBuffer) clean(idxLine int) { +func (r *RuneBuffer) cleanWithIdxLine(idxLine int) { if r.hadClean || !r.interactive { return } diff -Nru golang-github-chzyer-readline-1.4/runes.go golang-github-chzyer-readline-1.4+git20171103.a4d5111/runes.go --- golang-github-chzyer-readline-1.4/runes.go 2016-07-26 13:51:17.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/runes.go 2017-11-30 20:35:43.000000000 +0000 @@ -3,12 +3,50 @@ import ( "bytes" "unicode" + "unicode/utf8" ) var runes = Runes{} +var TabWidth = 4 type Runes struct{} +func (Runes) EqualRune(a, b rune, fold bool) bool { + if a == b { + return true + } + if !fold { + return false + } + if a > b { + a, b = b, a + } + if b < utf8.RuneSelf && 'A' <= a && a <= 'Z' { + if b == a+'a'-'A' { + return true + } + } + return false +} + +func (r Runes) EqualRuneFold(a, b rune) bool { + return r.EqualRune(a, b, true) +} + +func (r Runes) EqualFold(a, b []rune) bool { + if len(a) != len(b) { + return false + } + for i := 0; i < len(a); i++ { + if r.EqualRuneFold(a[i], b[i]) { + continue + } + return false + } + + return true +} + func (Runes) Equal(a, b []rune) bool { if len(a) != len(b) { return false @@ -21,12 +59,11 @@ return true } -// Search in runes from end to front -func (Runes) IndexAllBck(r, sub []rune) int { +func (rs Runes) IndexAllBckEx(r, sub []rune, fold bool) int { for i := len(r) - len(sub); i >= 0; i-- { found := true for j := 0; j < len(sub); j++ { - if r[i+j] != sub[j] { + if !rs.EqualRune(r[i+j], sub[j], fold) { found = false break } @@ -38,15 +75,24 @@ return -1 } +// Search in runes from end to front +func (rs Runes) IndexAllBck(r, sub []rune) int { + return rs.IndexAllBckEx(r, sub, false) +} + // Search in runes from front to end -func (Runes) IndexAll(r, sub []rune) int { +func (rs Runes) IndexAll(r, sub []rune) int { + return rs.IndexAllEx(r, sub, false) +} + +func (rs Runes) IndexAllEx(r, sub []rune, fold bool) int { for i := 0; i < len(r); i++ { found := true if len(r[i:]) < len(sub) { return -1 } for j := 0; j < len(sub); j++ { - if r[i+j] != sub[j] { + if !rs.EqualRune(r[i+j], sub[j], fold) { found = false break } @@ -98,6 +144,9 @@ } func (Runes) Width(r rune) int { + if r == '\t' { + return TabWidth + } if unicode.IsOneOf(zeroWidth, r) { return 0 } @@ -124,6 +173,13 @@ return n } +func (Runes) HasPrefixFold(r, prefix []rune) bool { + if len(r) < len(prefix) { + return false + } + return runes.EqualFold(r[:len(prefix)], prefix) +} + func (Runes) HasPrefix(r, prefix []rune) bool { if len(r) < len(prefix) { return false diff -Nru golang-github-chzyer-readline-1.4/search.go golang-github-chzyer-readline-1.4+git20171103.a4d5111/search.go --- golang-github-chzyer-readline-1.4/search.go 2016-07-26 13:51:17.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/search.go 2017-11-30 20:35:43.000000000 +0000 @@ -96,7 +96,10 @@ o.search(true) } -func (o *opSearch) SearchMode(dir int) { +func (o *opSearch) SearchMode(dir int) bool { + if o.width == 0 { + return false + } alreadyInMode := o.inMode o.inMode = true o.dir = dir @@ -106,6 +109,7 @@ } else { o.SearchRefresh(-1) } + return true } func (o *opSearch) ExitSearchMode(revert bool) { diff -Nru golang-github-chzyer-readline-1.4/std.go golang-github-chzyer-readline-1.4+git20171103.a4d5111/std.go --- golang-github-chzyer-readline-1.4/std.go 2016-07-26 13:51:17.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/std.go 2017-11-30 20:35:43.000000000 +0000 @@ -4,6 +4,7 @@ "io" "os" "sync" + "sync/atomic" ) var ( @@ -64,3 +65,126 @@ ins.SetPrompt(prompt) return ins.Readline() } + +type CancelableStdin struct { + r io.Reader + mutex sync.Mutex + stop chan struct{} + closed int32 + notify chan struct{} + data []byte + read int + err error +} + +func NewCancelableStdin(r io.Reader) *CancelableStdin { + c := &CancelableStdin{ + r: r, + notify: make(chan struct{}), + stop: make(chan struct{}), + } + go c.ioloop() + return c +} + +func (c *CancelableStdin) ioloop() { +loop: + for { + select { + case <-c.notify: + c.read, c.err = c.r.Read(c.data) + select { + case c.notify <- struct{}{}: + case <-c.stop: + break loop + } + case <-c.stop: + break 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 + select { + case c.notify <- struct{}{}: + case <-c.stop: + return 0, io.EOF + } + select { + case <-c.notify: + return c.read, c.err + case <-c.stop: + return 0, io.EOF + } +} + +func (c *CancelableStdin) Close() error { + 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.Reader + buf []byte + bufErr error +} + +// NewFillableStdin gives you FillableStdin +func NewFillableStdin(stdin io.Reader) (io.Reader, 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) + 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() + + return s.stdin.Read(p) + +} diff -Nru golang-github-chzyer-readline-1.4/term_bsd.go golang-github-chzyer-readline-1.4+git20171103.a4d5111/term_bsd.go --- golang-github-chzyer-readline-1.4/term_bsd.go 2016-07-26 13:51:17.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/term_bsd.go 2017-11-30 20:35:43.000000000 +0000 @@ -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 +} diff -Nru golang-github-chzyer-readline-1.4/term.go golang-github-chzyer-readline-1.4+git20171103.a4d5111/term.go --- golang-github-chzyer-readline-1.4/term.go 2016-07-26 13:51:17.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/term.go 2017-11-30 20:35:43.000000000 +0000 @@ -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 darwin dragonfly freebsd linux,!appengine netbsd openbsd solaris // Package terminal provides support functions for dealing with terminals, as // commonly found on UNIX systems. @@ -19,19 +19,17 @@ 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 @@ // 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 @@ 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 @@ 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 diff -Nru golang-github-chzyer-readline-1.4/terminal.go golang-github-chzyer-readline-1.4+git20171103.a4d5111/terminal.go --- golang-github-chzyer-readline-1.4/terminal.go 2016-07-26 13:51:17.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/terminal.go 2017-11-30 20:35:43.000000000 +0000 @@ -3,12 +3,14 @@ import ( "bufio" "fmt" + "io" "strings" "sync" "sync/atomic" ) type Terminal struct { + m sync.Mutex cfg *Config outchan chan rune closed int32 @@ -17,6 +19,8 @@ wg sync.WaitGroup isReading int32 sleeping int32 + + sizeChan chan string } func NewTerminal(cfg *Config) (*Terminal, error) { @@ -28,6 +32,7 @@ kickChan: make(chan struct{}, 1), outchan: make(chan rune), stopChan: make(chan struct{}, 1), + sizeChan: make(chan string, 1), } go t.ioloop() @@ -60,6 +65,24 @@ 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 +} + +func (t *Terminal) GetOffset(f func(offset string)) { + go func() { + f(<-t.sizeChan) + }() + t.Write([]byte("\033[6n")) +} + func (t *Terminal) Print(s string) { fmt.Fprintf(t.cfg.Stdout, "%s", s) } @@ -94,14 +117,18 @@ func (t *Terminal) ioloop() { t.wg.Add(1) - defer t.wg.Done() + defer func() { + t.wg.Done() + close(t.outchan) + }() + var ( isEscape bool isEscapeEx bool expectNextChar bool ) - buf := bufio.NewReader(t.cfg.Stdin) + buf := bufio.NewReader(t.getStdin()) for { if !expectNextChar { atomic.StoreInt32(&t.isReading, 0) @@ -132,7 +159,24 @@ r = escapeKey(r, buf) } else if isEscapeEx { isEscapeEx = false - r = escapeExKey(r, buf) + if key := readEscKey(r, buf); key != nil { + r = escapeExKey(key) + // offset + if key.typ == 'R' { + if _, _, ok := key.Get2(); ok { + select { + case t.sizeChan <- key.attr: + default: + } + } + expectNextChar = true + continue + } + } + if r == 0 { + expectNextChar = true + continue + } } expectNextChar = true @@ -150,7 +194,7 @@ t.outchan <- r } } - close(t.outchan) + } func (t *Terminal) Bell() { @@ -161,15 +205,34 @@ if atomic.SwapInt32(&t.closed, 1) != 0 { return nil } - t.stopChan <- struct{}{} + if closer, ok := t.cfg.Stdin.(io.Closer); ok { + closer.Close() + } + close(t.stopChan) t.wg.Wait() 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 } diff -Nru golang-github-chzyer-readline-1.4/term_linux.go golang-github-chzyer-readline-1.4+git20171103.a4d5111/term_linux.go --- golang-github-chzyer-readline-1.4/term_linux.go 2016-07-26 13:51:17.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/term_linux.go 2017-11-30 20:35:43.000000000 +0000 @@ -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 +} diff -Nru golang-github-chzyer-readline-1.4/term_solaris.go golang-github-chzyer-readline-1.4+git20171103.a4d5111/term_solaris.go --- golang-github-chzyer-readline-1.4/term_solaris.go 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/term_solaris.go 2017-11-30 20:35:43.000000000 +0000 @@ -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 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)) +} diff -Nru golang-github-chzyer-readline-1.4/term_unix.go golang-github-chzyer-readline-1.4+git20171103.a4d5111/term_unix.go --- golang-github-chzyer-readline-1.4/term_unix.go 1970-01-01 00:00:00.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/term_unix.go 2017-11-30 20:35:43.000000000 +0000 @@ -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 +} diff -Nru golang-github-chzyer-readline-1.4/.travis.yml golang-github-chzyer-readline-1.4+git20171103.a4d5111/.travis.yml --- golang-github-chzyer-readline-1.4/.travis.yml 2016-07-26 13:51:17.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/.travis.yml 2017-11-30 20:35:43.000000000 +0000 @@ -1,10 +1,8 @@ language: go go: - - 1.5 -before_install: - - go get golang.org/x/crypto/ssh/terminal + - 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/... - - go test -v + - go test -race -v diff -Nru golang-github-chzyer-readline-1.4/utils.go golang-github-chzyer-readline-1.4+git20171103.a4d5111/utils.go --- golang-github-chzyer-readline-1.4/utils.go 2016-07-26 13:51:17.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/utils.go 2017-11-30 20:35:43.000000000 +0000 @@ -3,15 +3,56 @@ import ( "bufio" "bytes" + "container/list" + "fmt" + "os" "strconv" + "strings" "sync" "time" + "unicode" ) var ( isWindows = false ) +const ( + CharLineStart = 1 + CharBackward = 2 + CharInterrupt = 3 + CharDelete = 4 + CharLineEnd = 5 + CharForward = 6 + CharBell = 7 + CharCtrlH = 8 + CharTab = 9 + CharCtrlJ = 10 + CharKill = 11 + CharCtrlL = 12 + CharEnter = 13 + CharNext = 14 + CharPrev = 16 + CharBckSearch = 18 + CharFwdSearch = 19 + CharTranspose = 20 + CharCtrlU = 21 + CharCtrlW = 23 + CharCtrlY = 25 + CharCtrlZ = 26 + CharEsc = 27 + CharEscapeEx = 91 + CharBackspace = 127 +) + +const ( + MetaBackward rune = -iota - 1 + MetaForward + MetaDelete + MetaBackspace + MetaTranspose +) + // WaitForResume need to call before current process got suspend. // It will run a ticker until a long duration is occurs, // which means this process is resumed. @@ -42,7 +83,9 @@ if err != nil { // errno 0 means everything is ok :) if err.Error() == "errno 0" { - err = nil + return nil + } else { + return err } } return nil @@ -54,8 +97,9 @@ } // translate Esc[X -func escapeExKey(r rune, reader *bufio.Reader) rune { - switch r { +func escapeExKey(key *escapeKeyPair) rune { + var r rune + switch key.typ { case 'D': r = CharBackward case 'C': @@ -68,19 +112,53 @@ r = CharLineStart case 'F': r = CharLineEnd - default: - if r == '3' && reader != nil { - d, _, _ := reader.ReadRune() - if d == '~' { - r = CharDelete - } else { - reader.UnreadRune() - } + case '~': + if key.attr == "3" { + r = CharDelete } + default: } return r } +type escapeKeyPair struct { + attr string + typ rune +} + +func (e *escapeKeyPair) Get2() (int, int, bool) { + sp := strings.Split(e.attr, ";") + if len(sp) < 2 { + return -1, -1, false + } + s1, err := strconv.Atoi(sp[0]) + if err != nil { + return -1, -1, false + } + s2, err := strconv.Atoi(sp[1]) + if err != nil { + return -1, -1, false + } + return s1, s2, true +} + +func readEscKey(r rune, reader *bufio.Reader) *escapeKeyPair { + p := escapeKeyPair{} + buf := bytes.NewBuffer(nil) + for { + if r == ';' { + } else if unicode.IsNumber(r) { + } else { + p.typ = r + break + } + buf.WriteRune(r) + r, _, _ = reader.ReadRune() + } + p.attr = buf.String() + return &p +} + // translate EscX to Meta+X func escapeKey(r rune, reader *bufio.Reader) rune { switch r { @@ -174,3 +252,26 @@ } return Restore(GetStdin(), r.state) } + +// ----------------------------------------------------------------------------- + +func sleep(n int) { + Debug(n) + time.Sleep(2000 * time.Millisecond) +} + +// print a linked list to Debug() +func debugList(l *list.List) { + idx := 0 + for e := l.Front(); e != nil; e = e.Next() { + Debug(idx, fmt.Sprintf("%+v", e.Value)) + idx++ + } +} + +// append log info to another file +func Debug(o ...interface{}) { + f, _ := os.OpenFile("debug.tmp", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + fmt.Fprintln(f, o...) + f.Close() +} diff -Nru golang-github-chzyer-readline-1.4/utils_unix.go golang-github-chzyer-readline-1.4+git20171103.a4d5111/utils_unix.go --- golang-github-chzyer-readline-1.4/utils_unix.go 2016-07-26 13:51:17.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/utils_unix.go 2017-11-30 20:35:43.000000000 +0000 @@ -1,4 +1,4 @@ -// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd +// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd solaris package readline @@ -8,7 +8,6 @@ "os/signal" "sync" "syscall" - "unsafe" ) type winsize struct { @@ -30,17 +29,11 @@ // 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 { diff -Nru golang-github-chzyer-readline-1.4/vim.go golang-github-chzyer-readline-1.4+git20171103.a4d5111/vim.go --- golang-github-chzyer-readline-1.4/vim.go 2016-07-26 13:51:17.000000000 +0000 +++ golang-github-chzyer-readline-1.4+git20171103.a4d5111/vim.go 2017-11-30 20:35:43.000000000 +0000 @@ -72,6 +72,8 @@ case 'l': rb.Delete() } + case 'p': + rb.Yank() case 'b', 'B': rb.MoveToPrevWord() case 'w', 'W':