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  }