package 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: 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() }
package 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 }
package 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 }
package 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 }
package 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 } 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) }) 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.t.KickRead() o.buf.Refresh(nil) // to refresh the line continue // ignore this rune } } if r == 0 { // io.EOF if o.buf.Len() == 0 { o.buf.Clean() 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 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 } case CharBackward: o.buf.MoveBackward() case CharForward: o.buf.MoveForward() case CharPrev: buf := o.history.Prev() if buf != nil { o.buf.Set(buf) } else { o.t.Bell() } case CharNext: buf, ok := o.history.Next() if ok { o.buf.Set(buf) } else { o.t.Bell() } case CharDelete: 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} 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.buf.Refresh(nil) } else if o.IsInCompleteMode() { if !keepInCompleteMode { o.ExitCompleteMode(false) 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() { select { case o.errchan <- io.EOF: default: } 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 }
package 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, } }
// 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. // if there has a pending reading operation, that reading will be interrupted. // so you can capture the signal and call Instance.Close(), it's thread-safe. func (i *Instance) Close() error { i.Config.Stdin.Close() i.Operation.Close() if err := i.Terminal.Close(); err != nil { return err } return nil } // call CaptureExitSignal when you want readline exit gracefully. func (i *Instance) CaptureExitSignal() { CaptureExitSignal(func() { i.Close() }) } func (i *Instance) Clean() { i.Operation.Clean() } 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() }
package 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() }
package 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 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 !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) { r.pushKill(r.buf[i:r.idx]) r.buf = append(r.buf[:i], r.buf[r.idx:]...) r.idx = i return } } 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() if !r.interactive { if f != nil { f() } return } r.clean() if f != nil { f() } r.print() } func (r *RuneBuffer) SetOffset(offset string) { r.Lock() r.offset = offset r.Unlock() } func (r *RuneBuffer) print() { r.w.Write(r.output()) r.hadClean = false } 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-- { // move input to the left of one buf = append(buf, '\b') if sep[i] { // up one line, go to the start of the line and move cursor right to the end (r.width) buf = append(buf, "\033[A\r"+"\033["+strconv.Itoa(r.width)+"C"...) } } return buf } func (r *RuneBuffer) Reset() []rune { ret := runes.Copy(r.buf) r.buf = r.buf[:0] 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) }
package 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:] }
package 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()) }
package 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 }
// 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 aix darwin dragonfly freebsd linux,!appengine netbsd openbsd os400 solaris // Package terminal provides support functions for dealing with terminals, as // commonly found on UNIX systems. // // 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 }
// 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 }
// 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 }
package 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 isEscapeSS3 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 } else if r == CharO { // ^]O expectNextChar = true isEscapeSS3 = 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 } } else if isEscapeSS3 { isEscapeSS3 = false if key := readEscKey(r, buf); key != nil { r = escapeSS3Key(key) } 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, CharDelete: 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 }
package readline import ( "bufio" "bytes" "container/list" "fmt" "os" "os/signal" "strconv" "strings" "sync" "syscall" "time" "unicode" ) var ( isWindows = false ) const ( CharLineStart = 1 CharBackward = 2 CharInterrupt = 3 CharDelete = 4 CharLineEnd = 5 CharForward = 6 CharBell = 7 CharCtrlH = 8 CharTab = 9 CharCtrlJ = 10 CharKill = 11 CharCtrlL = 12 CharEnter = 13 CharNext = 14 CharPrev = 16 CharBckSearch = 18 CharFwdSearch = 19 CharTranspose = 20 CharCtrlU = 21 CharCtrlW = 23 CharCtrlY = 25 CharCtrlZ = 26 CharEsc = 27 CharO = 79 CharEscapeEx = 91 CharBackspace = 127 ) const ( MetaBackward rune = -iota - 1 MetaForward MetaDelete MetaBackspace MetaTranspose ) // WaitForResume need to call before current process got suspend. // It will run a ticker until a long duration is occurs, // which means this process is resumed. 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 '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 } // translate EscOX SS3 codes for up/down/etc. func escapeSS3Key(key *escapeKeyPair) rune { var r rune switch key.typ { case 'D': r = CharBackward case 'C': r = CharForward case 'A': r = CharPrev case 'B': r = CharNext case 'H': r = CharLineStart case 'F': r = CharLineEnd default: } return r } type escapeKeyPair struct { attr string typ rune } 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() } func CaptureExitSignal(f func()) { cSignal := make(chan os.Signal, 1) signal.Notify(cSignal, os.Interrupt, syscall.SIGTERM) go func() { for range cSignal { f() } }() }
// +build aix darwin dragonfly freebsd linux,!appengine netbsd openbsd os400 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() } }() }) }
package 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 }