github.com/jmigpin/editor@v1.6.0/util/uiutil/widget/textedit.go (about) 1 package widget 2 3 import ( 4 "image" 5 6 "github.com/jmigpin/editor/util/evreg" 7 "github.com/jmigpin/editor/util/iout/iorw" 8 "github.com/jmigpin/editor/util/iout/iorw/rwedit" 9 "github.com/jmigpin/editor/util/iout/iorw/rwundo" 10 "github.com/jmigpin/editor/util/uiutil/event" 11 ) 12 13 //godebug:annotatefile 14 15 type TextEdit struct { 16 *Text 17 uiCtx UIContext 18 rwev *iorw.RWEvents 19 rwu *rwundo.RWUndo 20 ctx *rwedit.Ctx // ctx for rw editing utils (contains cursor) 21 RWEvReg *evreg.Register // the rwundo wraps the rwev, so on a write event callback, the undo data is not commited yet. It is incorrect to try to undo inside a write callback. If a rwev wraps rwundo, undoing will not trigger the outer rwev events, otherwise undoing would register as another undo event (cycle). 22 } 23 24 func NewTextEdit(uiCtx UIContext) *TextEdit { 25 t := NewText(uiCtx) 26 te := &TextEdit{Text: t, uiCtx: uiCtx} 27 28 te.rwev = iorw.NewRWEvents(te.Text.rw) 29 te.RWEvReg = &te.rwev.EvReg 30 te.RWEvReg.Add(iorw.RWEvIdWrite2, te.onWrite2) 31 32 hist := rwundo.NewHistory(200) 33 te.rwu = rwundo.NewRWUndo(te.rwev, hist) 34 35 te.ctx = rwedit.NewCtx() 36 te.ctx.RW = te.rwu 37 te.ctx.C = rwedit.NewTriggerCursor(te.onCursorChange) 38 te.ctx.Fns.Error = uiCtx.Error 39 te.ctx.Fns.GetPoint = te.GetPoint 40 te.ctx.Fns.GetIndex = te.GetIndex 41 te.ctx.Fns.LineHeight = te.LineHeight 42 te.ctx.Fns.LineCommentStr = func() string { return "" } // set by texteditx 43 te.ctx.Fns.MakeIndexVisible = te.MakeIndexVisible 44 te.ctx.Fns.Undo = te.Undo 45 te.ctx.Fns.Redo = te.Redo 46 te.ctx.Fns.SetClipboardData = te.uiCtx.SetClipboardData 47 te.ctx.Fns.GetClipboardData = func(i event.ClipboardIndex, fn func(string, error)) { 48 te.uiCtx.GetClipboardData(i, func(s string, err error) { 49 te.uiCtx.RunOnUIGoRoutine(func() { 50 fn(s, err) 51 }) 52 }) 53 } 54 55 return te 56 } 57 58 //---------- 59 60 func (te *TextEdit) RW() iorw.ReadWriterAt { 61 // TODO: returning rw with undo/events, differs from SetRW(), workaround is to use te.Text.RW() to get underlying rw 62 63 return te.ctx.RW 64 } 65 66 func (te *TextEdit) SetRW(rw iorw.ReadWriterAt) { 67 // TODO: setting basic rw (bytes), differs from RW() 68 69 te.Text.SetRW(rw) 70 te.rwev.ReadWriterAt = rw 71 } 72 73 func (te *TextEdit) SetRWFromMaster(m *TextEdit) { 74 te.SetRW(m.Text.rw) 75 te.rwu.History = m.rwu.History 76 } 77 78 //---------- 79 80 // Called when the changes are done on this textedit 81 func (te *TextEdit) onWrite2(ev interface{}) { 82 e := ev.(*iorw.RWEvWrite2) 83 if e.Changed { 84 te.contentChanged() 85 } 86 } 87 88 // Called when changes were made on another row 89 func (te *TextEdit) HandleRWWrite2(ev *iorw.RWEvWrite2) { 90 te.stableRuneOffset(&ev.RWEvWrite) 91 te.stableCursor(&ev.RWEvWrite) 92 if ev.Changed { 93 te.contentChanged() 94 } 95 } 96 97 //---------- 98 99 func (te *TextEdit) EditCtx() *rwedit.Ctx { 100 return te.ctx 101 } 102 103 //---------- 104 105 func (te *TextEdit) onCursorChange() { 106 te.Drawer.SetCursorOffset(te.CursorIndex()) 107 te.MarkNeedsPaint() 108 } 109 110 //---------- 111 112 func (te *TextEdit) Cursor() rwedit.Cursor { 113 return te.ctx.C 114 } 115 116 func (te *TextEdit) CursorIndex() int { 117 return te.Cursor().Index() 118 } 119 120 func (te *TextEdit) SetCursorIndex(i int) { 121 te.Cursor().SetIndex(i) 122 } 123 124 //---------- 125 126 func (te *TextEdit) Undo() error { return te.undoRedo(false) } 127 func (te *TextEdit) Redo() error { return te.undoRedo(true) } 128 func (te *TextEdit) undoRedo(redo bool) error { 129 c, ok, err := te.rwu.UndoRedo(redo, false) 130 if err != nil { 131 return err 132 } 133 if ok { 134 te.ctx.C.Set(c) // restore cursor 135 te.MakeCursorVisible() 136 } 137 return nil 138 } 139 140 func (te *TextEdit) ClearUndones() { 141 te.rwu.History.ClearUndones() 142 } 143 144 //---------- 145 146 func (te *TextEdit) BeginUndoGroup() { 147 c := te.ctx.C.Get() 148 te.rwu.History.BeginUndoGroup(c) 149 } 150 151 func (te *TextEdit) EndUndoGroup() { 152 c := te.ctx.C.Get() 153 te.rwu.History.EndUndoGroup(c) 154 } 155 156 //---------- 157 158 func (te *TextEdit) OnInputEvent(ev interface{}, p image.Point) event.Handled { 159 te.BeginUndoGroup() 160 defer te.EndUndoGroup() 161 162 handled, err := rwedit.HandleInput(te.ctx, ev) 163 if err != nil { 164 te.uiCtx.Error(err) 165 } 166 return handled 167 } 168 169 //---------- 170 171 func (te *TextEdit) SetBytes(b []byte) error { 172 te.BeginUndoGroup() 173 defer te.EndUndoGroup() 174 defer func() { 175 // because after setbytes the possible selection might not be correct (ex: go fmt; variable renames with lsprotorename) 176 te.ctx.C.SetSelectionOff() 177 }() 178 return iorw.SetBytes(te.ctx.RW, b) 179 } 180 181 func (te *TextEdit) SetBytesClearPos(b []byte) error { 182 te.BeginUndoGroup() 183 defer te.EndUndoGroup() 184 err := iorw.SetBytes(te.ctx.RW, b) 185 te.ClearPos() // keep position in undogroup (history record) 186 return err 187 } 188 189 // Keeps position (useful for file save) 190 func (te *TextEdit) SetBytesClearHistory(b []byte) error { 191 te.rwu.History.Clear() 192 rw := te.rwu.ReadWriterAt // bypass history 193 if err := iorw.SetBytes(rw, b); err != nil { 194 return err 195 } 196 return nil 197 } 198 199 func (te *TextEdit) AppendBytesClearHistory(b []byte) error { 200 te.rwu.History.Clear() 201 rw := te.rwu.ReadWriterAt // bypass history 202 if err := rw.OverwriteAt(rw.Max(), 0, b); err != nil { 203 return err 204 } 205 return nil 206 } 207 208 //---------- 209 210 func (te *TextEdit) SetStr(str string) error { 211 return te.SetBytes([]byte(str)) 212 } 213 214 func (te *TextEdit) SetStrClearPos(str string) error { 215 return te.SetBytesClearPos([]byte(str)) 216 } 217 218 func (te *TextEdit) SetStrClearHistory(str string) error { 219 return te.SetBytesClearHistory([]byte(str)) 220 } 221 222 //---------- 223 224 func (te *TextEdit) ClearPos() { 225 te.ctx.C.SetIndexSelectionOff(0) 226 te.MakeIndexVisible(0) 227 } 228 229 //---------- 230 231 func (te *TextEdit) MakeCursorVisible() { 232 if a, b, ok := te.ctx.C.SelectionIndexes(); ok { 233 te.MakeRangeVisible(a, b-a) 234 } else { 235 te.MakeIndexVisible(te.ctx.C.Index()) 236 } 237 } 238 239 //---------- 240 241 func (te *TextEdit) stableRuneOffset(ev *iorw.RWEvWrite) { 242 // keep offset based scrolling stable 243 ro := StableOffsetScroll(te.RuneOffset(), ev.Index, ev.Dn, ev.In) 244 te.SetRuneOffset(ro) 245 } 246 247 func (te *TextEdit) stableCursor(ev *iorw.RWEvWrite) { 248 c := te.Cursor() 249 ci := StableOffsetScroll(c.Index(), ev.Index, ev.Dn, ev.In) 250 if c.HaveSelection() { 251 si := StableOffsetScroll(c.SelectionIndex(), ev.Index, ev.Dn, ev.In) 252 c.SetSelection(si, ci) 253 } else { 254 te.SetCursorIndex(ci) 255 } 256 }