pax_global_header00006660000000000000000000000064141713765010014517gustar00rootroot0000000000000052 comment=692d84ca36e284454768f4c9223bf396ab9de4fd golang-github-wader-readline-0.0~git20220118.692d84c/000077500000000000000000000000001417137650100215125ustar00rootroot00000000000000golang-github-wader-readline-0.0~git20220118.692d84c/.gitignore000066400000000000000000000000121417137650100234730ustar00rootroot00000000000000.vscode/* golang-github-wader-readline-0.0~git20220118.692d84c/.travis.yml000066400000000000000000000003741417137650100236270ustar00rootroot00000000000000language: go go: - 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 -race -v golang-github-wader-readline-0.0~git20220118.692d84c/CHANGELOG.md000066400000000000000000000057271417137650100233360ustar00rootroot00000000000000# ChangeLog ### 1.4 - 2016-07-25 * [#60][60] Support dynamic autocompletion * Fix ANSI parser on Windows * Fix wrong column width in complete mode on Windows * Remove dependent package "golang.org/x/crypto/ssh/terminal" ### 1.3 - 2016-05-09 * [#38][38] add SetChildren for prefix completer interface * [#42][42] improve multiple lines compatibility * [#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 * [#53][53] Fix bug which causes integer divide by zero panicking when input buffer is empty ### 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) * [#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. * 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. * [#28][28], fixes the history is not working as expected. * [#33][33], vim mode now support `c`, `d`, `x (delete character)`, `r (replace character)` ### 1.1 - 2015-11-20 * [#12][12] Add support for key ``/``/`` * Only enter raw mode as needed (calling `Readline()`), program will receive signal(e.g. Ctrl+C) if not interact with `readline`. * Bugs fixed for `PrefixCompleter` * Press `Ctrl+D` in empty line will cause `io.EOF` in error, Press `Ctrl+C` in anytime will cause `ErrInterrupt` instead of `io.EOF`, this will privodes a shell-like user experience. * Customable Interrupt/EOF prompt in `Config` * [#17][17] Change atomic package to use 32bit function to let it runnable on arm 32bit devices * Provides a new password user experience(`readline.ReadPasswordEx()`). ### 1.0 - 2015-10-14 * 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 golang-github-wader-readline-0.0~git20220118.692d84c/LICENSE000066400000000000000000000020621417137650100225170ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2015 Chzyer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. golang-github-wader-readline-0.0~git20220118.692d84c/README.md000066400000000000000000000276661417137650100230120ustar00rootroot00000000000000[![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) [![OpenCollective](https://opencollective.com/readline/badge/backers.svg)](#backers) [![OpenCollective](https://opencollective.com/readline/badge/sponsors.svg)](#sponsors)

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) ## 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) ## Backers Love Readline? Help me keep it alive by donating funds to cover project expenses!
[[Become a backer](https://opencollective.com/readline#backer)] ## Sponsors Become a sponsor and get your logo here on our Github page. [[Become a sponsor](https://opencollective.com/readline#sponsor)] golang-github-wader-readline-0.0~git20220118.692d84c/ansi_windows.go000066400000000000000000000124101417137650100245430ustar00rootroot00000000000000// +build windows package readline import ( "bufio" "io" "strconv" "strings" "sync" "unicode/utf8" "unsafe" ) const ( _ = uint16(0) COLOR_FBLUE = 0x0001 COLOR_FGREEN = 0x0002 COLOR_FRED = 0x0004 COLOR_FINTENSITY = 0x0008 COLOR_BBLUE = 0x0010 COLOR_BGREEN = 0x0020 COLOR_BRED = 0x0040 COLOR_BINTENSITY = 0x0080 COMMON_LVB_UNDERSCORE = 0x8000 COMMON_LVB_BOLD = 0x0007 ) var ColorTableFg = []word{ 0, // 30: Black COLOR_FRED, // 31: Red COLOR_FGREEN, // 32: Green COLOR_FRED | COLOR_FGREEN, // 33: Yellow COLOR_FBLUE, // 34: Blue COLOR_FRED | COLOR_FBLUE, // 35: Magenta COLOR_FGREEN | COLOR_FBLUE, // 36: Cyan COLOR_FRED | COLOR_FBLUE | COLOR_FGREEN, // 37: White } var ColorTableBg = []word{ 0, // 40: Black COLOR_BRED, // 41: Red COLOR_BGREEN, // 42: Green COLOR_BRED | COLOR_BGREEN, // 43: Yellow COLOR_BBLUE, // 44: Blue COLOR_BRED | COLOR_BBLUE, // 45: Magenta COLOR_BGREEN | COLOR_BBLUE, // 46: Cyan COLOR_BRED | COLOR_BBLUE | COLOR_BGREEN, // 47: White } type ANSIWriter struct { target io.Writer wg sync.WaitGroup ctx *ANSIWriterCtx sync.Mutex } func NewANSIWriter(w io.Writer) *ANSIWriter { a := &ANSIWriter{ target: w, ctx: NewANSIWriterCtx(w), } return a } func (a *ANSIWriter) Close() error { a.wg.Wait() return nil } type ANSIWriterCtx struct { isEsc bool isEscSeq bool arg []string target *bufio.Writer wantFlush bool } func NewANSIWriterCtx(target io.Writer) *ANSIWriterCtx { return &ANSIWriterCtx{ target: bufio.NewWriter(target), } } func (a *ANSIWriterCtx) Flush() { a.target.Flush() } func (a *ANSIWriterCtx) process(r rune) bool { if a.wantFlush { if r == 0 || r == CharEsc { a.wantFlush = false a.target.Flush() } } if a.isEscSeq { a.isEscSeq = a.ioloopEscSeq(a.target, r, &a.arg) return true } switch r { case CharEsc: a.isEsc = true case '[': if a.isEsc { a.arg = nil a.isEscSeq = true a.isEsc = false break } fallthrough default: a.target.WriteRune(r) a.wantFlush = true } return true } func (a *ANSIWriterCtx) ioloopEscSeq(w *bufio.Writer, r rune, argptr *[]string) bool { arg := *argptr var err error if r >= 'A' && r <= 'D' { count := short(GetInt(arg, 1)) info, err := GetConsoleScreenBufferInfo() if err != nil { return false } switch r { case 'A': // up info.dwCursorPosition.y -= count case 'B': // down info.dwCursorPosition.y += count case 'C': // right info.dwCursorPosition.x += count case 'D': // left info.dwCursorPosition.x -= count } SetConsoleCursorPosition(&info.dwCursorPosition) return false } switch r { case 'J': killLines() case 'K': eraseLine() case 'm': color := word(0x7) for _, item := range arg { var c int c, err = strconv.Atoi(item) if err != nil { w.WriteString("[" + strings.Join(arg, ";") + "m") break } switch { case c == 1: color |= COLOR_FINTENSITY // Add on 01 case c >= 30 && c <= 37: c -= 30 bits := ((c & 0x1) << 2) | c&0x2 | ((c & 0x4) >> 2) // swap bit 1 and 3, Invert red blue color = color&0xFFF8 | word(bits) case c == 39: // default foreground color color = COLOR_FRED | COLOR_FBLUE | COLOR_FGREEN case c >= 40 && c <= 47: c -= 40 bits := ((c & 0x1) << 2) | c&0x2 | ((c & 0x4) >> 2) // swap bit 1 and 3, Invert red blue color = color&0xFF8F | (word(bits << 4)) case c == 4: color |= COMMON_LVB_UNDERSCORE | 0x7 default: color = 0x07 } } if err != nil { break } kernel.SetConsoleTextAttribute(stdout, uintptr(color)) case '\007': // set title case ';': if len(arg) == 0 || arg[len(arg)-1] != "" { arg = append(arg, "") *argptr = arg } return true default: if len(arg) == 0 { arg = append(arg, "") } arg[len(arg)-1] += string(r) *argptr = arg return true } *argptr = nil return false } func (a *ANSIWriter) Write(b []byte) (int, error) { a.Lock() defer a.Unlock() off := 0 for len(b) > off { r, size := utf8.DecodeRune(b[off:]) if size == 0 { return off, io.ErrShortWrite } off += size a.ctx.process(r) } a.ctx.Flush() return off, nil } func killLines() error { sbi, err := GetConsoleScreenBufferInfo() if err != nil { return err } size := (sbi.dwCursorPosition.y - sbi.dwSize.y) * sbi.dwSize.x size += sbi.dwCursorPosition.x var written int kernel.FillConsoleOutputAttribute(stdout, uintptr(ColorTableFg[7]), uintptr(size), sbi.dwCursorPosition.ptr(), uintptr(unsafe.Pointer(&written)), ) return kernel.FillConsoleOutputCharacterW(stdout, uintptr(' '), uintptr(size), sbi.dwCursorPosition.ptr(), uintptr(unsafe.Pointer(&written)), ) } func eraseLine() error { sbi, err := GetConsoleScreenBufferInfo() if err != nil { return err } size := sbi.dwSize.x sbi.dwCursorPosition.x = 0 var written int return kernel.FillConsoleOutputCharacterW(stdout, uintptr(' '), uintptr(size), sbi.dwCursorPosition.ptr(), uintptr(unsafe.Pointer(&written)), ) } golang-github-wader-readline-0.0~git20220118.692d84c/complete.go000066400000000000000000000141361417137650100236560ustar00rootroot00000000000000package readline import ( "bufio" "bytes" "fmt" "io" ) type AutoCompleter interface { // Readline will pass the whole line and current offset to it // Completer need to pass all the candidates, and how long they shared the same characters in line // Example: // [go, git, git-shell, grep] // Do("g", 1) => ["o", "it", "it-shell", "rep"], 1 // Do("gi", 2) => ["t", "t-shell"], 2 // Do("git", 3) => ["", "-shell"], 3 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 width int inCompleteMode bool inSelectMode bool candidate [][]rune candidateSource []rune candidateOff int candidateChoise int candidateColNum int } func newOpCompleter(w io.Writer, op *Operation, width int) *opCompleter { return &opCompleter{ w: w, op: op, width: width, } } func (o *opCompleter) doSelect() { if len(o.candidate) == 1 { o.op.buf.WriteRunes(o.candidate[0]) o.ExitCompleteMode(false) return } o.nextCandidate(1) o.CompleteRefresh() } func (o *opCompleter) nextCandidate(i int) { o.candidateChoise += i o.candidateChoise = o.candidateChoise % len(o.candidate) if o.candidateChoise < 0 { o.candidateChoise = len(o.candidate) + o.candidateChoise } } func (o *opCompleter) OnComplete() bool { if o.width == 0 { return false } if o.IsInCompleteSelectMode() { o.doSelect() return true } buf := o.op.buf rs := buf.Runes() if o.IsInCompleteMode() && o.candidateSource != nil && runes.Equal(rs, o.candidateSource) { o.EnterCompleteSelectMode() o.doSelect() return true } o.ExitCompleteSelectMode() o.candidateSource = rs newLines, offset := o.op.cfg.AutoComplete.Do(rs, buf.idx) if len(newLines) == 0 { o.ExitCompleteMode(false) return true } // only Aggregate candidates in non-complete mode if !o.IsInCompleteMode() { if len(newLines) == 1 { buf.WriteRunes(newLines[0]) o.ExitCompleteMode(false) return true } same, size := runes.Aggregate(newLines) if size > 0 { buf.WriteRunes(same) o.ExitCompleteMode(false) return true } } o.EnterCompleteMode(offset, newLines) return true } func (o *opCompleter) IsInCompleteSelectMode() bool { return o.inSelectMode } func (o *opCompleter) IsInCompleteMode() bool { return o.inCompleteMode } func (o *opCompleter) HandleCompleteSelect(r rune) bool { next := true switch r { case CharEnter, CharCtrlJ: next = false o.op.buf.WriteRunes(o.op.candidate[o.op.candidateChoise]) o.ExitCompleteMode(false) case CharLineStart: num := o.candidateChoise % o.candidateColNum o.nextCandidate(-num) case CharLineEnd: num := o.candidateColNum - o.candidateChoise%o.candidateColNum - 1 o.candidateChoise += num if o.candidateChoise >= len(o.candidate) { o.candidateChoise = len(o.candidate) - 1 } case CharBackspace: o.ExitCompleteSelectMode() next = false case CharTab, CharForward: o.doSelect() case CharBell, CharInterrupt: o.ExitCompleteMode(true) next = false case CharNext: tmpChoise := o.candidateChoise + o.candidateColNum if tmpChoise >= o.getMatrixSize() { tmpChoise -= o.getMatrixSize() } else if tmpChoise >= len(o.candidate) { tmpChoise += o.candidateColNum tmpChoise -= o.getMatrixSize() } o.candidateChoise = tmpChoise case CharBackward, MetaShiftTab: o.nextCandidate(-1) case CharPrev: tmpChoise := o.candidateChoise - o.candidateColNum if tmpChoise < 0 { tmpChoise += o.getMatrixSize() if tmpChoise >= len(o.candidate) { tmpChoise -= o.candidateColNum } } o.candidateChoise = tmpChoise default: next = false o.ExitCompleteSelectMode() } if next { o.CompleteRefresh() return true } return false } func (o *opCompleter) getMatrixSize() int { line := len(o.candidate) / o.candidateColNum if len(o.candidate)%o.candidateColNum != 0 { line++ } return line * o.candidateColNum } func (o *opCompleter) OnWidthChange(newWidth int) { o.width = newWidth } func (o *opCompleter) CompleteRefresh() { if !o.inCompleteMode { return } lineCnt := o.op.buf.CursorLineCount() colWidth := 0 for _, c := range o.candidate { w := runes.WidthAll(c) if w > colWidth { colWidth = w } } colWidth += o.candidateOff + 1 same := o.op.buf.RuneSlice(-o.candidateOff) // -1 to avoid reach the end of line width := o.width - 1 colNum := width / colWidth if colNum != 0 { colWidth += (width - (colWidth * colNum)) / colNum } o.candidateColNum = colNum buf := bufio.NewWriter(o.w) buf.Write(bytes.Repeat([]byte("\n"), lineCnt)) colIdx := 0 lines := 1 buf.WriteString("\033[J") for idx, c := range o.candidate { inSelect := idx == o.candidateChoise && o.IsInCompleteSelectMode() if inSelect { buf.WriteString("\033[30;47m") } buf.WriteString(string(same)) buf.WriteString(string(c)) buf.Write(bytes.Repeat([]byte(" "), colWidth-runes.WidthAll(c)-runes.WidthAll(same))) if inSelect { buf.WriteString("\033[0m") } colIdx++ if colIdx == colNum { buf.WriteString("\n") lines++ colIdx = 0 } } // move back fmt.Fprintf(buf, "\033[%dA\r", lineCnt-1+lines) fmt.Fprintf(buf, "\033[%dC", o.op.buf.idx+o.op.buf.PromptLen()) buf.Flush() } func (o *opCompleter) aggCandidate(candidate [][]rune) int { offset := 0 for i := 0; i < len(candidate[0]); i++ { for j := 0; j < len(candidate)-1; j++ { if i > len(candidate[j]) { goto aggregate } if candidate[j][i] != candidate[j+1][i] { goto aggregate } } offset = i } aggregate: return offset } func (o *opCompleter) EnterCompleteSelectMode() { o.inSelectMode = true o.candidateChoise = -1 o.CompleteRefresh() } func (o *opCompleter) EnterCompleteMode(offset int, candidate [][]rune) { o.inCompleteMode = true o.candidate = candidate o.candidateOff = offset o.CompleteRefresh() } func (o *opCompleter) ExitCompleteSelectMode() { o.inSelectMode = false o.candidate = nil o.candidateChoise = -1 o.candidateOff = -1 o.candidateSource = nil } func (o *opCompleter) ExitCompleteMode(revent bool) { o.inCompleteMode = false o.ExitCompleteSelectMode() } golang-github-wader-readline-0.0~git20220118.692d84c/complete_helper.go000066400000000000000000000075721417137650100252230ustar00rootroot00000000000000package readline import ( "bytes" "strings" ) // Caller type for dynamic completion type DynamicCompleteFunc func(string) []string type PrefixCompleterInterface interface { Print(prefix string, level int, buf *bytes.Buffer) Do(line []rune, pos int) (newLine [][]rune, length int) GetName() []rune GetChildren() []PrefixCompleterInterface SetChildren(children []PrefixCompleterInterface) } type DynamicPrefixCompleterInterface interface { PrefixCompleterInterface IsDynamic() bool GetDynamicNames(line []rune) [][]rune } type PrefixCompleter struct { Name []rune Dynamic bool Callback DynamicCompleteFunc Children []PrefixCompleterInterface } func (p *PrefixCompleter) Tree(prefix string) string { buf := bytes.NewBuffer(nil) p.Print(prefix, 0, buf) return buf.String() } func Print(p PrefixCompleterInterface, prefix string, level int, buf *bytes.Buffer) { if strings.TrimSpace(string(p.GetName())) != "" { buf.WriteString(prefix) if level > 0 { buf.WriteString("├") buf.WriteString(strings.Repeat("─", (level*4)-2)) buf.WriteString(" ") } buf.WriteString(string(p.GetName()) + "\n") level++ } for _, ch := range p.GetChildren() { ch.Print(prefix, level, buf) } } func (p *PrefixCompleter) Print(prefix string, level int, buf *bytes.Buffer) { Print(p, prefix, level, buf) } func (p *PrefixCompleter) IsDynamic() bool { return p.Dynamic } func (p *PrefixCompleter) GetName() []rune { return p.Name } func (p *PrefixCompleter) GetDynamicNames(line []rune) [][]rune { var names = [][]rune{} for _, name := range p.Callback(string(line)) { names = append(names, []rune(name+" ")) } return names } func (p *PrefixCompleter) GetChildren() []PrefixCompleterInterface { return p.Children } func (p *PrefixCompleter) SetChildren(children []PrefixCompleterInterface) { p.Children = children } func NewPrefixCompleter(pc ...PrefixCompleterInterface) *PrefixCompleter { return PcItem("", pc...) } func PcItem(name string, pc ...PrefixCompleterInterface) *PrefixCompleter { name += " " return &PrefixCompleter{ Name: []rune(name), Dynamic: false, Children: pc, } } func PcItemDynamic(callback DynamicCompleteFunc, pc ...PrefixCompleterInterface) *PrefixCompleter { return &PrefixCompleter{ Callback: callback, Dynamic: true, Children: pc, } } func (p *PrefixCompleter) Do(line []rune, pos int) (newLine [][]rune, offset int) { return doInternal(p, line, pos, line) } func Do(p PrefixCompleterInterface, line []rune, pos int) (newLine [][]rune, offset int) { return doInternal(p, line, pos, line) } func doInternal(p PrefixCompleterInterface, line []rune, pos int, origLine []rune) (newLine [][]rune, offset int) { line = runes.TrimSpaceLeft(line[:pos]) goNext := false var lineCompleter PrefixCompleterInterface for _, child := range p.GetChildren() { childNames := make([][]rune, 1) childDynamic, ok := child.(DynamicPrefixCompleterInterface) if ok && childDynamic.IsDynamic() { childNames = childDynamic.GetDynamicNames(origLine) } else { childNames[0] = child.GetName() } for _, childName := range childNames { if len(line) >= len(childName) { if runes.HasPrefix(line, childName) { if len(line) == len(childName) { newLine = append(newLine, []rune{' '}) } else { newLine = append(newLine, childName) } offset = len(childName) lineCompleter = child goNext = true } } else { if runes.HasPrefix(childName, line) { newLine = append(newLine, childName[len(line):]) offset = len(line) lineCompleter = child } } } } if len(newLine) != 1 { return } tmpLine := make([]rune, 0, len(line)) for i := offset; i < len(line); i++ { if line[i] == ' ' { continue } tmpLine = append(tmpLine, line[i:]...) return doInternal(lineCompleter, tmpLine, len(tmpLine), origLine) } if goNext { return doInternal(lineCompleter, nil, 0, origLine) } return } golang-github-wader-readline-0.0~git20220118.692d84c/complete_segment.go000066400000000000000000000034001417137650100253700ustar00rootroot00000000000000package readline type SegmentCompleter interface { // a // |- a1 // |--- a11 // |- a2 // b // input: // DoTree([], 0) [a, b] // DoTree([a], 1) [a] // DoTree([a, ], 0) [a1, a2] // DoTree([a, a], 1) [a1, a2] // DoTree([a, a1], 2) [a1] // DoTree([a, a1, ], 0) [a11] // DoTree([a, a1, a], 1) [a11] DoSegment([][]rune, int) [][]rune } type dumpSegmentCompleter struct { f func([][]rune, int) [][]rune } func (d *dumpSegmentCompleter) DoSegment(segment [][]rune, n int) [][]rune { return d.f(segment, n) } func SegmentFunc(f func([][]rune, int) [][]rune) AutoCompleter { return &SegmentComplete{&dumpSegmentCompleter{f}} } func SegmentAutoComplete(completer SegmentCompleter) *SegmentComplete { return &SegmentComplete{ SegmentCompleter: completer, } } type SegmentComplete struct { SegmentCompleter } func RetSegment(segments [][]rune, cands [][]rune, idx int) ([][]rune, int) { ret := make([][]rune, 0, len(cands)) lastSegment := segments[len(segments)-1] for _, cand := range cands { if !runes.HasPrefix(cand, lastSegment) { continue } ret = append(ret, cand[len(lastSegment):]) } return ret, idx } func SplitSegment(line []rune, pos int) ([][]rune, int) { segs := [][]rune{} lastIdx := -1 line = line[:pos] pos = 0 for idx, l := range line { if l == ' ' { pos = 0 segs = append(segs, line[lastIdx+1:idx]) lastIdx = idx } else { pos++ } } segs = append(segs, line[lastIdx+1:]) return segs, pos } func (c *SegmentComplete) Do(line []rune, pos int) (newLine [][]rune, offset int) { segment, idx := SplitSegment(line, pos) cands := c.DoSegment(segment, idx) newLine, offset = RetSegment(segment, cands, idx) for idx := range newLine { newLine[idx] = append(newLine[idx], ' ') } return newLine, offset } golang-github-wader-readline-0.0~git20220118.692d84c/complete_segment_test.go000066400000000000000000000066071417137650100264430ustar00rootroot00000000000000package readline import ( "fmt" "reflect" "testing" ) // imitates github.com/chzyer/test test.Equals // err not used atm func testEqual(t *testing.T, actual interface{}, expected interface{}, err error) { _ = err if !reflect.DeepEqual(actual, expected) { t.Errorf("got %v, expected %v", actual, expected) } } func rs(s [][]rune) []string { ret := make([]string, len(s)) for idx, ss := range s { ret[idx] = string(ss) } return ret } func sr(s ...string) [][]rune { ret := make([][]rune, len(s)) for idx, ss := range s { ret[idx] = []rune(ss) } return ret } func TestRetSegment(t *testing.T) { // a // |- a1 // |--- a11 // |--- a12 // |- a2 // |--- a21 // b // add // adddomain ret := []struct { Segments [][]rune Cands [][]rune idx int Ret [][]rune pos int }{ {sr(""), sr("a", "b", "add", "adddomain"), 0, sr("a", "b", "add", "adddomain"), 0}, {sr("a"), sr("a", "add", "adddomain"), 1, sr("", "dd", "dddomain"), 1}, {sr("a", ""), sr("a1", "a2"), 0, sr("a1", "a2"), 0}, {sr("a", "a"), sr("a1", "a2"), 1, sr("1", "2"), 1}, {sr("a", "a1"), sr("a1"), 2, sr(""), 2}, {sr("add"), sr("add", "adddomain"), 2, sr("", "domain"), 2}, } for idx, r := range ret { ret, pos := RetSegment(r.Segments, r.Cands, r.idx) testEqual(t, ret, r.Ret, fmt.Errorf("%v", idx)) testEqual(t, pos, r.pos, fmt.Errorf("%v", idx)) } } func TestSplitSegment(t *testing.T) { // a // |- a1 // |--- a11 // |--- a12 // |- a2 // |--- a21 // b ret := []struct { Line string Pos int Segments [][]rune Idx int }{ {"", 0, sr(""), 0}, {"a", 1, sr("a"), 1}, {"a ", 2, sr("a", ""), 0}, {"a a", 3, sr("a", "a"), 1}, {"a a1", 4, sr("a", "a1"), 2}, {"a a1 ", 5, sr("a", "a1", ""), 0}, } for i, r := range ret { ret, idx := SplitSegment([]rune(r.Line), r.Pos) testEqual(t, rs(ret), rs(r.Segments), fmt.Errorf("%v", i)) testEqual(t, idx, r.Idx, fmt.Errorf("%v", i)) } } type Tree struct { Name string Children []Tree } func TestSegmentCompleter(t *testing.T) { tree := Tree{"", []Tree{ {"a", []Tree{ {"a1", []Tree{ {"a11", nil}, {"a12", nil}, }}, {"a2", []Tree{ {"a21", nil}, }}, }}, {"b", nil}, {"route", []Tree{ {"add", nil}, {"adddomain", nil}, }}, }} s := SegmentFunc(func(ret [][]rune, n int) [][]rune { tree := tree main: for level := 0; level < len(ret)-1; { name := string(ret[level]) for _, t := range tree.Children { if t.Name == name { tree = t level++ continue main } } } ret = make([][]rune, len(tree.Children)) for idx, r := range tree.Children { ret[idx] = []rune(r.Name) } return ret }) // a // |- a1 // |--- a11 // |--- a12 // |- a2 // |--- a21 // b ret := []struct { Line string Pos int Ret [][]rune Share int }{ {"", 0, sr("a", "b", "route"), 0}, {"a", 1, sr(""), 1}, {"a ", 2, sr("a1", "a2"), 0}, {"a a", 3, sr("1", "2"), 1}, {"a a1", 4, sr(""), 2}, {"a a1 ", 5, sr("a11", "a12"), 0}, {"a a1 a", 6, sr("11", "12"), 1}, {"a a1 a1", 7, sr("1", "2"), 2}, {"a a1 a11", 8, sr(""), 3}, {"route add", 9, sr("", "domain"), 3}, } for _, r := range ret { for idx, rr := range r.Ret { r.Ret[idx] = append(rr, ' ') } } for i, r := range ret { newLine, length := s.Do([]rune(r.Line), r.Pos) testEqual(t, rs(newLine), rs(r.Ret), fmt.Errorf("%v", i)) testEqual(t, length, r.Share, fmt.Errorf("%v", i)) } } golang-github-wader-readline-0.0~git20220118.692d84c/doc/000077500000000000000000000000001417137650100222575ustar00rootroot00000000000000golang-github-wader-readline-0.0~git20220118.692d84c/doc/shortcut.md000066400000000000000000000064511417137650100244620ustar00rootroot00000000000000## 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 |golang-github-wader-readline-0.0~git20220118.692d84c/example/000077500000000000000000000000001417137650100231455ustar00rootroot00000000000000golang-github-wader-readline-0.0~git20220118.692d84c/example/readline-demo/000077500000000000000000000000001417137650100256525ustar00rootroot00000000000000golang-github-wader-readline-0.0~git20220118.692d84c/example/readline-demo/readline-demo.go000066400000000000000000000070221417137650100307070ustar00rootroot00000000000000package main import ( "fmt" "io" "io/ioutil" "log" "strconv" "strings" "time" "github.com/wader/readline" ) func usage(w io.Writer) { io.WriteString(w, "commands:\n") io.WriteString(w, completer.Tree(" ")) } // Function constructor - constructs new function for listing given directory func listFiles(path string) func(string) []string { return func(line string) []string { names := make([]string, 0) files, _ := ioutil.ReadDir(path) for _, f := range files { names = append(names, f.Name()) } return names } } var completer = readline.NewPrefixCompleter( readline.PcItem("mode", readline.PcItem("vi"), readline.PcItem("emacs"), ), readline.PcItem("login"), readline.PcItem("say", readline.PcItemDynamic(listFiles("./"), readline.PcItem("with", readline.PcItem("following"), readline.PcItem("items"), ), ), readline.PcItem("hello"), readline.PcItem("bye"), ), readline.PcItem("setprompt"), readline.PcItem("setpassword"), readline.PcItem("bye"), readline.PcItem("help"), readline.PcItem("go", readline.PcItem("build", readline.PcItem("-o"), readline.PcItem("-v")), readline.PcItem("install", readline.PcItem("-v"), readline.PcItem("-vv"), readline.PcItem("-vvv"), ), readline.PcItem("test"), ), 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 ", HistoryFile: "/tmp/readline.tmp", AutoComplete: completer, InterruptPrompt: "^C", EOFPrompt: "exit", HistorySearchFold: true, FuncFilterInputRune: filterInput, }) if err != nil { panic(err) } defer l.Close() setPasswordCfg := l.GenPasswordConfig() setPasswordCfg.SetListener(func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { l.SetPrompt(fmt.Sprintf("Enter password(%v): ", len(line))) l.Refresh() return nil, 0, false }) log.SetOutput(l.Stderr()) for { line, err := l.Readline() if err == readline.ErrInterrupt { if len(line) == 0 { break } else { continue } } else if err == io.EOF { break } line = strings.TrimSpace(line) switch { case strings.HasPrefix(line, "mode "): switch line[5:] { case "vi": l.SetVimMode(true) case "emacs": l.SetVimMode(false) default: println("invalid mode:", line[5:]) } case line == "mode": if l.IsVimMode() { println("current mode: vim") } else { println("current mode: emacs") } case line == "login": pswd, err := l.ReadPassword("please enter your password: ") if err != nil { break } println("you enter:", strconv.Quote(string(pswd))) case line == "help": usage(l.Stderr()) case line == "setpassword": pswd, err := l.ReadPasswordWithConfig(setPasswordCfg) if err == nil { println("you set:", strconv.Quote(string(pswd))) } case strings.HasPrefix(line, "setprompt"): if len(line) <= 10 { log.Println("setprompt ") break } l.SetPrompt(line[10:]) case strings.HasPrefix(line, "say"): line := strings.TrimSpace(line[3:]) if len(line) == 0 { log.Println("say what?") break } go func() { for range time.Tick(time.Second) { log.Println(line) } }() case line == "bye": goto exit case line == "sleep": log.Println("sleep 4 second") time.Sleep(4 * time.Second) case line == "": default: log.Println("you said:", strconv.Quote(line)) } } exit: } golang-github-wader-readline-0.0~git20220118.692d84c/example/readline-im/000077500000000000000000000000001417137650100253335ustar00rootroot00000000000000golang-github-wader-readline-0.0~git20220118.692d84c/example/readline-im/README.md000066400000000000000000000002301417137650100266050ustar00rootroot00000000000000# readline-im ![readline-im](https://dl.dropboxusercontent.com/s/52hc7bo92g3pgi5/03F93B8D-9B4B-4D35-BBAA-22FBDAC7F299-26173-000164AA33980001.gif?dl=0) golang-github-wader-readline-0.0~git20220118.692d84c/example/readline-im/readline-im.go000066400000000000000000000016631417137650100300560ustar00rootroot00000000000000package main import ( "fmt" "math/rand" "time" "log" "github.com/wader/readline" ) func main() { rl, err := readline.NewEx(&readline.Config{ UniqueEditLine: true, }) if err != nil { panic(err) } defer rl.Close() rl.SetPrompt("username: ") username, err := rl.Readline() if err != nil { return } rl.ResetHistory() log.SetOutput(rl.Stderr()) fmt.Fprintln(rl, "Hi,", username+"! My name is Dave.") rl.SetPrompt(username + "> ") done := make(chan struct{}) go func() { rand.Seed(time.Now().Unix()) loop: for { select { case <-time.After(time.Duration(rand.Intn(20)) * 100 * time.Millisecond): case <-done: break loop } log.Println("Dave:", "hello") } log.Println("Dave:", "bye") done <- struct{}{} }() for { ln := rl.Line() if ln.CanContinue() { continue } else if ln.CanBreak() { break } log.Println(username+":", ln.Line) } rl.Clean() done <- struct{}{} <-done } golang-github-wader-readline-0.0~git20220118.692d84c/example/readline-multiline/000077500000000000000000000000001417137650100267305ustar00rootroot00000000000000readline-multiline.go000066400000000000000000000012431417137650100327630ustar00rootroot00000000000000golang-github-wader-readline-0.0~git20220118.692d84c/example/readline-multilinepackage main import ( "strings" "github.com/wader/readline" ) func main() { rl, err := readline.NewEx(&readline.Config{ Prompt: "> ", HistoryFile: "/tmp/readline-multiline", DisableAutoSaveHistory: true, }) if err != nil { panic(err) } defer rl.Close() var cmds []string for { line, err := rl.Readline() if err != nil { break } line = strings.TrimSpace(line) if len(line) == 0 { continue } cmds = append(cmds, line) if !strings.HasSuffix(line, ";") { rl.SetPrompt(">>> ") continue } cmd := strings.Join(cmds, " ") cmds = cmds[:0] rl.SetPrompt("> ") rl.SaveHistory(cmd) println(cmd) } } golang-github-wader-readline-0.0~git20220118.692d84c/example/readline-remote/000077500000000000000000000000001417137650100262215ustar00rootroot00000000000000golang-github-wader-readline-0.0~git20220118.692d84c/example/readline-remote/readline-remote-client/000077500000000000000000000000001417137650100325515ustar00rootroot00000000000000client.go000066400000000000000000000002321417137650100342740ustar00rootroot00000000000000golang-github-wader-readline-0.0~git20220118.692d84c/example/readline-remote/readline-remote-clientpackage main import "github.com/wader/readline" func main() { if err := readline.DialRemote("tcp", ":12344"); err != nil { println(err.Error()) } } golang-github-wader-readline-0.0~git20220118.692d84c/example/readline-remote/readline-remote-server/000077500000000000000000000000001417137650100326015ustar00rootroot00000000000000server.go000066400000000000000000000006401417137650100343570ustar00rootroot00000000000000golang-github-wader-readline-0.0~git20220118.692d84c/example/readline-remote/readline-remote-serverpackage main import ( "fmt" "github.com/wader/readline" ) func main() { cfg := &readline.Config{ Prompt: "readline-remote: ", } handleFunc := func(rl *readline.Instance) { for { line, err := rl.Readline() if err != nil { break } fmt.Fprintln(rl.Stdout(), "receive:"+line) } } err := readline.ListenRemote("tcp", ":12344", cfg, handleFunc) if err != nil { println(err.Error()) } } golang-github-wader-readline-0.0~git20220118.692d84c/go.mod000066400000000000000000000002151417137650100226160ustar00rootroot00000000000000// fork of github.com/chzyer/readline module github.com/wader/readline go 1.16 require golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 golang-github-wader-readline-0.0~git20220118.692d84c/go.sum000066400000000000000000000003171417137650100226460ustar00rootroot00000000000000golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 h1:TyHqChC80pFkXWraUUf6RuB5IqFdQieMLwwCJokV2pc= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang-github-wader-readline-0.0~git20220118.692d84c/history.go000066400000000000000000000137771417137650100235610ustar00rootroot00000000000000package readline import ( "bufio" "container/list" "fmt" "os" "strings" "sync" ) type hisItem struct { Source []rune Version int64 Tmp []rune } func (h *hisItem) Clean() { h.Source = nil h.Tmp = nil } type opHistory struct { cfg *Config history *list.List 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 } func (o *opHistory) Reset() { o.history = list.New() o.current = nil } func (o *opHistory) IsHistoryClosed() bool { o.fdLock.Lock() defer o.fdLock.Unlock() return o.fd.Fd() == ^(uintptr(0)) } func (o *opHistory) Init() { if o.IsHistoryClosed() { o.initHistory() } } func (o *opHistory) initHistory() { if o.cfg.HistoryFile != "" { o.historyUpdatePath(o.cfg.HistoryFile) } } // 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 } o.fd = f r := bufio.NewReader(o.fd) total := 0 for ; ; total++ { line, err := r.ReadString('\n') if err != nil { break } // ignore the empty line line = strings.TrimSpace(line) if len(line) == 0 { continue } o.Push([]rune(line)) o.Compact() } if total > o.cfg.HistoryLimit { o.rewriteLocked() } o.historyVer++ o.Push(nil) return } func (o *opHistory) Compact() { for o.history.Len() > o.cfg.HistoryLimit && o.history.Len() > 0 { o.history.Remove(o.history.Front()) } } func (o *opHistory) Rewrite() { o.fdLock.Lock() defer o.fdLock.Unlock() o.rewriteLocked() } func (o *opHistory) rewriteLocked() { if o.cfg.HistoryFile == "" { return } tmpFile := o.cfg.HistoryFile + ".tmp" fd, err := os.OpenFile(tmpFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC|os.O_APPEND, 0666) if err != nil { return } buf := bufio.NewWriter(fd) for elem := o.history.Front(); elem != nil; elem = elem.Next() { buf.WriteString(string(elem.Value.(*hisItem).Source) + "\n") } buf.Flush() // replace history file if err = os.Rename(tmpFile, o.cfg.HistoryFile); err != nil { fd.Close() return } if o.fd != nil { o.fd.Close() } // fd is write only, just satisfy what we need. o.fd = fd } func (o *opHistory) Close() { o.fdLock.Lock() defer o.fdLock.Unlock() if o.fd != nil { o.fd.Close() } } func (o *opHistory) FindBck(isNewSearch bool, rs []rune, start int) (int, *list.Element) { for elem := o.current; elem != nil; elem = elem.Prev() { item := o.showItem(elem.Value) if isNewSearch { start += len(rs) } if elem == o.current { if len(item) >= start { item = item[:start] } } idx := runes.IndexAllBckEx(item, rs, o.cfg.HistorySearchFold) if idx < 0 { continue } return idx, elem } return -1, nil } func (o *opHistory) FindFwd(isNewSearch bool, rs []rune, start int) (int, *list.Element) { for elem := o.current; elem != nil; elem = elem.Next() { item := o.showItem(elem.Value) if isNewSearch { start -= len(rs) if start < 0 { start = 0 } } if elem == o.current { if len(item)-1 >= start { item = item[start:] } else { continue } } idx := runes.IndexAllEx(item, rs, o.cfg.HistorySearchFold) if idx < 0 { continue } if elem == o.current { idx += start } return idx, elem } return -1, nil } func (o *opHistory) showItem(obj interface{}) []rune { item := obj.(*hisItem) if item.Version == o.historyVer { return item.Tmp } return item.Source } func (o *opHistory) Prev() []rune { if o.current == nil { return nil } current := o.current.Prev() if current == nil { return nil } o.current = current return runes.Copy(o.showItem(current.Value)) } func (o *opHistory) Next() ([]rune, bool) { if o.current == nil { return nil, false } current := o.current.Next() if current == nil { return nil, false } o.current = current 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() { Debug(fmt.Sprintf("%+v", item.Value)) } } // 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 // just clean lastest history if back := o.history.Back(); back != nil { prev := back.Prev() if prev != nil { if runes.Equal(current, prev.Value.(*hisItem).Source) { o.current = o.history.Back() o.current.Value.(*hisItem).Clean() o.historyVer++ return nil } } } if len(current) == 0 { o.current = o.history.Back() if o.current != nil { o.current.Value.(*hisItem).Clean() o.historyVer++ return nil } } if o.current != o.history.Back() { // move history item to current command currentItem := o.current.Value.(*hisItem) // set current to last item o.current = o.history.Back() current = runes.Copy(currentItem.Tmp) } // err only can be a IO error, just report err = o.Update(current, true) // push a new one to commit current command o.historyVer++ o.Push(nil) return } func (o *opHistory) Revert() { o.historyVer++ o.current = o.history.Back() } 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) o.Compact() return } r := o.current.Value.(*hisItem) r.Version = o.historyVer if commit { r.Source = s if o.fd != nil { // just report the error _, err = o.fd.Write([]byte(string(r.Source) + "\n")) } } else { r.Tmp = append(r.Tmp[:0], s...) } o.current.Value = r o.Compact() return } func (o *opHistory) Push(s []rune) { s = runes.Copy(s) elem := o.history.PushBack(&hisItem{Source: s}) o.current = elem } golang-github-wader-readline-0.0~git20220118.692d84c/operation.go000066400000000000000000000255061417137650100240510ustar00rootroot00000000000000package readline import ( "errors" "io" "sync" ) var ( ErrInterrupt = errors.New("Interrupt") ) type InterruptError struct { Line []rune } func (*InterruptError) Error() string { return "Interrupted" } type Operation struct { m sync.Mutex cfg *Config t *Terminal buf *RuneBuffer outchan chan []rune errchan chan error w io.Writer history *opHistory *opSearch *opCompleter *opPassword *opVim *opUndo } func (o *Operation) SetBuffer(what string) { o.buf.Set([]rune(what)) } type wrapWriter struct { r *Operation t *Terminal target io.Writer } func (w *wrapWriter) Write(b []byte) (int, error) { if !w.t.IsReading() { return w.target.Write(b) } var ( n int err error ) w.r.buf.Refresh(func() { n, err = w.target.Write(b) }) if w.r.IsSearchMode() { w.r.SearchRefresh(-1) } if w.r.IsInCompleteMode() { w.r.CompleteRefresh() } return n, err } func NewOperation(t *Terminal, cfg *Config) *Operation { width := cfg.FuncGetWidth() op := &Operation{ t: t, buf: NewRuneBuffer(t, cfg.Prompt, cfg, width), outchan: make(chan []rune), errchan: make(chan error, 1), } op.w = op.buf.w op.SetConfig(cfg) op.opVim = newVimMode(op) op.opCompleter = newOpCompleter(op.buf.w, op, width) op.opPassword = newOpPassword(op) op.cfg.FuncOnWidthChanged(func() { newWidth := cfg.FuncGetWidth() op.opCompleter.OnWidthChange(newWidth) op.opSearch.OnWidthChange(newWidth) op.buf.OnWidthChange(newWidth) }) op.opUndo = newOpUndo(op) op.buf.OnChange = op.opUndo.add go op.ioloop() return op } func (o *Operation) SetPrompt(s string) { o.buf.SetPrompt(s) } 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.buf.Refresh(nil) // to refresh the line continue // ignore this rune } } if r == 0 { // io.EOF if o.buf.Len() == 0 { o.buf.Clean() select { case o.errchan <- io.EOF: } break } else { // if stdin got io.EOF and there is something left in buffer, // let's flush them by sending CharEnter. // And we will got io.EOF int next loop. r = CharEnter } } isUpdateHistory := true if o.IsInCompleteSelectMode() { keepInCompleteMode = o.HandleCompleteSelect(r) if keepInCompleteMode { continue } o.buf.Refresh(nil) switch r { case CharEnter, CharCtrlJ: o.history.Update(o.buf.Runes(), false) fallthrough case CharInterrupt: o.t.KickRead() fallthrough case CharBell: continue } } if o.IsEnableVimMode() { r = o.HandleVim(r, o.t.ReadRune) if r == 0 { continue } } switch r { case CharBell: if o.IsSearchMode() { o.ExitSearchMode(true) o.buf.Refresh(nil) } if o.IsInCompleteMode() { o.ExitCompleteMode(true) o.buf.Refresh(nil) } case CharTab: if o.GetConfig().AutoComplete == nil { o.t.Bell() break } if o.OnComplete() { keepInCompleteMode = true } else { o.t.Bell() break } case CharBckSearch: if !o.SearchMode(S_DIR_BCK) { o.t.Bell() break } keepInSearchMode = true case CharCtrlU: o.buf.KillFront() case CharFwdSearch: if !o.SearchMode(S_DIR_FWD) { o.t.Bell() break } keepInSearchMode = true case CharKill: o.buf.Kill() keepInCompleteMode = true case MetaForward: o.buf.MoveToNextWord() case CharTranspose: o.buf.Transpose() case MetaBackward: o.buf.MoveToPrevWord() case MetaDelete: o.buf.DeleteWord() case CharLineStart: o.buf.MoveToLineStart() case CharLineEnd: o.buf.MoveToLineEnd() case CharBackspace, CharCtrlH: if o.IsSearchMode() { o.SearchBackspace() keepInSearchMode = true break } if o.buf.Len() == 0 { o.t.Bell() break } o.buf.Backspace() if o.IsInCompleteMode() { o.OnComplete() } case CharCtrlZ: o.buf.Clean() o.t.SleepToResume() o.Refresh() case CharCtrlL: ClearScreen(o.w) o.Refresh() case MetaBackspace, CharCtrlW: o.buf.BackEscapeWord() case CharCtrlY: o.buf.Yank() case CharCtrl_: o.opUndo.undo() case CharEnter, CharCtrlJ: if o.IsSearchMode() { o.ExitSearchMode(false) } o.buf.MoveToLineEnd() var data []rune if !o.GetConfig().UniqueEditLine { o.buf.WriteRune('\n') data = o.buf.Reset() data = data[:len(data)-1] // trim \n } else { o.buf.Clean() data = o.buf.Reset() } o.outchan <- data if !o.GetConfig().DisableAutoSaveHistory { // ignore IO error _ = o.history.New(data) } else { isUpdateHistory = false } o.opUndo.init() case CharBackward: o.buf.MoveBackward() case CharForward: o.buf.MoveForward() case CharPrev: buf := o.history.Prev() if buf != nil { o.buf.Set(buf) o.opUndo.init() } else { o.t.Bell() } case CharNext: buf, ok := o.history.Next() if ok { o.buf.Set(buf) o.opUndo.init() } else { o.t.Bell() } case CharDelete: if o.buf.Len() > 0 || !o.IsNormalMode() { o.t.KickRead() if !o.buf.Delete() { o.t.Bell() } } case CharEOT: if o.buf.Len() > 0 || !o.IsNormalMode() { o.t.KickRead() if !o.buf.Delete() { o.t.Bell() } break } // treat as EOF 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.GetConfig().UniqueEditLine { o.buf.Clean() } case CharInterrupt: if o.IsSearchMode() { o.t.KickRead() o.ExitSearchMode(true) break } if o.IsInCompleteMode() { o.t.KickRead() o.ExitCompleteMode(true) o.buf.Refresh(nil) break } o.buf.MoveToLineEnd() o.buf.Refresh(nil) hint := o.GetConfig().InterruptPrompt + "\n" if !o.GetConfig().UniqueEditLine { o.buf.WriteString(hint) } remain := o.buf.Reset() if !o.GetConfig().UniqueEditLine { remain = remain[:len(remain)-len([]rune(hint))] } isUpdateHistory = false o.history.Revert() o.errchan <- &InterruptError{remain} case MetaShiftTab: // do nothing default: if o.IsSearchMode() { o.SearchChar(r) keepInSearchMode = true break } o.buf.WriteRune(r) if o.IsInCompleteMode() { o.OnComplete() keepInCompleteMode = true } } 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.opUndo.init() o.buf.Refresh(nil) } else if o.IsInCompleteMode() { if !keepInCompleteMode { o.ExitCompleteMode(false) o.opUndo.init() o.Refresh() } else { o.buf.Refresh(nil) o.CompleteRefresh() } } if isUpdateHistory && !o.IsSearchMode() { // 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.GetConfig().Stderr, r: o, t: o.t} } func (o *Operation) Stdout() io.Writer { return &wrapWriter{target: o.GetConfig().Stdout, r: o, t: o.t} } func (o *Operation) String() (string, error) { r, err := o.Runes() return string(r), err } func (o *Operation) Runes() ([]rune, error) { o.t.EnterRawMode() defer o.t.ExitRawMode() listener := o.GetConfig().Listener if listener != nil { listener.OnChange(nil, 0, 0) } o.buf.Refresh(nil) // print prompt o.t.KickRead() select { case r := <-o.outchan: return r, nil case err := <-o.errchan: if e, ok := err.(*InterruptError); ok { return e.Line, ErrInterrupt } return nil, err } } func (o *Operation) PasswordEx(prompt string, l Listener) ([]byte, error) { cfg := o.GenPasswordConfig() cfg.Prompt = prompt cfg.Listener = l return o.PasswordWithConfig(cfg) } func (o *Operation) GenPasswordConfig() *Config { return o.opPassword.PasswordConfig() } func (o *Operation) PasswordWithConfig(cfg *Config) ([]byte, error) { if err := o.opPassword.EnterPasswordMode(cfg); err != nil { return nil, err } defer o.opPassword.ExitPasswordMode() return o.Slice() } func (o *Operation) Password(prompt string) ([]byte, error) { return o.PasswordEx(prompt, nil) } func (o *Operation) SetTitle(t string) { o.w.Write([]byte("\033[2;" + t + "\007")) } func (o *Operation) Slice() ([]byte, error) { r, err := o.Runes() if err != nil { return nil, err } return []byte(string(r)), nil } func (o *Operation) Close() { o.history.Close() } func (o *Operation) SetHistoryPath(path string) { if o.history != nil { o.history.Close() } o.cfg.HistoryFile = path o.history = newOpHistory(o.cfg) } func (o *Operation) IsNormalMode() bool { return !o.IsInCompleteMode() && !o.IsSearchMode() } func (op *Operation) SetConfig(cfg *Config) (*Config, error) { op.m.Lock() defer op.m.Unlock() if op.cfg == cfg { return op.cfg, nil } if err := cfg.Init(); err != nil { return op.cfg, err } old := op.cfg op.cfg = cfg op.SetPrompt(cfg.Prompt) op.SetMaskRune(cfg.MaskRune) op.buf.SetConfig(cfg) width := op.cfg.FuncGetWidth() if cfg.opHistory == nil { op.SetHistoryPath(cfg.HistoryFile) cfg.opHistory = op.history cfg.opSearch = newOpSearch(op.buf.w, op.buf, op.history, cfg, width) } op.history = cfg.opHistory // SetHistoryPath will close opHistory which already exists // so if we use it next time, we need to reopen it by `InitHistory()` op.history.Init() if op.cfg.AutoComplete != nil { op.opCompleter = newOpCompleter(op.buf.w, op, width) } op.opSearch = cfg.opSearch return old, nil } func (o *Operation) ResetHistory() { o.history.Reset() } // if err is not nil, it just mean it fail to write to file // other things goes fine. func (o *Operation) SaveHistory(content string) error { return o.history.New([]rune(content)) } func (o *Operation) Refresh() { if o.t.IsReading() { o.buf.Refresh(nil) } } func (o *Operation) Clean() { o.buf.Clean() } func FuncListener(f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)) Listener { return &DumpListener{f: f} } type DumpListener struct { f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) } func (d *DumpListener) OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { return d.f(line, pos, key) } 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 } golang-github-wader-readline-0.0~git20220118.692d84c/password.go000066400000000000000000000011661417137650100237070ustar00rootroot00000000000000package readline type opPassword struct { o *Operation backupCfg *Config } func newOpPassword(o *Operation) *opPassword { return &opPassword{o: o} } func (o *opPassword) ExitPasswordMode() { o.o.SetConfig(o.backupCfg) o.backupCfg = nil } func (o *opPassword) EnterPasswordMode(cfg *Config) (err error) { o.backupCfg, err = o.o.SetConfig(cfg) return } func (o *opPassword) PasswordConfig() *Config { return &Config{ EnableMask: true, InterruptPrompt: "\n", EOFPrompt: "\n", HistoryLimit: -1, Painter: &defaultPainter{}, Stdout: o.o.cfg.Stdout, Stderr: o.o.cfg.Stderr, } } golang-github-wader-readline-0.0~git20220118.692d84c/rawreader_windows.go000066400000000000000000000044761417137650100256020ustar00rootroot00000000000000// +build windows package readline import ( "unsafe" ) const ( VK_CANCEL = 0x03 VK_BACK = 0x08 VK_TAB = 0x09 VK_RETURN = 0x0D VK_SHIFT = 0x10 VK_CONTROL = 0x11 VK_MENU = 0x12 VK_ESCAPE = 0x1B VK_LEFT = 0x25 VK_UP = 0x26 VK_RIGHT = 0x27 VK_DOWN = 0x28 VK_DELETE = 0x2E VK_LSHIFT = 0xA0 VK_RSHIFT = 0xA1 VK_LCONTROL = 0xA2 VK_RCONTROL = 0xA3 ) // RawReader translate input record to ANSI escape sequence. // To provides same behavior as unix terminal. type RawReader struct { ctrlKey bool altKey bool } func NewRawReader() *RawReader { r := new(RawReader) return r } // only process one action in one read func (r *RawReader) Read(buf []byte) (int, error) { ir := new(_INPUT_RECORD) var read int var err error next: err = kernel.ReadConsoleInputW(stdin, uintptr(unsafe.Pointer(ir)), 1, uintptr(unsafe.Pointer(&read)), ) if err != nil { return 0, err } if ir.EventType != EVENT_KEY { goto next } ker := (*_KEY_EVENT_RECORD)(unsafe.Pointer(&ir.Event[0])) if ker.bKeyDown == 0 { // keyup if r.ctrlKey || r.altKey { switch ker.wVirtualKeyCode { case VK_RCONTROL, VK_LCONTROL: r.ctrlKey = false case VK_MENU: //alt r.altKey = false } } goto next } if ker.unicodeChar == 0 { var target rune switch ker.wVirtualKeyCode { case VK_RCONTROL, VK_LCONTROL: r.ctrlKey = true case VK_MENU: //alt r.altKey = true case VK_LEFT: target = CharBackward case VK_RIGHT: target = CharForward case VK_UP: target = CharPrev case VK_DOWN: target = CharNext case VK_DELETE: target = CharDelete } if target != 0 { return r.write(buf, target) } goto next } char := rune(ker.unicodeChar) if r.ctrlKey { switch char { case 'A': char = CharLineStart case 'E': char = CharLineEnd case 'R': char = CharBckSearch case 'S': char = CharFwdSearch } } else if r.altKey { switch char { case VK_BACK: char = CharBackspace } return r.writeEsc(buf, char) } return r.write(buf, char) } func (r *RawReader) writeEsc(b []byte, char rune) (int, error) { b[0] = '\033' n := copy(b[1:], []byte(string(char))) return n + 1, nil } func (r *RawReader) write(b []byte, char rune) (int, error) { n := copy(b, []byte(string(char))) return n, nil } func (r *RawReader) Close() error { return nil } golang-github-wader-readline-0.0~git20220118.692d84c/readline.go000066400000000000000000000155751417137650100236410ustar00rootroot00000000000000// 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" type Instance struct { Config *Config Terminal *Terminal Operation *Operation } type Config struct { // prompt supports ANSI escape sequence, so we can color some characters even in windows Prompt string // readline will persist historys to file where HistoryFile specified HistoryFile string // 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 // Any key press will pass to Listener // 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 InterruptPrompt string EOFPrompt string FuncGetWidth func() int Stdin io.ReadCloser StdinWriter io.Writer Stdout io.Writer Stderr io.Writer EnableMask bool MaskRune rune // erase the editing line after user submited it // 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 FuncExitRaw func() error FuncOnWidthChanged func(func()) ForceUseInteractive bool // private fields inited bool opHistory *opHistory opSearch *opSearch } func (c *Config) useInteractive() bool { if c.ForceUseInteractive { return true } return c.FuncIsTerminal() } func (c *Config) Init() error { if c.inited { return nil } c.inited = true if c.Stdin == nil { c.Stdin = NewCancelableStdin(Stdin) } c.Stdin, c.StdinWriter = NewFillableStdin(c.Stdin) if c.Stdout == nil { c.Stdout = Stdout } if c.Stderr == nil { c.Stderr = Stderr } if c.HistoryLimit == 0 { c.HistoryLimit = 500 } if c.InterruptPrompt == "" { c.InterruptPrompt = "^C" } else if c.InterruptPrompt == "\n" { c.InterruptPrompt = "" } if c.EOFPrompt == "" { c.EOFPrompt = "^D" } else if c.EOFPrompt == "\n" { c.EOFPrompt = "" } if c.AutoComplete == nil { c.AutoComplete = &TabCompleter{} } if c.FuncGetWidth == nil { c.FuncGetWidth = GetScreenWidth } if c.FuncIsTerminal == nil { c.FuncIsTerminal = DefaultIsTerminal } rm := new(RawMode) if c.FuncMakeRaw == nil { c.FuncMakeRaw = rm.Enter } if c.FuncExitRaw == nil { c.FuncExitRaw = rm.Exit } if c.FuncOnWidthChanged == nil { c.FuncOnWidthChanged = DefaultOnWidthChanged } return nil } func (c Config) Clone() *Config { c.opHistory = nil c.opSearch = nil return &c } func (c *Config) SetListener(f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)) { 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, Operation: rl, }, nil } func New(prompt string) (*Instance, error) { return NewEx(&Config{Prompt: prompt}) } func (i *Instance) ResetHistory() { i.Operation.ResetHistory() } func (i *Instance) SetPrompt(s string) { i.Operation.SetPrompt(s) } func (i *Instance) SetMaskRune(r rune) { i.Operation.SetMaskRune(r) } // change history persistence in runtime func (i *Instance) SetHistoryPath(p string) { i.Operation.SetHistoryPath(p) } // readline will refresh automatic when write through Stdout() func (i *Instance) Stdout() io.Writer { return i.Operation.Stdout() } // readline will refresh automatic when write through Stdout() func (i *Instance) Stderr() io.Writer { return i.Operation.Stderr() } // switch VimMode in runtime func (i *Instance) SetVimMode(on bool) { i.Operation.SetVimMode(on) } func (i *Instance) IsVimMode() bool { return i.Operation.IsEnableVimMode() } func (i *Instance) GenPasswordConfig() *Config { return i.Operation.GenPasswordConfig() } // we can generate a config by `i.GenPasswordConfig()` func (i *Instance) ReadPasswordWithConfig(cfg *Config) ([]byte, error) { return i.Operation.PasswordWithConfig(cfg) } func (i *Instance) ReadPasswordEx(prompt string, l Listener) ([]byte, error) { return i.Operation.PasswordEx(prompt, l) } func (i *Instance) ReadPassword(prompt string) ([]byte, error) { return i.Operation.Password(prompt) } type Result struct { Line string Error error } func (l *Result) CanContinue() bool { return len(l.Line) != 0 && l.Error == ErrInterrupt } func (l *Result) CanBreak() bool { return !l.CanContinue() && l.Error != nil } func (i *Instance) Line() *Result { ret, err := i.Readline() return &Result{ret, err} } // err is one of (nil, io.EOF, readline.ErrInterrupt) 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) } // same as readline func (i *Instance) ReadSlice() ([]byte, error) { return i.Operation.Slice() } // we must make sure that call Close() before process exit. func (i *Instance) Close() error { if err := i.Terminal.Close(); err != nil { return err } i.Config.Stdin.Close() i.Operation.Close() return nil } func (i *Instance) Clean() { i.Operation.Clean() } 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 } old := i.Config i.Config = cfg i.Operation.SetConfig(cfg) i.Terminal.SetConfig(cfg) return old } 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() } golang-github-wader-readline-0.0~git20220118.692d84c/readline_test.go000066400000000000000000000005111417137650100246600ustar00rootroot00000000000000package 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() } golang-github-wader-readline-0.0~git20220118.692d84c/remote.go000066400000000000000000000205551417137650100233430ustar00rootroot00000000000000package readline import ( "bufio" "bytes" "encoding/binary" "fmt" "io" "net" "os" "sync" "sync/atomic" ) type MsgType int16 const ( T_DATA = MsgType(iota) T_WIDTH T_WIDTH_REPORT T_ISTTY_REPORT T_RAW T_ERAW // exit raw T_EOF ) type RemoteSvr struct { eof int32 closed int32 width int32 reciveChan chan struct{} writeChan chan *writeCtx conn net.Conn isTerminal bool funcWidthChan func() stopChan chan struct{} dataBufM sync.Mutex dataBuf bytes.Buffer } type writeReply struct { n int err error } type writeCtx struct { msg *Message reply chan *writeReply } func newWriteCtx(msg *Message) *writeCtx { return &writeCtx{ msg: msg, reply: make(chan *writeReply), } } func NewRemoteSvr(conn net.Conn) (*RemoteSvr, error) { rs := &RemoteSvr{ width: -1, conn: conn, writeChan: make(chan *writeCtx), reciveChan: make(chan struct{}), stopChan: make(chan struct{}), } buf := bufio.NewReader(rs.conn) if err := rs.init(buf); err != nil { return nil, err } go rs.readLoop(buf) go rs.writeLoop() return rs, nil } func (r *RemoteSvr) init(buf *bufio.Reader) error { m, err := ReadMessage(buf) if err != nil { return err } // receive isTerminal if m.Type != T_ISTTY_REPORT { return fmt.Errorf("unexpected init message") } r.GotIsTerminal(m.Data) // receive width m, err = ReadMessage(buf) if err != nil { return err } if m.Type != T_WIDTH_REPORT { return fmt.Errorf("unexpected init message") } r.GotReportWidth(m.Data) return nil } func (r *RemoteSvr) HandleConfig(cfg *Config) { cfg.Stderr = r cfg.Stdout = r cfg.Stdin = r cfg.FuncExitRaw = r.ExitRawMode cfg.FuncIsTerminal = r.IsTerminal cfg.FuncMakeRaw = r.EnterRawMode cfg.FuncExitRaw = r.ExitRawMode cfg.FuncGetWidth = r.GetWidth cfg.FuncOnWidthChanged = func(f func()) { r.funcWidthChan = f } } func (r *RemoteSvr) IsTerminal() bool { return r.isTerminal } func (r *RemoteSvr) checkEOF() error { if atomic.LoadInt32(&r.eof) == 1 { return io.EOF } return nil } func (r *RemoteSvr) Read(b []byte) (int, error) { r.dataBufM.Lock() n, err := r.dataBuf.Read(b) r.dataBufM.Unlock() if n == 0 { if err := r.checkEOF(); err != nil { return 0, err } } if n == 0 && err == io.EOF { <-r.reciveChan r.dataBufM.Lock() n, err = r.dataBuf.Read(b) r.dataBufM.Unlock() } if n == 0 { if err := r.checkEOF(); err != nil { return 0, err } } return n, err } func (r *RemoteSvr) writeMsg(m *Message) error { ctx := newWriteCtx(m) r.writeChan <- ctx reply := <-ctx.reply return reply.err } func (r *RemoteSvr) Write(b []byte) (int, error) { ctx := newWriteCtx(NewMessage(T_DATA, b)) r.writeChan <- ctx reply := <-ctx.reply return reply.n, reply.err } func (r *RemoteSvr) EnterRawMode() error { return r.writeMsg(NewMessage(T_RAW, nil)) } func (r *RemoteSvr) ExitRawMode() error { return r.writeMsg(NewMessage(T_ERAW, nil)) } func (r *RemoteSvr) writeLoop() { defer r.Close() loop: for { select { case ctx, ok := <-r.writeChan: if !ok { break } n, err := ctx.msg.WriteTo(r.conn) ctx.reply <- &writeReply{n, err} case <-r.stopChan: break loop } } } 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) { defer r.Close() for { m, err := ReadMessage(buf) if err != nil { break } switch m.Type { case T_EOF: atomic.StoreInt32(&r.eof, 1) select { case r.reciveChan <- struct{}{}: default: } case T_DATA: r.dataBufM.Lock() r.dataBuf.Write(m.Data) r.dataBufM.Unlock() select { case r.reciveChan <- struct{}{}: default: } case T_WIDTH_REPORT: r.GotReportWidth(m.Data) case T_ISTTY_REPORT: r.GotIsTerminal(m.Data) } } } func (r *RemoteSvr) GotIsTerminal(data []byte) { if binary.BigEndian.Uint16(data) == 0 { r.isTerminal = false } else { r.isTerminal = true } } func (r *RemoteSvr) GotReportWidth(data []byte) { atomic.StoreInt32(&r.width, int32(binary.BigEndian.Uint16(data))) if r.funcWidthChan != nil { r.funcWidthChan() } } func (r *RemoteSvr) GetWidth() int { return int(atomic.LoadInt32(&r.width)) } // ----------------------------------------------------------------------------- type Message struct { Type MsgType Data []byte } func ReadMessage(r io.Reader) (*Message, error) { m := new(Message) var length int32 if err := binary.Read(r, binary.BigEndian, &length); err != nil { return nil, err } if err := binary.Read(r, binary.BigEndian, &m.Type); err != nil { return nil, err } m.Data = make([]byte, int(length)-2) if _, err := io.ReadFull(r, m.Data); err != nil { return nil, err } return m, nil } func NewMessage(t MsgType, data []byte) *Message { return &Message{t, data} } func (m *Message) WriteTo(w io.Writer) (int, error) { buf := bytes.NewBuffer(make([]byte, 0, len(m.Data)+2+4)) binary.Write(buf, binary.BigEndian, int32(len(m.Data)+2)) binary.Write(buf, binary.BigEndian, m.Type) buf.Write(m.Data) n, err := buf.WriteTo(w) return int(n), err } // ----------------------------------------------------------------------------- type RemoteCli struct { conn net.Conn raw RawMode receiveChan chan struct{} inited int32 isTerminal *bool data bytes.Buffer dataM sync.Mutex } func NewRemoteCli(conn net.Conn) (*RemoteCli, error) { r := &RemoteCli{ conn: conn, receiveChan: make(chan struct{}), } return r, nil } func (r *RemoteCli) MarkIsTerminal(is bool) { r.isTerminal = &is } func (r *RemoteCli) init() error { if !atomic.CompareAndSwapInt32(&r.inited, 0, 1) { return nil } if err := r.reportIsTerminal(); err != nil { return err } if err := r.reportWidth(); err != nil { return err } // register sig for width changed DefaultOnWidthChanged(func() { r.reportWidth() }) return nil } func (r *RemoteCli) writeMsg(m *Message) error { r.dataM.Lock() _, err := m.WriteTo(r.conn) r.dataM.Unlock() return err } func (r *RemoteCli) Write(b []byte) (int, error) { m := NewMessage(T_DATA, b) r.dataM.Lock() _, err := m.WriteTo(r.conn) r.dataM.Unlock() return len(b), err } func (r *RemoteCli) reportWidth() error { screenWidth := GetScreenWidth() data := make([]byte, 2) binary.BigEndian.PutUint16(data, uint16(screenWidth)) msg := NewMessage(T_WIDTH_REPORT, data) if err := r.writeMsg(msg); err != nil { return err } return nil } func (r *RemoteCli) reportIsTerminal() error { var isTerminal bool if r.isTerminal != nil { isTerminal = *r.isTerminal } else { isTerminal = DefaultIsTerminal() } data := make([]byte, 2) if isTerminal { binary.BigEndian.PutUint16(data, 1) } else { binary.BigEndian.PutUint16(data, 0) } msg := NewMessage(T_ISTTY_REPORT, data) if err := r.writeMsg(msg); err != nil { return err } return nil } func (r *RemoteCli) readLoop() { buf := bufio.NewReader(r.conn) for { msg, err := ReadMessage(buf) if err != nil { break } switch msg.Type { case T_ERAW: r.raw.Exit() case T_RAW: r.raw.Enter() case T_DATA: os.Stdout.Write(msg.Data) } } } func (r *RemoteCli) ServeBy(source io.Reader) error { if err := r.init(); err != nil { return err } go func() { defer r.Close() for { n, _ := io.Copy(r, source) if n == 0 { break } } }() defer r.raw.Exit() r.readLoop() return nil } func (r *RemoteCli) Close() { r.writeMsg(NewMessage(T_EOF, nil)) } func (r *RemoteCli) Serve() error { return r.ServeBy(os.Stdin) } func ListenRemote(n, addr string, cfg *Config, h func(*Instance), onListen ...func(net.Listener) error) error { ln, err := net.Listen(n, addr) if err != nil { return err } if len(onListen) > 0 { if err := onListen[0](ln); err != nil { return err } } for { conn, err := ln.Accept() if err != nil { break } go func() { defer conn.Close() rl, err := HandleConn(*cfg, conn) if err != nil { return } h(rl) }() } return nil } func HandleConn(cfg Config, conn net.Conn) (*Instance, error) { r, err := NewRemoteSvr(conn) if err != nil { return nil, err } r.HandleConfig(&cfg) rl, err := NewEx(&cfg) if err != nil { return nil, err } return rl, nil } func DialRemote(n, addr string) error { conn, err := net.Dial(n, addr) if err != nil { return err } defer conn.Close() cli, err := NewRemoteCli(conn) if err != nil { return err } return cli.Serve() } golang-github-wader-readline-0.0~git20220118.692d84c/runebuf.go000066400000000000000000000255631417137650100235220ustar00rootroot00000000000000package readline import ( "bufio" "bytes" "io" "strconv" "strings" "sync" ) type runeBufferBck struct { buf []rune idx int } type RuneBuffer struct { buf []rune idx int prompt []rune w io.Writer hadClean bool interactive bool cfg *Config width int bck *runeBufferBck offset string lastKill []rune lastChangeIdx int OnChange func(pos int, buf []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() { r.Refresh(func() { if r.bck == nil { return } r.buf = r.bck.buf r.idx = r.bck.idx }) } func NewRuneBuffer(w io.Writer, prompt string, cfg *Config, width int) *RuneBuffer { rb := &RuneBuffer{ w: w, interactive: cfg.useInteractive(), cfg: cfg, width: width, } rb.SetPrompt(prompt) return rb } 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]) return rs } rs := make([]rune, -i) copy(rs, r.buf[r.idx+i:r.idx]) return rs } 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) } func (r *RuneBuffer) MoveToLineStart() { r.Refresh(func() { if r.idx == 0 { return } r.idx = 0 }) } func (r *RuneBuffer) MoveBackward() { r.Refresh(func() { if r.idx == 0 { return } r.idx-- }) } func (r *RuneBuffer) WriteString(s string) { r.WriteRunes([]rune(s)) } func (r *RuneBuffer) WriteRune(s rune) { r.WriteRunes([]rune{s}) } func (r *RuneBuffer) WriteRunes(s []rune) { r.Refresh(func() { tail := append(s, r.buf[r.idx:]...) r.buf = append(r.buf[:r.idx], tail...) r.idx += len(s) }) } func (r *RuneBuffer) MoveForward() { r.Refresh(func() { if r.idx == len(r.buf) { return } r.idx++ }) } func (r *RuneBuffer) IsCursorInEnd() bool { r.Lock() defer r.Unlock() return r.idx == len(r.buf) } func (r *RuneBuffer) Replace(ch rune) { r.Refresh(func() { r.buf[r.idx] = ch }) } func (r *RuneBuffer) Erase() { r.Refresh(func() { r.idx = 0 r.pushKill(r.buf[:]) r.buf = r.buf[:0] }) } func (r *RuneBuffer) Delete() (success bool) { r.Refresh(func() { 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 }) return } func (r *RuneBuffer) DeleteWord() { if r.idx == len(r.buf) { return } init := r.idx for init < len(r.buf) && IsWordBreak(r.buf[init]) { init++ } 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:]...) }) return } } r.Kill() } func (r *RuneBuffer) MoveToPrevWord() (success bool) { r.Refresh(func() { if r.idx == 0 { return } for i := r.idx - 1; i > 0; i-- { if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) { r.idx = i success = true return } } r.idx = 0 success = true }) return } func (r *RuneBuffer) KillFront() { r.Refresh(func() { if r.idx == 0 { return } 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] }) } func (r *RuneBuffer) Kill() { r.Refresh(func() { r.pushKill(r.buf[r.idx:]) r.buf = r.buf[:r.idx] }) } func (r *RuneBuffer) Transpose() { r.Refresh(func() { if len(r.buf) == 1 { r.idx++ } if len(r.buf) < 2 { return } if r.idx == 0 { r.idx = 1 } else if r.idx >= len(r.buf) { r.idx = len(r.buf) - 1 } r.buf[r.idx], r.buf[r.idx-1] = r.buf[r.idx-1], r.buf[r.idx] r.idx++ }) } func (r *RuneBuffer) MoveToNextWord() { r.Refresh(func() { for i := r.idx + 1; i < len(r.buf); i++ { if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) { r.idx = i return } } r.idx = len(r.buf) }) } func (r *RuneBuffer) MoveToEndWord() { r.Refresh(func() { // already at the end, so do nothing if r.idx == len(r.buf) { return } // if we are at the end of a word already, go to next if !IsWordBreak(r.buf[r.idx]) && IsWordBreak(r.buf[r.idx+1]) { r.idx++ } // keep going until at the end of a word for i := r.idx + 1; i < len(r.buf); i++ { if IsWordBreak(r.buf[i]) && !IsWordBreak(r.buf[i-1]) { r.idx = i - 1 return } } r.idx = len(r.buf) }) } func (r *RuneBuffer) BackEscapeWord() { r.Refresh(func() { if r.idx == 0 { return } for i := r.idx - 1; i >= 0; i-- { if i == 0 || (IsWordBreak(r.buf[i-1])) && !IsWordBreak(r.buf[i]) { r.pushKill(r.buf[i:r.idx]) r.buf = append(r.buf[:i], r.buf[r.idx:]...) r.idx = i return } } r.buf = r.buf[:0] r.idx = 0 }) } 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 { return } r.idx-- r.buf = append(r.buf[:r.idx], r.buf[r.idx+1:]...) }) } func (r *RuneBuffer) MoveToLineEnd() { r.Refresh(func() { if r.idx == len(r.buf) { return } r.idx = len(r.buf) }) } func (r *RuneBuffer) LineCount(width int) int { if width == -1 { width = r.width } return LineCount(width, runes.WidthAll(r.buf)+r.PromptLen()) } func (r *RuneBuffer) MoveTo(ch rune, prevChar, reverse bool) (success bool) { r.Refresh(func() { if reverse { for i := r.idx - 1; i >= 0; i-- { if r.buf[i] == ch { r.idx = i if prevChar { r.idx++ } success = true return } } return } for i := r.idx + 1; i < len(r.buf); i++ { if r.buf[i] == ch { r.idx = i if prevChar { r.idx-- } success = true return } } }) return } func (r *RuneBuffer) isInLineEdge() bool { if isWindows { return false } sp := r.getSplitByLine(r.buf) return len(sp[len(sp)-1]) == 0 } func (r *RuneBuffer) getSplitByLine(rs []rune) []string { 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 } func (r *RuneBuffer) CursorLineCount() int { return r.LineCount(r.width) - r.IdxLine(r.width) } func (r *RuneBuffer) Refresh(f func()) { r.Lock() defer r.Unlock() prevIdx := r.idx prevBuf := append([]rune{}, r.buf...) if !r.interactive { if f != nil { f() } return } r.clean() if f != nil { f() } r.print() if r.OnChange != nil { if !runes.Equal(r.buf, prevBuf) { if prevIdx != r.lastChangeIdx+1 { r.OnChange(prevIdx, prevBuf) } r.lastChangeIdx = prevIdx } } } 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 } func (r *RuneBuffer) output() []byte { buf := bytes.NewBuffer(nil) buf.WriteString(string(r.prompt)) if r.cfg.EnableMask && len(r.buf) > 0 { buf.Write([]byte(strings.Repeat(string(r.cfg.MaskRune), len(r.buf)-1))) if r.buf[len(r.buf)-1] == '\n' { buf.Write([]byte{'\n'}) } else { buf.Write([]byte(string(r.cfg.MaskRune))) } if len(r.buf) > r.idx { buf.Write(r.getBackspaceSequence()) } } else { 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(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-- { 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"...) } else { // move input to the left of one buf = append(buf, '\b') } } return buf } func (r *RuneBuffer) Reset() []rune { ret := runes.Copy(r.buf) r.buf = r.buf[:0] r.idx = 0 return ret } func (r *RuneBuffer) calWidth(m int) int { if m > 0 { return runes.WidthAll(r.buf[r.idx : r.idx+m]) } return runes.WidthAll(r.buf[r.idx+m : r.idx]) } func (r *RuneBuffer) SetStyle(start, end int, style string) { if end < start { panic("end < start") } // goto start move := start - r.idx if move > 0 { r.w.Write([]byte(string(r.buf[r.idx : r.idx+move]))) } else { r.w.Write(bytes.Repeat([]byte("\b"), r.calWidth(move))) } r.w.Write([]byte("\033[" + style + "m")) r.w.Write([]byte(string(r.buf[start:end]))) r.w.Write([]byte("\033[0m")) // TODO: move back } func (r *RuneBuffer) SetWithIdx(idx int, buf []rune) { r.Refresh(func() { r.buf = buf r.idx = idx }) } func (r *RuneBuffer) Set(buf []rune) { r.SetWithIdx(len(buf), buf) } 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) if r.width == 0 { buf.WriteString(strings.Repeat("\r\b", len(r.buf)+r.promptLen())) buf.Write([]byte("\033[J")) } else { 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") } } buf.Flush() return } func (r *RuneBuffer) Clean() { r.Lock() r.clean() r.Unlock() } func (r *RuneBuffer) clean() { r.cleanWithIdxLine(r.idxLine(r.width)) } func (r *RuneBuffer) cleanWithIdxLine(idxLine int) { if r.hadClean || !r.interactive { return } r.hadClean = true r.cleanOutput(r.w, idxLine) } golang-github-wader-readline-0.0~git20220118.692d84c/runes.go000066400000000000000000000073771417137650100232130ustar00rootroot00000000000000package readline 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 } for i := 0; i < len(a); i++ { if a[i] != b[i] { return false } } return true } 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 !rs.EqualRune(r[i+j], sub[j], fold) { found = false break } } if found { return i } } 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 (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 !rs.EqualRune(r[i+j], sub[j], fold) { found = false break } } if found { return i } } return -1 } func (Runes) Index(r rune, rs []rune) int { for i := 0; i < len(rs); i++ { if rs[i] == r { return i } } return -1 } func (Runes) ColorFilter(r []rune) []rune { newr := make([]rune, 0, len(r)) for pos := 0; pos < len(r); pos++ { if r[pos] == '\033' && r[pos+1] == '[' { idx := runes.Index('m', r[pos+2:]) if idx == -1 { continue } pos += idx + 2 continue } newr = append(newr, r[pos]) } return newr } var zeroWidth = []*unicode.RangeTable{ unicode.Mn, unicode.Me, unicode.Cc, unicode.Cf, } var doubleWidth = []*unicode.RangeTable{ unicode.Han, unicode.Hangul, unicode.Hiragana, unicode.Katakana, } func (Runes) Width(r rune) int { if r == '\t' { return TabWidth } if unicode.IsOneOf(zeroWidth, r) { return 0 } if unicode.IsOneOf(doubleWidth, r) { return 2 } return 1 } func (Runes) WidthAll(r []rune) (length int) { for i := 0; i < len(r); i++ { length += runes.Width(r[i]) } return } func (Runes) Backspace(r []rune) []byte { return bytes.Repeat([]byte{'\b'}, runes.WidthAll(r)) } func (Runes) Copy(r []rune) []rune { n := make([]rune, len(r)) copy(n, r) 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 } return runes.Equal(r[:len(prefix)], prefix) } func (Runes) Aggregate(candicate [][]rune) (same []rune, size int) { for i := 0; i < len(candicate[0]); i++ { for j := 0; j < len(candicate)-1; j++ { if i >= len(candicate[j]) || i >= len(candicate[j+1]) { goto aggregate } if candicate[j][i] != candicate[j+1][i] { goto aggregate } } size = i + 1 } aggregate: if size > 0 { same = runes.Copy(candicate[0][:size]) for i := 0; i < len(candicate); i++ { n := runes.Copy(candicate[i]) copy(n, n[size:]) candicate[i] = n[:len(n)-size] } } return } func (Runes) TrimSpaceLeft(in []rune) []rune { firstIndex := len(in) for i, r := range in { if unicode.IsSpace(r) == false { firstIndex = i break } } return in[firstIndex:] } golang-github-wader-readline-0.0~git20220118.692d84c/runes/000077500000000000000000000000001417137650100226465ustar00rootroot00000000000000golang-github-wader-readline-0.0~git20220118.692d84c/runes/runes.go000066400000000000000000000050341417137650100243330ustar00rootroot00000000000000// deprecated. // see https://github.com/chzyer/readline/issues/43 // use github.com/chzyer/readline/runes.go package runes import ( "bytes" "unicode" ) func Equal(a, b []rune) bool { if len(a) != len(b) { return false } for i := 0; i < len(a); i++ { if a[i] != b[i] { return false } } return true } // Search in runes from end to front func IndexAllBck(r, sub []rune) 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] { found = false break } } if found { return i } } return -1 } // Search in runes from front to end func IndexAll(r, sub []rune) 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] { found = false break } } if found { return i } } return -1 } func Index(r rune, rs []rune) int { for i := 0; i < len(rs); i++ { if rs[i] == r { return i } } return -1 } func ColorFilter(r []rune) []rune { newr := make([]rune, 0, len(r)) for pos := 0; pos < len(r); pos++ { if r[pos] == '\033' && r[pos+1] == '[' { idx := Index('m', r[pos+2:]) if idx == -1 { continue } pos += idx + 2 continue } newr = append(newr, r[pos]) } return newr } var zeroWidth = []*unicode.RangeTable{ unicode.Mn, unicode.Me, unicode.Cc, unicode.Cf, } var doubleWidth = []*unicode.RangeTable{ unicode.Han, unicode.Hangul, unicode.Hiragana, unicode.Katakana, } func Width(r rune) int { if unicode.IsOneOf(zeroWidth, r) { return 0 } if unicode.IsOneOf(doubleWidth, r) { return 2 } return 1 } func WidthAll(r []rune) (length int) { for i := 0; i < len(r); i++ { length += Width(r[i]) } return } func Backspace(r []rune) []byte { return bytes.Repeat([]byte{'\b'}, WidthAll(r)) } func Copy(r []rune) []rune { n := make([]rune, len(r)) copy(n, r) return n } func HasPrefix(r, prefix []rune) bool { if len(r) < len(prefix) { return false } return Equal(r[:len(prefix)], prefix) } func Aggregate(candicate [][]rune) (same []rune, size int) { for i := 0; i < len(candicate[0]); i++ { for j := 0; j < len(candicate)-1; j++ { if i >= len(candicate[j]) || i >= len(candicate[j+1]) { goto aggregate } if candicate[j][i] != candicate[j+1][i] { goto aggregate } } size = i + 1 } aggregate: if size > 0 { same = Copy(candicate[0][:size]) for i := 0; i < len(candicate); i++ { n := Copy(candicate[i]) copy(n, n[size:]) candicate[i] = n[:len(n)-size] } } return } golang-github-wader-readline-0.0~git20220118.692d84c/runes/runes_test.go000066400000000000000000000025031417137650100253700ustar00rootroot00000000000000package runes import ( "reflect" "testing" ) type twidth struct { r []rune length int } func TestRuneWidth(t *testing.T) { runes := []twidth{ {[]rune("☭"), 1}, {[]rune("a"), 1}, {[]rune("你"), 2}, {ColorFilter([]rune("☭\033[13;1m你")), 3}, } for _, r := range runes { if w := WidthAll(r.r); w != r.length { t.Fatal("result not expect", r.r, r.length, w) } } } type tagg struct { r [][]rune e [][]rune length int } func TestAggRunes(t *testing.T) { runes := []tagg{ { [][]rune{[]rune("ab"), []rune("a"), []rune("abc")}, [][]rune{[]rune("b"), []rune(""), []rune("bc")}, 1, }, { [][]rune{[]rune("addb"), []rune("ajkajsdf"), []rune("aasdfkc")}, [][]rune{[]rune("ddb"), []rune("jkajsdf"), []rune("asdfkc")}, 1, }, { [][]rune{[]rune("ddb"), []rune("ajksdf"), []rune("aasdfkc")}, [][]rune{[]rune("ddb"), []rune("ajksdf"), []rune("aasdfkc")}, 0, }, { [][]rune{[]rune("ddb"), []rune("ddajksdf"), []rune("ddaasdfkc")}, [][]rune{[]rune("b"), []rune("ajksdf"), []rune("aasdfkc")}, 2, }, } for _, r := range runes { same, off := Aggregate(r.r) if off != r.length { t.Fatal("result not expect", off) } if len(same) != off { t.Fatal("result not expect", same) } if !reflect.DeepEqual(r.r, r.e) { t.Fatal("result not expect") } } } golang-github-wader-readline-0.0~git20220118.692d84c/runes_test.go000066400000000000000000000025141417137650100242360ustar00rootroot00000000000000package readline import ( "reflect" "testing" ) type twidth struct { r []rune length int } func TestRuneWidth(t *testing.T) { rs := []twidth{ {[]rune("☭"), 1}, {[]rune("a"), 1}, {[]rune("你"), 2}, {runes.ColorFilter([]rune("☭\033[13;1m你")), 3}, } for _, r := range rs { if w := runes.WidthAll(r.r); w != r.length { t.Fatal("result not expect", r.r, r.length, w) } } } type tagg struct { r [][]rune e [][]rune length int } func TestAggRunes(t *testing.T) { rs := []tagg{ { [][]rune{[]rune("ab"), []rune("a"), []rune("abc")}, [][]rune{[]rune("b"), []rune(""), []rune("bc")}, 1, }, { [][]rune{[]rune("addb"), []rune("ajkajsdf"), []rune("aasdfkc")}, [][]rune{[]rune("ddb"), []rune("jkajsdf"), []rune("asdfkc")}, 1, }, { [][]rune{[]rune("ddb"), []rune("ajksdf"), []rune("aasdfkc")}, [][]rune{[]rune("ddb"), []rune("ajksdf"), []rune("aasdfkc")}, 0, }, { [][]rune{[]rune("ddb"), []rune("ddajksdf"), []rune("ddaasdfkc")}, [][]rune{[]rune("b"), []rune("ajksdf"), []rune("aasdfkc")}, 2, }, } for _, r := range rs { same, off := runes.Aggregate(r.r) if off != r.length { t.Fatal("result not expect", off) } if len(same) != off { t.Fatal("result not expect", same) } if !reflect.DeepEqual(r.r, r.e) { t.Fatal("result not expect") } } } golang-github-wader-readline-0.0~git20220118.692d84c/search.go000066400000000000000000000061311417137650100233070ustar00rootroot00000000000000package readline import ( "bytes" "container/list" "fmt" "io" ) const ( S_STATE_FOUND = iota S_STATE_FAILING ) const ( S_DIR_BCK = iota S_DIR_FWD ) type opSearch struct { inMode bool state int dir int source *list.Element w io.Writer buf *RuneBuffer data []rune history *opHistory cfg *Config markStart int markEnd int width int } func newOpSearch(w io.Writer, buf *RuneBuffer, history *opHistory, cfg *Config, width int) *opSearch { return &opSearch{ w: w, buf: buf, cfg: cfg, history: history, width: width, } } func (o *opSearch) OnWidthChange(newWidth int) { o.width = newWidth } func (o *opSearch) IsSearchMode() bool { return o.inMode } func (o *opSearch) SearchBackspace() { if len(o.data) > 0 { o.data = o.data[:len(o.data)-1] o.search(true) } } func (o *opSearch) findHistoryBy(isNewSearch bool) (int, *list.Element) { if o.dir == S_DIR_BCK { return o.history.FindBck(isNewSearch, o.data, o.buf.idx) } return o.history.FindFwd(isNewSearch, o.data, o.buf.idx) } func (o *opSearch) search(isChange bool) bool { if len(o.data) == 0 { o.state = S_STATE_FOUND o.SearchRefresh(-1) return true } idx, elem := o.findHistoryBy(isChange) if elem == nil { o.SearchRefresh(-2) return false } o.history.current = elem item := o.history.showItem(o.history.current.Value) start, end := 0, 0 if o.dir == S_DIR_BCK { start, end = idx, idx+len(o.data) } else { start, end = idx, idx+len(o.data) idx += len(o.data) } o.buf.SetWithIdx(idx, item) o.markStart, o.markEnd = start, end o.SearchRefresh(idx) return true } func (o *opSearch) SearchChar(r rune) { o.data = append(o.data, r) o.search(true) } func (o *opSearch) SearchMode(dir int) bool { if o.width == 0 { return false } alreadyInMode := o.inMode o.inMode = true o.dir = dir o.source = o.history.current if alreadyInMode { o.search(false) } else { o.SearchRefresh(-1) } return true } func (o *opSearch) ExitSearchMode(revert bool) { if revert { o.history.current = o.source o.buf.Set(o.history.showItem(o.history.current.Value)) } o.markStart, o.markEnd = 0, 0 o.state = S_STATE_FOUND o.inMode = false o.source = nil o.data = nil } func (o *opSearch) SearchRefresh(x int) { if x == -2 { o.state = S_STATE_FAILING } else if x >= 0 { o.state = S_STATE_FOUND } if x < 0 { x = o.buf.idx } x = o.buf.CurrentWidth(x) x += o.buf.PromptLen() x = x % o.width if o.markStart > 0 { o.buf.SetStyle(o.markStart, o.markEnd, "4") } lineCnt := o.buf.CursorLineCount() buf := bytes.NewBuffer(nil) buf.Write(bytes.Repeat([]byte("\n"), lineCnt)) buf.WriteString("\033[J") if o.state == S_STATE_FAILING { buf.WriteString("failing ") } if o.dir == S_DIR_BCK { buf.WriteString("bck") } else if o.dir == S_DIR_FWD { buf.WriteString("fwd") } buf.WriteString("-i-search: ") buf.WriteString(string(o.data)) // keyword buf.WriteString("\033[4m \033[0m") // _ fmt.Fprintf(buf, "\r\033[%dA", lineCnt) // move prev if x > 0 { fmt.Fprintf(buf, "\033[%dC", x) // move forward } o.w.Write(buf.Bytes()) } golang-github-wader-readline-0.0~git20220118.692d84c/std.go000066400000000000000000000071171417137650100226410ustar00rootroot00000000000000package readline import ( "io" "os" "sync" "sync/atomic" ) var ( Stdin io.ReadCloser = os.Stdin Stdout io.WriteCloser = os.Stdout Stderr io.WriteCloser = os.Stderr ) var ( std *Instance stdOnce sync.Once ) // global instance will not submit history automatic func getInstance() *Instance { stdOnce.Do(func() { std, _ = NewEx(&Config{ DisableAutoSaveHistory: true, }) }) return std } // let readline load history from filepath // and try to persist history into disk // set fp to "" to prevent readline persisting history to disk // so the `AddHistory` will return nil error forever. func SetHistoryPath(fp string) { ins := getInstance() cfg := ins.Config.Clone() cfg.HistoryFile = fp ins.SetConfig(cfg) } // set auto completer to global instance func SetAutoComplete(completer AutoCompleter) { ins := getInstance() cfg := ins.Config.Clone() cfg.AutoComplete = completer ins.SetConfig(cfg) } // add history to global instance manually // raise error only if `SetHistoryPath` is set with a non-empty path func AddHistory(content string) error { ins := getInstance() return ins.SaveHistory(content) } func Password(prompt string) ([]byte, error) { ins := getInstance() return ins.ReadPassword(prompt) } // readline with global configs func Line(prompt string) (string, error) { ins := getInstance() 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.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 } golang-github-wader-readline-0.0~git20220118.692d84c/std_windows.go000066400000000000000000000002151417137650100244030ustar00rootroot00000000000000// +build windows package readline func init() { Stdin = NewRawReader() Stdout = NewANSIWriter(Stdout) Stderr = NewANSIWriter(Stderr) } golang-github-wader-readline-0.0~git20220118.692d84c/term.go000066400000000000000000000060601417137650100230120ustar00rootroot00000000000000// 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 solaris // Package terminal provides support functions for dealing with terminals, as // commonly found on UNIX systems. // // Putting a terminal into raw mode is the most common requirement: // // oldState, err := terminal.MakeRaw(0) // if err != nil { // panic(err) // } // defer terminal.Restore(0, oldState) package readline import ( "io" "syscall" ) // State contains the state of a terminal. type State struct { termios Termios } // IsTerminal returns true if the given file descriptor is a terminal. func IsTerminal(fd int) bool { _, err := getTermios(fd) return err == nil } // MakeRaw put the terminal connected to the given file descriptor into raw // mode and returns the previous state of the terminal so that it can be // restored. func MakeRaw(fd int) (*State, error) { var oldState State if termios, err := getTermios(fd); err != nil { return nil, err } else { oldState.termios = *termios } newState := oldState.termios // This attempts to replicate the behaviour documented for cfmakeraw in // the termios(3) manpage. newState.Iflag &^= syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON // newState.Oflag &^= syscall.OPOST newState.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN newState.Cflag &^= syscall.CSIZE | syscall.PARENB newState.Cflag |= syscall.CS8 newState.Cc[syscall.VMIN] = 1 newState.Cc[syscall.VTIME] = 0 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) { termios, err := getTermios(fd) if err != nil { return nil, err } 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 { 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) { oldState, err := getTermios(fd) if err != nil { return nil, err } newState := oldState newState.Lflag &^= syscall.ECHO newState.Lflag |= syscall.ICANON | syscall.ISIG newState.Iflag |= syscall.ICRNL if err := setTermios(fd, newState); err != nil { return nil, err } defer func() { setTermios(fd, oldState) }() var buf [16]byte var ret []byte for { n, err := syscall.Read(fd, buf[:]) if err != nil { return nil, err } if n == 0 { if len(ret) == 0 { return nil, io.EOF } break } if buf[n-1] == '\n' { n-- } ret = append(ret, buf[:n]...) if n < len(buf) { break } } return ret, nil } golang-github-wader-readline-0.0~git20220118.692d84c/term_bsd.go000066400000000000000000000013361417137650100236430ustar00rootroot00000000000000// 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 darwin dragonfly freebsd netbsd openbsd package readline import ( "syscall" "unsafe" ) 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 } golang-github-wader-readline-0.0~git20220118.692d84c/term_linux.go000066400000000000000000000017041417137650100242310ustar00rootroot00000000000000// Copyright 2013 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package 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 } golang-github-wader-readline-0.0~git20220118.692d84c/term_solaris.go000066400000000000000000000014221417137650100245430ustar00rootroot00000000000000// 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)) } golang-github-wader-readline-0.0~git20220118.692d84c/term_unix.go000066400000000000000000000012271417137650100240550ustar00rootroot00000000000000// 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 } golang-github-wader-readline-0.0~git20220118.692d84c/term_windows.go000066400000000000000000000104661417137650100245710ustar00rootroot00000000000000// 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 windows // Package terminal provides support functions for dealing with terminals, as // commonly found on UNIX systems. // // Putting a terminal into raw mode is the most common requirement: // // oldState, err := terminal.MakeRaw(0) // if err != nil { // panic(err) // } // defer terminal.Restore(0, oldState) package readline import ( "io" "syscall" "unsafe" ) const ( enableLineInput = 2 enableEchoInput = 4 enableProcessedInput = 1 enableWindowInput = 8 enableMouseInput = 16 enableInsertMode = 32 enableQuickEditMode = 64 enableExtendedFlags = 128 enableAutoPosition = 256 enableProcessedOutput = 1 enableWrapAtEolOutput = 2 ) var kernel32 = syscall.NewLazyDLL("kernel32.dll") var ( procGetConsoleMode = kernel32.NewProc("GetConsoleMode") procSetConsoleMode = kernel32.NewProc("SetConsoleMode") procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") ) type ( coord struct { x short y short } smallRect struct { left short top short right short bottom short } consoleScreenBufferInfo struct { size coord cursorPosition coord attributes word window smallRect maximumWindowSize coord } ) type State struct { mode uint32 } // IsTerminal returns true if the given file descriptor is a terminal. func IsTerminal(fd int) bool { var st uint32 r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) return r != 0 && e == 0 } // MakeRaw put the terminal connected to the given file descriptor into raw // mode and returns the previous state of the terminal so that it can be // restored. func MakeRaw(fd int) (*State, error) { var st uint32 _, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) if e != 0 { return nil, error(e) } raw := st &^ (enableEchoInput | enableProcessedInput | enableLineInput | enableProcessedOutput) _, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(raw), 0) if e != 0 { return nil, error(e) } return &State{st}, nil } // 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 st uint32 _, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) if e != 0 { return nil, error(e) } return &State{st}, nil } // Restore restores the terminal connected to the given file descriptor to a // previous state. func restoreTerm(fd int, state *State) error { _, _, err := syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(state.mode), 0) return err } // GetSize returns the dimensions of the given terminal. func GetSize(fd int) (width, height int, err error) { var info consoleScreenBufferInfo _, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&info)), 0) if e != 0 { return 0, 0, error(e) } return int(info.size.x), int(info.size.y), nil } // ReadPassword reads a line of input from a terminal without local echo. This // is commonly used for inputting passwords and other sensitive data. The slice // returned does not include the \n. func ReadPassword(fd int) ([]byte, error) { var st uint32 _, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) if e != 0 { return nil, error(e) } old := st st &^= (enableEchoInput) st |= (enableProcessedInput | enableLineInput | enableProcessedOutput) _, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(st), 0) if e != 0 { return nil, error(e) } defer func() { syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(old), 0) }() var buf [16]byte var ret []byte for { n, err := syscall.Read(syscall.Handle(fd), buf[:]) if err != nil { return nil, err } if n == 0 { if len(ret) == 0 { return nil, io.EOF } break } if buf[n-1] == '\n' { n-- } if n > 0 && buf[n-1] == '\r' { n-- } ret = append(ret, buf[:n]...) if n < len(buf) { break } } return ret, nil } golang-github-wader-readline-0.0~git20220118.692d84c/terminal.go000066400000000000000000000100271417137650100236540ustar00rootroot00000000000000package readline import ( "bufio" "fmt" "io" "strings" "sync" "sync/atomic" ) type Terminal struct { m sync.Mutex cfg *Config outchan chan rune closed int32 stopChan chan struct{} kickChan chan struct{} wg sync.WaitGroup isReading int32 sleeping int32 sizeChan chan string } func NewTerminal(cfg *Config) (*Terminal, error) { if err := cfg.Init(); err != nil { return nil, err } t := &Terminal{ cfg: cfg, kickChan: make(chan struct{}, 1), outchan: make(chan rune), stopChan: make(chan struct{}, 1), sizeChan: make(chan string, 1), } go t.ioloop() return t, nil } // SleepToResume will sleep myself, and return only if I'm resumed. func (t *Terminal) SleepToResume() { if !atomic.CompareAndSwapInt32(&t.sleeping, 0, 1) { return } defer atomic.StoreInt32(&t.sleeping, 0) t.ExitRawMode() ch := WaitForResume() SuspendMe() <-ch t.EnterRawMode() } func (t *Terminal) EnterRawMode() (err error) { return t.cfg.FuncMakeRaw() } func (t *Terminal) ExitRawMode() (err error) { return t.cfg.FuncExitRaw() } 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 } 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) } func (t *Terminal) PrintRune(r rune) { fmt.Fprintf(t.cfg.Stdout, "%c", r) } func (t *Terminal) Readline() *Operation { return NewOperation(t, t.cfg) } // return rune(0) if meet EOF func (t *Terminal) ReadRune() rune { ch, ok := <-t.outchan if !ok { return rune(0) } return ch } func (t *Terminal) IsReading() bool { return atomic.LoadInt32(&t.isReading) == 1 } func (t *Terminal) KickRead() { select { case t.kickChan <- struct{}{}: default: } } func (t *Terminal) ioloop() { t.wg.Add(1) defer func() { t.wg.Done() close(t.outchan) }() var ( isEscape bool isEscapeEx bool expectNextChar bool ) buf := bufio.NewReader(t.getStdin()) for { if !expectNextChar { atomic.StoreInt32(&t.isReading, 0) select { case <-t.kickChan: atomic.StoreInt32(&t.isReading, 1) case <-t.stopChan: return } } expectNextChar = false r, _, err := buf.ReadRune() if err != nil { if strings.Contains(err.Error(), "interrupted system call") { expectNextChar = true continue } break } if isEscape { isEscape = false if r == CharEscapeEx { expectNextChar = true isEscapeEx = true continue } r = escapeKey(r, buf) } else if isEscapeEx { isEscapeEx = false 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 switch r { case CharEsc: if t.cfg.VimMode { t.outchan <- r break } isEscape = true case CharInterrupt, CharEnter, CharCtrlJ, CharEOT: expectNextChar = false fallthrough default: t.outchan <- r } } } func (t *Terminal) Bell() { fmt.Fprintf(t, "%c", CharBell) } func (t *Terminal) Close() error { if atomic.SwapInt32(&t.closed, 1) != 0 { return nil } 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 } golang-github-wader-readline-0.0~git20220118.692d84c/undo.go000066400000000000000000000012221417137650100230030ustar00rootroot00000000000000package readline type undoEntry struct { pos int buf []rune } type opUndo struct { op *Operation stack []undoEntry } func newOpUndo(op *Operation) *opUndo { o := &opUndo{op: op} o.init() return o } func (o *opUndo) add(pos int, buf []rune) { o.stack = append(o.stack, undoEntry{pos: pos, buf: buf}) } func (o *opUndo) undo() { if len(o.stack) == 0 { return } e := o.stack[len(o.stack)-1] o.stack = o.stack[0 : len(o.stack)-1] o.op.buf.buf = e.buf o.op.buf.idx = e.pos o.op.buf.Refresh(nil) } func (o *opUndo) init() { o.stack = []undoEntry{ undoEntry{ pos: o.op.buf.idx, buf: append([]rune{}, o.op.buf.buf...), }, } } golang-github-wader-readline-0.0~git20220118.692d84c/utils.go000066400000000000000000000113211417137650100231770ustar00rootroot00000000000000package readline import ( "bufio" "bytes" "container/list" "fmt" "os" "strconv" "strings" "sync" "time" "unicode" ) var ( isWindows = false ) const ( CharLineStart = 1 CharBackward = 2 CharInterrupt = 3 CharEOT = 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 CharCtrl_ = 31 CharEsc = 27 CharEscapeEx = 91 CharBackspace = 127 ) //Extra virtual keys const ( MetaBackward rune = 0xE000 + iota /*-iota - 1*/ // private unicode space? MetaForward MetaDelete MetaBackspace MetaTranspose MetaShiftTab CharDelete ) // 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. func WaitForResume() chan struct{} { ch := make(chan struct{}) var wg sync.WaitGroup wg.Add(1) go func() { ticker := time.NewTicker(10 * time.Millisecond) t := time.Now() wg.Done() for { now := <-ticker.C if now.Sub(t) > 100*time.Millisecond { break } t = now } ticker.Stop() ch <- struct{}{} }() wg.Wait() return ch } func Restore(fd int, state *State) error { err := restoreTerm(fd, state) if err != nil { // errno 0 means everything is ok :) if err.Error() == "errno 0" { return nil } else { return err } } return nil } func IsPrintable(key rune) bool { isInSurrogateArea := key >= 0xd800 && key <= 0xdbff return key >= 32 && !isInSurrogateArea } // translate Esc[X func escapeExKey(key *escapeKeyPair) rune { var r rune switch key.typ { case 'Z': r = MetaShiftTab case 'D': r = CharBackward case 'C': r = CharForward case 'A': r = CharPrev case 'B': r = CharNext case 'H': r = CharLineStart case 'F': r = CharLineEnd 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 { case 'b': r = MetaBackward case 'f': r = MetaForward case 'd': r = MetaDelete case CharTranspose: r = MetaTranspose case CharBackspace: r = MetaBackspace case 'O': d, _, _ := reader.ReadRune() switch d { case 'H': r = CharLineStart case 'F': r = CharLineEnd default: reader.UnreadRune() } case CharEsc: } return r } func SplitByLine(start, screenWidth int, rs []rune) []string { var ret []string buf := bytes.NewBuffer(nil) currentWidth := start for _, r := range rs { w := runes.Width(r) currentWidth += w buf.WriteRune(r) if currentWidth >= screenWidth { ret = append(ret, buf.String()) buf.Reset() currentWidth = 0 } } ret = append(ret, buf.String()) return ret } // calculate how many lines for N character func LineCount(screenWidth, w int) int { r := w / screenWidth if w%screenWidth != 0 { r++ } return r } func IsWordBreak(i rune) bool { switch { case i >= 'a' && i <= 'z': case i >= 'A' && i <= 'Z': case i >= '0' && i <= '9': default: return true } return false } func GetInt(s []string, def int) int { if len(s) == 0 { return def } c, err := strconv.Atoi(s[0]) if err != nil { return def } return c } type RawMode struct { state *State } func (r *RawMode) Enter() (err error) { r.state, err = MakeRaw(GetStdin()) return err } func (r *RawMode) Exit() error { if r.state == nil { return nil } 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() } golang-github-wader-readline-0.0~git20220118.692d84c/utils_test.go000066400000000000000000000000211417137650100242310ustar00rootroot00000000000000package readline golang-github-wader-readline-0.0~git20220118.692d84c/utils_unix.go000066400000000000000000000027741417137650100242560ustar00rootroot00000000000000// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd solaris package readline import ( "io" "os" "os/signal" "sync" "syscall" ) type winsize struct { Row uint16 Col uint16 Xpixel uint16 Ypixel uint16 } // SuspendMe use to send suspend signal to myself, when we in the raw mode. // For OSX it need to send to parent's pid // For Linux it need to send to myself func SuspendMe() { p, _ := os.FindProcess(os.Getppid()) p.Signal(syscall.SIGTSTP) p, _ = os.FindProcess(os.Getpid()) p.Signal(syscall.SIGTSTP) } // get width of the terminal func getWidth(stdoutFd int) int { cols, _, err := GetSize(stdoutFd) if err != nil { return -1 } return cols } func GetScreenWidth() int { w := getWidth(syscall.Stdout) if w < 0 { w = getWidth(syscall.Stderr) } return w } // ClearScreen clears the console screen func ClearScreen(w io.Writer) (int, error) { return w.Write([]byte("\033[H")) } func DefaultIsTerminal() bool { return IsTerminal(syscall.Stdin) && (IsTerminal(syscall.Stdout) || IsTerminal(syscall.Stderr)) } func GetStdin() int { return syscall.Stdin } // ----------------------------------------------------------------------------- var ( widthChange sync.Once widthChangeCallback func() ) func DefaultOnWidthChanged(f func()) { widthChangeCallback = f widthChange.Do(func() { ch := make(chan os.Signal, 1) signal.Notify(ch, syscall.SIGWINCH) go func() { for { _, ok := <-ch if !ok { break } widthChangeCallback() } }() }) } golang-github-wader-readline-0.0~git20220118.692d84c/utils_windows.go000066400000000000000000000010521417137650100247510ustar00rootroot00000000000000// +build windows package readline import ( "io" "syscall" ) func SuspendMe() { } func GetStdin() int { return int(syscall.Stdin) } func init() { isWindows = true } // get width of the terminal func GetScreenWidth() int { info, _ := GetConsoleScreenBufferInfo() if info == nil { return -1 } return int(info.dwSize.x) } // ClearScreen clears the console screen func ClearScreen(_ io.Writer) error { return SetConsoleCursorPosition(&_COORD{0, 0}) } func DefaultIsTerminal() bool { return true } func DefaultOnWidthChanged(func()) { } golang-github-wader-readline-0.0~git20220118.692d84c/vim.go000066400000000000000000000054251417137650100226420ustar00rootroot00000000000000package readline const ( VIM_NORMAL = iota VIM_INSERT VIM_VISUAL ) type opVim struct { cfg *Config op *Operation vimMode int } func newVimMode(op *Operation) *opVim { ov := &opVim{ cfg: op.cfg, op: op, } ov.SetVimMode(ov.cfg.VimMode) return ov } func (o *opVim) SetVimMode(on bool) { if o.cfg.VimMode && !on { // turn off o.ExitVimMode() } o.cfg.VimMode = on o.vimMode = VIM_INSERT } func (o *opVim) ExitVimMode() { o.vimMode = VIM_INSERT } func (o *opVim) IsEnableVimMode() bool { return o.cfg.VimMode } func (o *opVim) handleVimNormalMovement(r rune, readNext func() rune) (t rune, handled bool) { rb := o.op.buf handled = true switch r { case 'h': t = CharBackward case 'j': t = CharNext case 'k': t = CharPrev case 'l': t = CharForward case '0', '^': rb.MoveToLineStart() case '$': rb.MoveToLineEnd() case 'x': rb.Delete() if rb.IsCursorInEnd() { rb.MoveBackward() } case 'r': rb.Replace(readNext()) case 'd': next := readNext() switch next { case 'd': rb.Erase() case 'w': rb.DeleteWord() case 'h': rb.Backspace() case 'l': rb.Delete() } case 'p': rb.Yank() case 'b', 'B': rb.MoveToPrevWord() case 'w', 'W': rb.MoveToNextWord() case 'e', 'E': rb.MoveToEndWord() case 'f', 'F', 't', 'T': next := readNext() prevChar := r == 't' || r == 'T' reverse := r == 'F' || r == 'T' switch next { case CharEsc: default: rb.MoveTo(next, prevChar, reverse) } default: return r, false } return t, true } func (o *opVim) handleVimNormalEnterInsert(r rune, readNext func() rune) (t rune, handled bool) { rb := o.op.buf handled = true switch r { case 'i': case 'I': rb.MoveToLineStart() case 'a': rb.MoveForward() case 'A': rb.MoveToLineEnd() case 's': rb.Delete() case 'S': rb.Erase() case 'c': next := readNext() switch next { case 'c': rb.Erase() case 'w': rb.DeleteWord() case 'h': rb.Backspace() case 'l': rb.Delete() } default: return r, false } o.EnterVimInsertMode() return } func (o *opVim) HandleVimNormal(r rune, readNext func() rune) (t rune) { switch r { case CharEnter, CharInterrupt: o.ExitVimMode() return r } if r, handled := o.handleVimNormalMovement(r, readNext); handled { return r } if r, handled := o.handleVimNormalEnterInsert(r, readNext); handled { return r } // invalid operation o.op.t.Bell() return 0 } func (o *opVim) EnterVimInsertMode() { o.vimMode = VIM_INSERT } func (o *opVim) ExitVimInsertMode() { o.vimMode = VIM_NORMAL } func (o *opVim) HandleVim(r rune, readNext func() rune) rune { if o.vimMode == VIM_NORMAL { return o.HandleVimNormal(r, readNext) } if r == CharEsc { o.ExitVimInsertMode() return 0 } switch o.vimMode { case VIM_INSERT: return r case VIM_VISUAL: } return r } golang-github-wader-readline-0.0~git20220118.692d84c/windows_api.go000066400000000000000000000057541417137650100243770ustar00rootroot00000000000000// +build windows package readline import ( "reflect" "syscall" "unsafe" ) var ( kernel = NewKernel() stdout = uintptr(syscall.Stdout) stdin = uintptr(syscall.Stdin) ) type Kernel struct { SetConsoleCursorPosition, SetConsoleTextAttribute, FillConsoleOutputCharacterW, FillConsoleOutputAttribute, ReadConsoleInputW, GetConsoleScreenBufferInfo, GetConsoleCursorInfo, GetStdHandle CallFunc } type short int16 type word uint16 type dword uint32 type wchar uint16 type _COORD struct { x short y short } func (c *_COORD) ptr() uintptr { return uintptr(*(*int32)(unsafe.Pointer(c))) } const ( EVENT_KEY = 0x0001 EVENT_MOUSE = 0x0002 EVENT_WINDOW_BUFFER_SIZE = 0x0004 EVENT_MENU = 0x0008 EVENT_FOCUS = 0x0010 ) type _KEY_EVENT_RECORD struct { bKeyDown int32 wRepeatCount word wVirtualKeyCode word wVirtualScanCode word unicodeChar wchar dwControlKeyState dword } // KEY_EVENT_RECORD KeyEvent; // MOUSE_EVENT_RECORD MouseEvent; // WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent; // MENU_EVENT_RECORD MenuEvent; // FOCUS_EVENT_RECORD FocusEvent; type _INPUT_RECORD struct { EventType word Padding uint16 Event [16]byte } type _CONSOLE_SCREEN_BUFFER_INFO struct { dwSize _COORD dwCursorPosition _COORD wAttributes word srWindow _SMALL_RECT dwMaximumWindowSize _COORD } type _SMALL_RECT struct { left short top short right short bottom short } type _CONSOLE_CURSOR_INFO struct { dwSize dword bVisible bool } type CallFunc func(u ...uintptr) error func NewKernel() *Kernel { k := &Kernel{} kernel32 := syscall.NewLazyDLL("kernel32.dll") v := reflect.ValueOf(k).Elem() t := v.Type() for i := 0; i < t.NumField(); i++ { name := t.Field(i).Name f := kernel32.NewProc(name) v.Field(i).Set(reflect.ValueOf(k.Wrap(f))) } return k } func (k *Kernel) Wrap(p *syscall.LazyProc) CallFunc { return func(args ...uintptr) error { var r0 uintptr var e1 syscall.Errno size := uintptr(len(args)) if len(args) <= 3 { buf := make([]uintptr, 3) copy(buf, args) r0, _, e1 = syscall.Syscall(p.Addr(), size, buf[0], buf[1], buf[2]) } else { buf := make([]uintptr, 6) copy(buf, args) r0, _, e1 = syscall.Syscall6(p.Addr(), size, buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], ) } if int(r0) == 0 { if e1 != 0 { return error(e1) } else { return syscall.EINVAL } } return nil } } func GetConsoleScreenBufferInfo() (*_CONSOLE_SCREEN_BUFFER_INFO, error) { t := new(_CONSOLE_SCREEN_BUFFER_INFO) err := kernel.GetConsoleScreenBufferInfo( stdout, uintptr(unsafe.Pointer(t)), ) return t, err } func GetConsoleCursorInfo() (*_CONSOLE_CURSOR_INFO, error) { t := new(_CONSOLE_CURSOR_INFO) err := kernel.GetConsoleCursorInfo(stdout, uintptr(unsafe.Pointer(t))) return t, err } func SetConsoleCursorPosition(c *_COORD) error { return kernel.SetConsoleCursorPosition(stdout, c.ptr()) }