github.com/elves/elvish@v0.15.0/pkg/cli/loop.go (about) 1 package cli 2 3 import "sync" 4 5 // Buffer size of the input channel. The value is chosen for no particular 6 // reason. 7 const inputChSize = 128 8 9 // A generic main loop manager. 10 type loop struct { 11 inputCh chan event 12 handleCb handleCb 13 14 redrawCb redrawCb 15 16 redrawCh chan struct{} 17 redrawFull bool 18 redrawMutex *sync.Mutex 19 20 returnCh chan loopReturn 21 } 22 23 type loopReturn struct { 24 buffer string 25 err error 26 } 27 28 // A placeholder type for events. 29 type event interface{} 30 31 // Callback for redrawing the editor UI to the terminal. 32 type redrawCb func(flag redrawFlag) 33 34 func dummyRedrawCb(redrawFlag) {} 35 36 // Flag to redrawCb. 37 type redrawFlag uint 38 39 // Bit flags for redrawFlag. 40 const ( 41 // fullRedraw signals a "full redraw". This is set on the first RedrawCb 42 // call or when Redraw has been called with full = true. 43 fullRedraw redrawFlag = 1 << iota 44 // finalRedraw signals that this is the final redraw in the event loop. 45 finalRedraw 46 ) 47 48 // Callback for handling a terminal event. 49 type handleCb func(event) 50 51 func dummyHandleCb(event) {} 52 53 // newLoop creates a new Loop instance. 54 func newLoop() *loop { 55 return &loop{ 56 inputCh: make(chan event, inputChSize), 57 handleCb: dummyHandleCb, 58 redrawCb: dummyRedrawCb, 59 60 redrawCh: make(chan struct{}, 1), 61 redrawFull: false, 62 redrawMutex: new(sync.Mutex), 63 64 returnCh: make(chan loopReturn, 1), 65 } 66 } 67 68 // HandleCb sets the handle callback. It must be called before any Read call. 69 func (lp *loop) HandleCb(cb handleCb) { 70 lp.handleCb = cb 71 } 72 73 // RedrawCb sets the redraw callback. It must be called before any Read call. 74 func (lp *loop) RedrawCb(cb redrawCb) { 75 lp.redrawCb = cb 76 } 77 78 // Redraw requests a redraw. If full is true, a full redraw is requested. It 79 // never blocks. 80 func (lp *loop) Redraw(full bool) { 81 lp.redrawMutex.Lock() 82 defer lp.redrawMutex.Unlock() 83 if full { 84 lp.redrawFull = true 85 } 86 select { 87 case lp.redrawCh <- struct{}{}: 88 default: 89 } 90 } 91 92 // Input provides an input event. It may block if the internal event buffer is 93 // full. 94 func (lp *loop) Input(ev event) { 95 lp.inputCh <- ev 96 } 97 98 // Return requests the main loop to return. It never blocks. If Return has been 99 // called before during the current loop iteration, it has no effect. 100 func (lp *loop) Return(buffer string, err error) { 101 select { 102 case lp.returnCh <- loopReturn{buffer, err}: 103 default: 104 } 105 } 106 107 // HasReturned returns whether Return has been called during the current loop 108 // iteration. 109 func (lp *loop) HasReturned() bool { 110 return len(lp.returnCh) == 1 111 } 112 113 // Run runs the event loop, until the Return method is called. It is generic 114 // and delegates all concrete work to callbacks. It is fully serial: it does 115 // not spawn any goroutines and never calls two callbacks in parallel, so the 116 // callbacks may manipulate shared states without synchronization. 117 func (lp *loop) Run() (buffer string, err error) { 118 for { 119 var flag redrawFlag 120 if lp.extractRedrawFull() { 121 flag |= fullRedraw 122 } 123 lp.redrawCb(flag) 124 select { 125 case event := <-lp.inputCh: 126 // Consume all events in the channel to minimize redraws. 127 consumeAllEvents: 128 for { 129 lp.handleCb(event) 130 select { 131 case ret := <-lp.returnCh: 132 lp.redrawCb(finalRedraw) 133 return ret.buffer, ret.err 134 default: 135 } 136 select { 137 case event = <-lp.inputCh: 138 // Continue the loop of consuming all events. 139 default: 140 break consumeAllEvents 141 } 142 } 143 case ret := <-lp.returnCh: 144 lp.redrawCb(finalRedraw) 145 return ret.buffer, ret.err 146 case <-lp.redrawCh: 147 } 148 } 149 } 150 151 func (lp *loop) extractRedrawFull() bool { 152 lp.redrawMutex.Lock() 153 defer lp.redrawMutex.Unlock() 154 155 full := lp.redrawFull 156 lp.redrawFull = false 157 return full 158 }