From 8eed6f96ba83a99d5169dd259ebdb27faafaee3e Mon Sep 17 00:00:00 2001 From: FabienJansem <60888905+FabienJansem@users.noreply.github.com> Date: Sun, 4 Sep 2022 13:04:29 +0200 Subject: [PATCH 1/2] Export Undo/Redo functions Export Undo/Redo functions Editor.History is available to save/restore modification history Fixes: https://todo.sr.ht/~eliasnaur/gio/438 Signed-off-by: Fabien Jansem fabien@jansem.eu.org --- widget/editor.go | 58 +++++++++++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/widget/editor.go b/widget/editor.go index 023bd9d60..35ae77a53 100644 --- a/widget/editor.go +++ b/widget/editor.go @@ -111,11 +111,7 @@ type Editor struct { locale system.Locale // history contains undo history. - history []modification - // nextHistoryIdx is the index within the history of the next modification. This - // is only not len(history) immediately after undo operations occur. It is framed as the "next" value - // to make the zero value consistent. - nextHistoryIdx int + History *History } type offEntry struct { @@ -503,9 +499,9 @@ func (e *Editor) command(gtx layout.Context, k key.Event) { e.caret.start = e.Len() case "Z": if k.Modifiers.Contain(key.ModShift) { - e.redo() + e.Redo() } else { - e.undo() + e.Undo() } } } @@ -891,15 +887,18 @@ func (e *Editor) Text() string { } // SetText replaces the contents of the editor, clearing any selection first. +// History is cleared before and after replacement func (e *Editor) SetText(s string) { e.rr = editBuffer{} e.caret.start = 0 e.caret.end = 0 + e.History = nil // avoid alteration of previous text history if e.SingleLine { s = strings.ReplaceAll(s, "\n", " ") } e.replace(e.caret.start, e.caret.end, s, true) e.caret.xoff = 0 + e.History = nil // initial text is not a modification } func (e *Editor) scrollBounds() image.Rectangle { @@ -1223,32 +1222,42 @@ type modification struct { ReverseContent string } -// undo applies the modification at e.history[e.historyIdx] and decrements -// e.historyIdx. -func (e *Editor) undo() { - if len(e.history) < 1 || e.nextHistoryIdx == 0 { +// History of modifications may be retrived/restored +type History struct { + // data contains undo/redo history. + data []modification + // nextIdx is the index within the history data of the next modification. + // This is only not len(data) immediately after undo operations occur. + // It is framed as the "next" value to make the zero value consistent. + nextIdx int +} + +// Undo applies the modification at e.history.data[e.history.nextIdx] +// and decrements e.history.nextIdx. +func (e *Editor) Undo() { + if e.History == nil || len(e.History.data) < 1 || e.History.nextIdx == 0 { return } - mod := e.history[e.nextHistoryIdx-1] + mod := e.History.data[e.History.nextIdx-1] replaceEnd := mod.StartRune + utf8.RuneCountInString(mod.ApplyContent) e.replace(mod.StartRune, replaceEnd, mod.ReverseContent, false) caretEnd := mod.StartRune + utf8.RuneCountInString(mod.ReverseContent) e.SetCaret(caretEnd, mod.StartRune) - e.nextHistoryIdx-- + e.History.nextIdx-- } -// redo applies the modification at e.history[e.historyIdx] and increments -// e.historyIdx. -func (e *Editor) redo() { - if len(e.history) < 1 || e.nextHistoryIdx == len(e.history) { +// Redo applies the modification at e.history.data[e.history.nextIdx] +// and increments e.history.nextIdx. +func (e *Editor) Redo() { + if e.History == nil || len(e.History.data) < 1 || e.History.nextIdx == len(e.History.data) { return } - mod := e.history[e.nextHistoryIdx] + mod := e.History.data[e.History.nextIdx] end := mod.StartRune + utf8.RuneCountInString(mod.ReverseContent) e.replace(mod.StartRune, end, mod.ApplyContent, false) caretEnd := mod.StartRune + utf8.RuneCountInString(mod.ApplyContent) e.SetCaret(caretEnd, mod.StartRune) - e.nextHistoryIdx++ + e.History.nextIdx++ } // replace the text between start and end with s. Indices are in runes. @@ -1288,15 +1297,18 @@ func (e *Editor) replace(start, end int, s string, addHistory bool) int { ru, _, _ := e.rr.ReadRune() deleted = append(deleted, ru) } - if e.nextHistoryIdx < len(e.history) { - e.history = e.history[:e.nextHistoryIdx] + if e.History == nil { + e.History = &History{} + } + if e.History.nextIdx < len(e.History.data) { + e.History.data = e.History.data[:e.History.nextIdx] } - e.history = append(e.history, modification{ + e.History.data = append(e.History.data, modification{ StartRune: startPos.runes, ApplyContent: s, ReverseContent: string(deleted), }) - e.nextHistoryIdx++ + e.History.nextIdx++ } e.rr.deleteRunes(startOff, replaceSize) From 9c405708e71ebdd588936577fdc0aa114f135302 Mon Sep 17 00:00:00 2001 From: FabienJansem <60888905+FabienJansem@users.noreply.github.com> Date: Sun, 4 Sep 2022 13:07:43 +0200 Subject: [PATCH 2/2] Export Undo/Redo functions Export Undo/Redo functions Editor.History is available to save/restore modification history Fixes: https://todo.sr.ht/~eliasnaur/gio/438 Signed-off-by: Fabien Jansem fabien@jansem.eu.org --- widget/editor_test.go | 63 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 5 deletions(-) diff --git a/widget/editor_test.go b/widget/editor_test.go index 76034956d..48000c508 100644 --- a/widget/editor_test.go +++ b/widget/editor_test.go @@ -47,10 +47,10 @@ func TestEditorHistory(t *testing.T) { e.Insert("") assertContents(t, e, "", 0, 0) // Ensure that undoing the overwrite succeeds. - e.undo() + e.Undo() assertContents(t, e, "안П你 hello 안П你", 13, 0) // Ensure that redoing the overwrite succeeds. - e.redo() + e.Redo() assertContents(t, e, "", 0, 0) // Insert some smaller text. e.Insert("안П你 hello") @@ -64,9 +64,9 @@ func TestEditorHistory(t *testing.T) { e.Insert("П") assertContents(t, e, "안ПeПlo", 4, 4) // Ensure both operations undo successfully. - e.undo() + e.Undo() assertContents(t, e, "안Пello", 4, 3) - e.undo() + e.Undo() assertContents(t, e, "안П你 hello", 5, 1) // Make a new modification. e.Insert("Something New") @@ -75,10 +75,63 @@ func TestEditorHistory(t *testing.T) { // This redo() call should do nothing. text := e.Text() start, end := e.Selection() - e.redo() + e.Redo() assertContents(t, e, text, start, end) } +// TestEditorHistoryExpose ensures that History and SetHistory behave correctly. +func TestEditorHistoryExpose(t *testing.T) { + e := new(Editor) + // Insert some text and do modifications. + e.Insert("안П你 hello") + e.SetCaret(1, 5) + e.Insert("П") + e.SetCaret(3, 4) + e.Insert("П") + // Save text and history + savedText := e.Text() + savedHistory := e.History + // Clear history + e.History = nil + // Ensure no more Undo/Redo available + e.Undo() + assertContents(t, e, "안ПeПlo", 4, 4) + e.Redo() + assertContents(t, e, "안ПeПlo", 4, 4) + // restore history + e.History = savedHistory + // Ensure all Undos are back + e.Undo() + assertContents(t, e, "안Пello", 4, 3) + e.Undo() + assertContents(t, e, "안П你 hello", 5, 1) + e.Undo() + assertContents(t, e, "", 0, 0) + // Ensure Redo also works + e.Redo() + assertContents(t, e, "안П你 hello", 9, 0) + e.Redo() + assertContents(t, e, "안Пello", 2, 1) + e.Redo() + assertContents(t, e, "안ПeПlo", 4, 3) + // Init a new text + e.SetText("New text") + // Ensure history has been cleared + e.Undo() + assertContents(t, e, "New text", 0, 0) + // Put back previous text and history + e.SetText(savedText) + assertContents(t, e, "안ПeПlo", 0, 0) + e.History = savedHistory + // Ensure all Undos are back + e.Undo() + assertContents(t, e, "안Пello", 4, 3) + e.Undo() + assertContents(t, e, "안П你 hello", 5, 1) + e.Undo() + assertContents(t, e, "", 0, 0) +} + func assertContents(t *testing.T, e *Editor, contents string, selectionStart, selectionEnd int) { t.Helper() actualContents := e.Text()