diff options
| author | Michael Kutowski <skytrias@protonmail.com> | 2023-11-22 23:56:58 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-11-22 23:56:58 +0100 |
| commit | 29f1b79d70a68c4702870da0a891bb1209519dad (patch) | |
| tree | 4f89ee89826713113c71c914ba499f0a2ba5a739 /core/text/edit | |
| parent | 955be66f1a4af3c214796e581dc7abe319be5cb7 (diff) | |
Add setup_once and clear_all, tidy up old code and add a few comments
Diffstat (limited to 'core/text/edit')
| -rw-r--r-- | core/text/edit/text_edit.odin | 106 |
1 files changed, 76 insertions, 30 deletions
diff --git a/core/text/edit/text_edit.odin b/core/text/edit/text_edit.odin index 8520ba674..8979088a7 100644 --- a/core/text/edit/text_edit.odin +++ b/core/text/edit/text_edit.odin @@ -1,4 +1,4 @@ -package text_edit +package edit /* Based off the articles by rxi: @@ -63,7 +63,9 @@ Translation :: enum u32 { Soft_Line_End, } - +// init the state to some timeout and set the respective allocators +// - undo_state_allocator dictates the dynamic undo|redo arrays allocators +// - undo_text_allocator is the allocator which allocates strings only init :: proc(s: ^State, undo_text_allocator, undo_state_allocator: runtime.Allocator, undo_timeout := DEFAULT_UNDO_TIMEOUT) { s.undo_timeout = undo_timeout @@ -74,6 +76,7 @@ init :: proc(s: ^State, undo_text_allocator, undo_state_allocator: runtime.Alloc s.redo.allocator = undo_state_allocator } +// clear undo|redo strings and delete their stacks destroy :: proc(s: ^State) { undo_clear(s, &s.undo) undo_clear(s, &s.redo) @@ -82,7 +85,6 @@ destroy :: proc(s: ^State) { s.builder = nil } - // Call at the beginning of each frame begin :: proc(s: ^State, id: u64, builder: ^strings.Builder) { assert(builder != nil) @@ -92,11 +94,7 @@ begin :: proc(s: ^State, id: u64, builder: ^strings.Builder) { s.id = id s.selection = {len(builder.buf), 0} s.builder = builder - s.current_time = time.tick_now() - if s.undo_timeout <= 0 { - s.undo_timeout = DEFAULT_UNDO_TIMEOUT - } - set_text(s, string(s.builder.buf[:])) + update_time(s) undo_clear(s, &s.undo) undo_clear(s, &s.redo) } @@ -107,12 +105,37 @@ end :: proc(s: ^State) { s.builder = nil } -set_text :: proc(s: ^State, text: string) { - strings.builder_reset(s.builder) - strings.write_string(s.builder, text) +// update current time so "insert" can check for timeouts +update_time :: proc(s: ^State) { + s.current_time = time.tick_now() + if s.undo_timeout <= 0 { + s.undo_timeout = DEFAULT_UNDO_TIMEOUT + } +} + +// setup the builder, selection and undo|redo state once allowing to retain selection +setup_once :: proc(s: ^State, builder: ^strings.Builder) { + s.builder = builder + s.selection = { len(builder.buf), 0 } + undo_clear(s, &s.undo) + undo_clear(s, &s.redo) } +// returns true when the builder had content to be cleared +// clear builder&selection and the undo|redo stacks +clear_all :: proc(s: ^State) -> (cleared: bool) { + if len(s.builder.buf) > 0 { + clear(&s.builder.buf) + s.selection = {} + cleared = true + } + undo_clear(s, &s.undo) + undo_clear(s, &s.redo) + return +} + +// push current text state to the wanted undo|redo stack undo_state_push :: proc(s: ^State, undo: ^[dynamic]^Undo_State) -> mem.Allocator_Error { text := string(s.builder.buf[:]) item := (^Undo_State)(mem.alloc(size_of(Undo_State) + len(text), align_of(Undo_State), s.undo_text_allocator) or_return) @@ -125,18 +148,21 @@ undo_state_push :: proc(s: ^State, undo: ^[dynamic]^Undo_State) -> mem.Allocator return nil } +// pop undo|redo state - push to redo|undo - set selection & text undo :: proc(s: ^State, undo, redo: ^[dynamic]^Undo_State) { if len(undo) > 0 { undo_state_push(s, redo) item := pop(undo) s.selection = item.selection #no_bounds_check { - set_text(s, string(item.text[:item.len])) + strings.builder_reset(s.builder) + strings.write_string(s.builder, string(item.text[:item.len])) } free(item, s.undo_text_allocator) } } +// iteratively clearn the undo|redo stack and free each allocated text state undo_clear :: proc(s: ^State, undo: ^[dynamic]^Undo_State) { for len(undo) > 0 { item := pop(undo) @@ -144,6 +170,7 @@ undo_clear :: proc(s: ^State, undo: ^[dynamic]^Undo_State) { } } +// clear redo stack and check if the undo timeout gets hit undo_check :: proc(s: ^State) { undo_clear(s, &s.redo) if time.tick_diff(s.last_edit_time, s.current_time) > s.undo_timeout { @@ -152,8 +179,7 @@ undo_check :: proc(s: ^State) { s.last_edit_time = s.current_time } - - +// insert text into the edit state - deletes the current selection input_text :: proc(s: ^State, text: string) { if len(text) == 0 { return @@ -166,6 +192,7 @@ input_text :: proc(s: ^State, text: string) { s.selection = {offset, offset} } +// insert slice of runes into the edit state - deletes the current selection input_runes :: proc(s: ^State, text: []rune) { if len(text) == 0 { return @@ -182,43 +209,55 @@ input_runes :: proc(s: ^State, text: []rune) { s.selection = {offset, offset} } +// insert a single rune into the edit state - deletes the current selection +input_rune :: proc(s: ^State, r: rune) { + if has_selection(s) { + selection_delete(s) + } + offset := s.selection[0] + b, w := utf8.encode_rune(r) + insert(s, offset, string(b[:w])) + offset += w + s.selection = {offset, offset} +} +// insert a single rune into the edit state - deletes the current selection insert :: proc(s: ^State, at: int, text: string) { undo_check(s) inject_at(&s.builder.buf, at, text) } +// remove the wanted range withing, usually the selection within byte indices remove :: proc(s: ^State, lo, hi: int) { undo_check(s) remove_range(&s.builder.buf, lo, hi) } - - +// true if selection head and tail dont match and form a selection of multiple characters has_selection :: proc(s: ^State) -> bool { return s.selection[0] != s.selection[1] } +// return the clamped lo/hi of the current selection +// since the selection[0] moves around and could be ahead of selection[1] +// useful when rendering and needing left->right sorted_selection :: proc(s: ^State) -> (lo, hi: int) { lo = min(s.selection[0], s.selection[1]) hi = max(s.selection[0], s.selection[1]) lo = clamp(lo, 0, len(s.builder.buf)) hi = clamp(hi, 0, len(s.builder.buf)) - s.selection[0] = lo - s.selection[1] = hi return } - +// delete the current selection range and set the proper selection afterwards selection_delete :: proc(s: ^State) { lo, hi := sorted_selection(s) remove(s, lo, hi) s.selection = {lo, lo} } - - -translate_position :: proc(s: ^State, pos: int, t: Translation) -> int { +// translates the caret position +translate_position :: proc(s: ^State, t: Translation) -> int { is_continuation_byte :: proc(b: byte) -> bool { return b >= 0x80 && b < 0xc0 } @@ -227,9 +266,7 @@ translate_position :: proc(s: ^State, pos: int, t: Translation) -> int { } buf := s.builder.buf[:] - - pos := pos - pos = clamp(pos, 0, len(buf)) + pos := clamp(s.selection[0], 0, len(buf)) switch t { case .Start: @@ -280,6 +317,7 @@ translate_position :: proc(s: ^State, pos: int, t: Translation) -> int { return clamp(pos, 0, len(buf)) } +// Moves the position of the caret (both sides of the selection) move_to :: proc(s: ^State, t: Translation) { if t == .Left && has_selection(s) { lo, _ := sorted_selection(s) @@ -288,32 +326,36 @@ move_to :: proc(s: ^State, t: Translation) { _, hi := sorted_selection(s) s.selection = {hi, hi} } else { - pos := translate_position(s, s.selection[0], t) + pos := translate_position(s, t) s.selection = {pos, pos} } } + +// Moves only the head of the selection and leaves the tail uneffected select_to :: proc(s: ^State, t: Translation) { - s.selection[0] = translate_position(s, s.selection[0], t) + s.selection[0] = translate_position(s, t) } + +// Deletes everything between the caret and resultant position delete_to :: proc(s: ^State, t: Translation) { if has_selection(s) { selection_delete(s) } else { lo := s.selection[0] - hi := translate_position(s, lo, t) + hi := translate_position(s, t) lo, hi = min(lo, hi), max(lo, hi) remove(s, lo, hi) s.selection = {lo, lo} } } - +// return the currently selected text current_selected_text :: proc(s: ^State) -> string { lo, hi := sorted_selection(s) return string(s.builder.buf[lo:hi]) } - +// copy & delete the current selection when copy() succeeds cut :: proc(s: ^State) -> bool { if copy(s) { selection_delete(s) @@ -322,6 +364,8 @@ cut :: proc(s: ^State) -> bool { return false } +// try and copy the currently selected text to the clipboard +// State.set_clipboard needs to be assigned copy :: proc(s: ^State) -> bool { if s.set_clipboard != nil { return s.set_clipboard(s.clipboard_user_data, current_selected_text(s)) @@ -329,6 +373,8 @@ copy :: proc(s: ^State) -> bool { return s.set_clipboard != nil } +// reinsert whatever the get_clipboard would return +// State.get_clipboard needs to be assigned paste :: proc(s: ^State) -> bool { if s.get_clipboard != nil { input_text(s, s.get_clipboard(s.clipboard_user_data) or_return) |