Path: blob/main/vendor/github.com/chzyer/readline/operation.go
2875 views
package readline12import (3"errors"4"io"5"sync"6)78var (9ErrInterrupt = errors.New("Interrupt")10)1112type InterruptError struct {13Line []rune14}1516func (*InterruptError) Error() string {17return "Interrupted"18}1920type Operation struct {21m sync.Mutex22cfg *Config23t *Terminal24buf *RuneBuffer25outchan chan []rune26errchan chan error27w io.Writer2829history *opHistory30*opSearch31*opCompleter32*opPassword33*opVim34}3536func (o *Operation) SetBuffer(what string) {37o.buf.Set([]rune(what))38}3940type wrapWriter struct {41r *Operation42t *Terminal43target io.Writer44}4546func (w *wrapWriter) Write(b []byte) (int, error) {47if !w.t.IsReading() {48return w.target.Write(b)49}5051var (52n int53err error54)55w.r.buf.Refresh(func() {56n, err = w.target.Write(b)57})5859if w.r.IsSearchMode() {60w.r.SearchRefresh(-1)61}62if w.r.IsInCompleteMode() {63w.r.CompleteRefresh()64}65return n, err66}6768func NewOperation(t *Terminal, cfg *Config) *Operation {69width := cfg.FuncGetWidth()70op := &Operation{71t: t,72buf: NewRuneBuffer(t, cfg.Prompt, cfg, width),73outchan: make(chan []rune),74errchan: make(chan error, 1),75}76op.w = op.buf.w77op.SetConfig(cfg)78op.opVim = newVimMode(op)79op.opCompleter = newOpCompleter(op.buf.w, op, width)80op.opPassword = newOpPassword(op)81op.cfg.FuncOnWidthChanged(func() {82newWidth := cfg.FuncGetWidth()83op.opCompleter.OnWidthChange(newWidth)84op.opSearch.OnWidthChange(newWidth)85op.buf.OnWidthChange(newWidth)86})87go op.ioloop()88return op89}9091func (o *Operation) SetPrompt(s string) {92o.buf.SetPrompt(s)93}9495func (o *Operation) SetMaskRune(r rune) {96o.buf.SetMask(r)97}9899func (o *Operation) GetConfig() *Config {100o.m.Lock()101cfg := *o.cfg102o.m.Unlock()103return &cfg104}105106func (o *Operation) ioloop() {107for {108keepInSearchMode := false109keepInCompleteMode := false110r := o.t.ReadRune()111112if o.GetConfig().FuncFilterInputRune != nil {113var process bool114r, process = o.GetConfig().FuncFilterInputRune(r)115if !process {116o.t.KickRead()117o.buf.Refresh(nil) // to refresh the line118continue // ignore this rune119}120}121122if r == 0 { // io.EOF123if o.buf.Len() == 0 {124o.buf.Clean()125select {126case o.errchan <- io.EOF:127}128break129} else {130// if stdin got io.EOF and there is something left in buffer,131// let's flush them by sending CharEnter.132// And we will got io.EOF int next loop.133r = CharEnter134}135}136isUpdateHistory := true137138if o.IsInCompleteSelectMode() {139keepInCompleteMode = o.HandleCompleteSelect(r)140if keepInCompleteMode {141continue142}143144o.buf.Refresh(nil)145switch r {146case CharEnter, CharCtrlJ:147o.history.Update(o.buf.Runes(), false)148fallthrough149case CharInterrupt:150o.t.KickRead()151fallthrough152case CharBell:153continue154}155}156157if o.IsEnableVimMode() {158r = o.HandleVim(r, o.t.ReadRune)159if r == 0 {160continue161}162}163164switch r {165case CharBell:166if o.IsSearchMode() {167o.ExitSearchMode(true)168o.buf.Refresh(nil)169}170if o.IsInCompleteMode() {171o.ExitCompleteMode(true)172o.buf.Refresh(nil)173}174case CharTab:175if o.GetConfig().AutoComplete == nil {176o.t.Bell()177break178}179if o.OnComplete() {180keepInCompleteMode = true181} else {182o.t.Bell()183break184}185186case CharBckSearch:187if !o.SearchMode(S_DIR_BCK) {188o.t.Bell()189break190}191keepInSearchMode = true192case CharCtrlU:193o.buf.KillFront()194case CharFwdSearch:195if !o.SearchMode(S_DIR_FWD) {196o.t.Bell()197break198}199keepInSearchMode = true200case CharKill:201o.buf.Kill()202keepInCompleteMode = true203case MetaForward:204o.buf.MoveToNextWord()205case CharTranspose:206o.buf.Transpose()207case MetaBackward:208o.buf.MoveToPrevWord()209case MetaDelete:210o.buf.DeleteWord()211case CharLineStart:212o.buf.MoveToLineStart()213case CharLineEnd:214o.buf.MoveToLineEnd()215case CharBackspace, CharCtrlH:216if o.IsSearchMode() {217o.SearchBackspace()218keepInSearchMode = true219break220}221222if o.buf.Len() == 0 {223o.t.Bell()224break225}226o.buf.Backspace()227if o.IsInCompleteMode() {228o.OnComplete()229}230case CharCtrlZ:231o.buf.Clean()232o.t.SleepToResume()233o.Refresh()234case CharCtrlL:235ClearScreen(o.w)236o.Refresh()237case MetaBackspace, CharCtrlW:238o.buf.BackEscapeWord()239case CharCtrlY:240o.buf.Yank()241case CharEnter, CharCtrlJ:242if o.IsSearchMode() {243o.ExitSearchMode(false)244}245o.buf.MoveToLineEnd()246var data []rune247if !o.GetConfig().UniqueEditLine {248o.buf.WriteRune('\n')249data = o.buf.Reset()250data = data[:len(data)-1] // trim \n251} else {252o.buf.Clean()253data = o.buf.Reset()254}255o.outchan <- data256if !o.GetConfig().DisableAutoSaveHistory {257// ignore IO error258_ = o.history.New(data)259} else {260isUpdateHistory = false261}262case CharBackward:263o.buf.MoveBackward()264case CharForward:265o.buf.MoveForward()266case CharPrev:267buf := o.history.Prev()268if buf != nil {269o.buf.Set(buf)270} else {271o.t.Bell()272}273case CharNext:274buf, ok := o.history.Next()275if ok {276o.buf.Set(buf)277} else {278o.t.Bell()279}280case CharDelete:281if o.buf.Len() > 0 || !o.IsNormalMode() {282o.t.KickRead()283if !o.buf.Delete() {284o.t.Bell()285}286break287}288289// treat as EOF290if !o.GetConfig().UniqueEditLine {291o.buf.WriteString(o.GetConfig().EOFPrompt + "\n")292}293o.buf.Reset()294isUpdateHistory = false295o.history.Revert()296o.errchan <- io.EOF297if o.GetConfig().UniqueEditLine {298o.buf.Clean()299}300case CharInterrupt:301if o.IsSearchMode() {302o.t.KickRead()303o.ExitSearchMode(true)304break305}306if o.IsInCompleteMode() {307o.t.KickRead()308o.ExitCompleteMode(true)309o.buf.Refresh(nil)310break311}312o.buf.MoveToLineEnd()313o.buf.Refresh(nil)314hint := o.GetConfig().InterruptPrompt + "\n"315if !o.GetConfig().UniqueEditLine {316o.buf.WriteString(hint)317}318remain := o.buf.Reset()319if !o.GetConfig().UniqueEditLine {320remain = remain[:len(remain)-len([]rune(hint))]321}322isUpdateHistory = false323o.history.Revert()324o.errchan <- &InterruptError{remain}325default:326if o.IsSearchMode() {327o.SearchChar(r)328keepInSearchMode = true329break330}331o.buf.WriteRune(r)332if o.IsInCompleteMode() {333o.OnComplete()334keepInCompleteMode = true335}336}337338listener := o.GetConfig().Listener339if listener != nil {340newLine, newPos, ok := listener.OnChange(o.buf.Runes(), o.buf.Pos(), r)341if ok {342o.buf.SetWithIdx(newPos, newLine)343}344}345346o.m.Lock()347if !keepInSearchMode && o.IsSearchMode() {348o.ExitSearchMode(false)349o.buf.Refresh(nil)350} else if o.IsInCompleteMode() {351if !keepInCompleteMode {352o.ExitCompleteMode(false)353o.Refresh()354} else {355o.buf.Refresh(nil)356o.CompleteRefresh()357}358}359if isUpdateHistory && !o.IsSearchMode() {360// it will cause null history361o.history.Update(o.buf.Runes(), false)362}363o.m.Unlock()364}365}366367func (o *Operation) Stderr() io.Writer {368return &wrapWriter{target: o.GetConfig().Stderr, r: o, t: o.t}369}370371func (o *Operation) Stdout() io.Writer {372return &wrapWriter{target: o.GetConfig().Stdout, r: o, t: o.t}373}374375func (o *Operation) String() (string, error) {376r, err := o.Runes()377return string(r), err378}379380func (o *Operation) Runes() ([]rune, error) {381o.t.EnterRawMode()382defer o.t.ExitRawMode()383384listener := o.GetConfig().Listener385if listener != nil {386listener.OnChange(nil, 0, 0)387}388389o.buf.Refresh(nil) // print prompt390o.t.KickRead()391select {392case r := <-o.outchan:393return r, nil394case err := <-o.errchan:395if e, ok := err.(*InterruptError); ok {396return e.Line, ErrInterrupt397}398return nil, err399}400}401402func (o *Operation) PasswordEx(prompt string, l Listener) ([]byte, error) {403cfg := o.GenPasswordConfig()404cfg.Prompt = prompt405cfg.Listener = l406return o.PasswordWithConfig(cfg)407}408409func (o *Operation) GenPasswordConfig() *Config {410return o.opPassword.PasswordConfig()411}412413func (o *Operation) PasswordWithConfig(cfg *Config) ([]byte, error) {414if err := o.opPassword.EnterPasswordMode(cfg); err != nil {415return nil, err416}417defer o.opPassword.ExitPasswordMode()418return o.Slice()419}420421func (o *Operation) Password(prompt string) ([]byte, error) {422return o.PasswordEx(prompt, nil)423}424425func (o *Operation) SetTitle(t string) {426o.w.Write([]byte("\033[2;" + t + "\007"))427}428429func (o *Operation) Slice() ([]byte, error) {430r, err := o.Runes()431if err != nil {432return nil, err433}434return []byte(string(r)), nil435}436437func (o *Operation) Close() {438select {439case o.errchan <- io.EOF:440default:441}442o.history.Close()443}444445func (o *Operation) SetHistoryPath(path string) {446if o.history != nil {447o.history.Close()448}449o.cfg.HistoryFile = path450o.history = newOpHistory(o.cfg)451}452453func (o *Operation) IsNormalMode() bool {454return !o.IsInCompleteMode() && !o.IsSearchMode()455}456457func (op *Operation) SetConfig(cfg *Config) (*Config, error) {458op.m.Lock()459defer op.m.Unlock()460if op.cfg == cfg {461return op.cfg, nil462}463if err := cfg.Init(); err != nil {464return op.cfg, err465}466old := op.cfg467op.cfg = cfg468op.SetPrompt(cfg.Prompt)469op.SetMaskRune(cfg.MaskRune)470op.buf.SetConfig(cfg)471width := op.cfg.FuncGetWidth()472473if cfg.opHistory == nil {474op.SetHistoryPath(cfg.HistoryFile)475cfg.opHistory = op.history476cfg.opSearch = newOpSearch(op.buf.w, op.buf, op.history, cfg, width)477}478op.history = cfg.opHistory479480// SetHistoryPath will close opHistory which already exists481// so if we use it next time, we need to reopen it by `InitHistory()`482op.history.Init()483484if op.cfg.AutoComplete != nil {485op.opCompleter = newOpCompleter(op.buf.w, op, width)486}487488op.opSearch = cfg.opSearch489return old, nil490}491492func (o *Operation) ResetHistory() {493o.history.Reset()494}495496// if err is not nil, it just mean it fail to write to file497// other things goes fine.498func (o *Operation) SaveHistory(content string) error {499return o.history.New([]rune(content))500}501502func (o *Operation) Refresh() {503if o.t.IsReading() {504o.buf.Refresh(nil)505}506}507508func (o *Operation) Clean() {509o.buf.Clean()510}511512func FuncListener(f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)) Listener {513return &DumpListener{f: f}514}515516type DumpListener struct {517f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)518}519520func (d *DumpListener) OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {521return d.f(line, pos, key)522}523524type Listener interface {525OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)526}527528type Painter interface {529Paint(line []rune, pos int) []rune530}531532type defaultPainter struct{}533534func (p *defaultPainter) Paint(line []rune, _ int) []rune {535return line536}537538539