github.com/jmigpin/editor@v1.6.0/core/terminalio.go (about) 1 package core 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "sync" 8 9 "github.com/jmigpin/editor/ui" 10 "github.com/jmigpin/editor/util/evreg" 11 "github.com/jmigpin/editor/util/iout" 12 "github.com/jmigpin/editor/util/uiutil/event" 13 ) 14 15 //godebug:annotatefile 16 17 type TerminalIO interface { 18 Init(tf *TerminalFilter) 19 20 Read([]byte) (int, error) // input interface 21 AddToRead([]byte) // add input internally to be read 22 23 WriteOp(interface{}) error // accepted types: {[]byte,string} 24 25 Close() error 26 } 27 28 //---------- 29 //---------- 30 //---------- 31 32 type ERowTermIO struct { 33 erow *ERow 34 tf *TerminalFilter 35 36 inputReg *evreg.Regist // input events 37 38 input struct { 39 sync.Mutex 40 cond *sync.Cond 41 buf bytes.Buffer 42 closing bool 43 } 44 update struct { 45 sync.Mutex 46 updating bool 47 ops []interface{} 48 } 49 } 50 51 func NewERowTermIO(erow *ERow) *ERowTermIO { 52 tio := &ERowTermIO{erow: erow} 53 tio.input.cond = sync.NewCond(&tio.input) 54 return tio 55 } 56 57 func (tio *ERowTermIO) Init(tf *TerminalFilter) { 58 tio.tf = tf 59 tio.initInput() 60 } 61 62 func (tio *ERowTermIO) Close() error { 63 tio.inputReg.Unregister() 64 65 // signal to unblock waiting for a read 66 tio.input.Lock() 67 tio.input.closing = true 68 tio.input.cond.Signal() 69 tio.input.Unlock() 70 71 return nil 72 } 73 74 //---------- 75 76 func (tio *ERowTermIO) Read(b []byte) (int, error) { 77 tio.input.Lock() 78 79 for tio.input.buf.Len() == 0 && !tio.input.closing { 80 tio.input.cond.Wait() 81 } 82 defer tio.input.Unlock() 83 if tio.input.closing { 84 return 0, io.EOF 85 } 86 return tio.input.buf.Read(b) 87 } 88 89 func (tio *ERowTermIO) AddToRead(b []byte) { 90 tio.input.Lock() 91 defer tio.input.cond.Signal() 92 defer tio.input.Unlock() 93 tio.input.buf.Write(b) 94 } 95 96 //---------- 97 98 func (tio *ERowTermIO) WriteOp(op interface{}) error { 99 tio.updateWriteOp(op) 100 return nil 101 } 102 103 func (tio *ERowTermIO) updateWriteOp(op interface{}) { 104 tio.update.Lock() 105 defer tio.update.Unlock() 106 107 tio.appendOp(op) 108 109 if tio.update.updating { 110 return 111 } 112 tio.update.updating = true 113 114 tio.erow.Ed.UI.RunOnUIGoRoutine(func() { 115 tio.update.Lock() 116 defer tio.update.Unlock() 117 tio.update.updating = false 118 // clear ops at the end 119 defer func() { tio.update.ops = nil }() 120 121 for _, op := range tio.update.ops { 122 if err := tio.updateWriteOp2(op); err != nil { 123 tio.erow.Ed.Error(err) 124 } 125 } 126 }) 127 } 128 129 func (tio *ERowTermIO) updateWriteOp2(op interface{}) error { 130 ta := tio.tf.erow.Row.TextArea 131 switch t := op.(type) { 132 case []byte: 133 if err := ta.AppendBytesClearHistory(t); err != nil { 134 return err 135 } 136 case string: 137 switch t { 138 case "clear": 139 if err := ta.SetBytesClearHistory(nil); err != nil { 140 return err 141 } 142 default: 143 panic(fmt.Sprintf("todo: %v", t)) 144 } 145 default: 146 panic(fmt.Sprintf("todo: %v %T", t, t)) 147 } 148 return nil 149 } 150 151 func (tio *ERowTermIO) appendOp(op interface{}) { 152 o := &tio.update.ops 153 switch t := op.(type) { 154 case []byte: 155 // copy to avoid losing/overwriting content 156 b := iout.CopyBytes(t) 157 158 // performance: append to previous op if possible 159 l := len(*o) 160 if l > 0 { 161 last := &(*o)[l-1] 162 if lb, ok := (*last).([]byte); ok { 163 *last = append(lb, b...) 164 return 165 } 166 } 167 168 *o = append(*o, b) 169 default: 170 *o = append(*o, op) 171 } 172 } 173 174 //---------- 175 176 func (tio *ERowTermIO) initInput() { 177 ta := tio.erow.Row.TextArea 178 tio.inputReg = ta.EvReg.Add(ui.TextAreaInputEventId, tio.onTextAreaInputEvent) 179 } 180 181 func (tio *ERowTermIO) onTextAreaInputEvent(ev0 interface{}) { 182 ev := ev0.(*ui.TextAreaInputEvent) 183 b, handled := tio.eventToBytes(ev.Event) 184 if len(b) > 0 { 185 tio.AddToRead(b) 186 } 187 ev.ReplyHandled = handled 188 } 189 190 //---------- 191 192 func (tio *ERowTermIO) eventToBytes(ev interface{}) ([]byte, event.Handled) { 193 // util funcs 194 keyboardEvs := func() bool { 195 return tio.erow.terminalOpt.keyEvents 196 } 197 byteOut := func(v byte, ru rune) []byte { 198 b := []byte{v} 199 // also add to output 200 if ru != 0 { 201 b2 := []byte(string(ru)) 202 //_, _ = tio.tf.Write(b2) // send back to filter 203 _ = tio.WriteOp(b2) // add directly to output 204 } 205 return b 206 } 207 208 switch t := ev.(type) { 209 case *event.KeyDown: 210 if keyboardEvs() { 211 var b []byte 212 switch t.KeySym { 213 case event.KSymReturn: 214 b = byteOut('\n', '\n') 215 case event.KSymEscape: 216 b = []byte{27} 217 case event.KSymTab: 218 b = []byte{'\t'} 219 case event.KSymBackspace: 220 b = []byte{'\b'} 221 default: 222 b = []byte(string(t.Rune)) 223 } 224 return b, true 225 } 226 } 227 return nil, false 228 }