Changeset View
Changeset View
Standalone View
Standalone View
source/blender/editors/space_text/text_undo.c
| Show First 20 Lines • Show All 53 Lines • ▼ Show 20 Lines | |||||
| #include "text_format.h" | #include "text_format.h" | ||||
| /* TODO(campbell): undo_system: move text undo out of text block. */ | /* TODO(campbell): undo_system: move text undo out of text block. */ | ||||
| /* -------------------------------------------------------------------- */ | /* -------------------------------------------------------------------- */ | ||||
| /** \name Implements ED Undo System | /** \name Implements ED Undo System | ||||
| * \{ */ | * \{ */ | ||||
| #define USE_ARRAY_STORE | |||||
brecht: I'm not a fan of all these #ifdefs, code is easier to read and refactor if there's just a… | |||||
Done Inline ActionsUseful for testing and they're kept for edit-mesh since this is more complex/error prone code case. removed. campbellbarton: Useful for testing and they're kept for edit-mesh since this is more complex/error prone code… | |||||
| #ifdef USE_ARRAY_STORE | |||||
| // # define DEBUG_PRINT | |||||
| // # define DEBUG_TIME | |||||
| # ifdef DEBUG_TIME | |||||
| # include "PIL_time_utildefines.h" | |||||
| # endif | |||||
| # include "BLI_array_store.h" | |||||
| /* check on best size later... */ | |||||
| # define ARRAY_CHUNK_SIZE 128 | |||||
| #endif | |||||
| typedef struct TextUndoStep { | typedef struct TextUndoStep { | ||||
| UndoStep step; | UndoStep step; | ||||
| UndoRefID_Text text_ref; | UndoRefID_Text text_ref; | ||||
| TextUndoBuf data; | struct { | ||||
| #ifdef USE_ARRAY_STORE | |||||
| BArrayState *state; | |||||
| #else | |||||
| uchar *buf; | |||||
| #endif | |||||
| int buf_strlen; | |||||
| } data; | |||||
| struct { | |||||
| int line, line_select; | |||||
| int column, column_select; | |||||
| } cursor; | |||||
| } TextUndoStep; | } TextUndoStep; | ||||
| #ifdef USE_ARRAY_STORE | |||||
| static struct { | |||||
| BArrayStore *bs; | |||||
Done Inline ActionsPlease use longer more descriptive name. brecht: Please use longer more descriptive name. | |||||
| int users; | |||||
| } um_arraystore = {NULL}; | |||||
Done Inline ActionsPlease add comment explaining what um_arraystore is for, and maybe find a more descriptive name. brecht: Please add comment explaining what `um_arraystore` is for, and maybe find a more descriptive… | |||||
| #endif | |||||
| static bool text_undosys_poll(bContext *UNUSED(C)) | static bool text_undosys_poll(bContext *UNUSED(C)) | ||||
| { | { | ||||
| /* Only use when operators initialized. */ | /* Only use when operators initialized. */ | ||||
| UndoStack *ustack = ED_undo_stack_get(); | UndoStack *ustack = ED_undo_stack_get(); | ||||
| return (ustack->step_init && (ustack->step_init->type == BKE_UNDOSYS_TYPE_TEXT)); | return (ustack->step_init && (ustack->step_init->type == BKE_UNDOSYS_TYPE_TEXT)); | ||||
| } | } | ||||
| static void text_undosys_step_encode_init(struct bContext *C, UndoStep *us_p) | static void text_undosys_step_encode_init(struct bContext *C, UndoStep *us_p) | ||||
| { | { | ||||
| TextUndoStep *us = (TextUndoStep *)us_p; | TextUndoStep *us = (TextUndoStep *)us_p; | ||||
| BLI_assert(BLI_array_is_zeroed(&us->data, 1)); | BLI_assert(BLI_array_is_zeroed(&us->data, 1)); | ||||
| UNUSED_VARS(C); | UNUSED_VARS(C, us); | ||||
| /* XXX, use to set the undo type only. */ | /* XXX, use to set the undo type only. */ | ||||
| us->data.buf = NULL; | |||||
| us->data.len = 0; | |||||
| us->data.pos = -1; | |||||
| } | } | ||||
| static bool text_undosys_step_encode(struct bContext *C, | static bool text_undosys_step_encode(struct bContext *C, | ||||
| struct Main *UNUSED(bmain), | struct Main *UNUSED(bmain), | ||||
| UndoStep *us_p) | UndoStep *us_p) | ||||
| { | { | ||||
| TextUndoStep *us = (TextUndoStep *)us_p; | TextUndoStep *us = (TextUndoStep *)us_p; | ||||
| Text *text = CTX_data_edit_text(C); | Text *text = CTX_data_edit_text(C); | ||||
| /* No undo data was generated. Hint, use global undo here. */ | int buf_strlen = 0; | ||||
| if ((us->data.pos == -1) || (us->data.buf == NULL)) { | |||||
| return false; | uchar *buf = (uchar *)txt_to_buf(text, &buf_strlen); | ||||
| #ifdef USE_ARRAY_STORE | |||||
| if (um_arraystore.bs == NULL) { | |||||
| um_arraystore.bs = BLI_array_store_create(1, ARRAY_CHUNK_SIZE); | |||||
| } | |||||
| um_arraystore.users += 1; | |||||
| const size_t total_size_prev = BLI_array_store_calc_size_compacted_get(um_arraystore.bs); | |||||
| us->data.state = BLI_array_store_state_add(um_arraystore.bs, buf, buf_strlen, NULL); | |||||
| MEM_freeN(buf); | |||||
| #else | |||||
| us->data.buf = buf; | |||||
| us->data.buf_strlen = buf_strlen; | |||||
| #endif | |||||
| us->cursor.line = txt_get_span(text->lines.first, text->curl); | |||||
| us->cursor.column = text->curc; | |||||
| if (txt_has_sel(text)) { | |||||
| us->cursor.line_select = (text->curl == text->sell) ? | |||||
| us->cursor.line : | |||||
| txt_get_span(text->lines.first, text->sell); | |||||
| us->cursor.column_select = text->selc; | |||||
| } | |||||
| else { | |||||
| us->cursor.line_select = us->cursor.line; | |||||
| us->cursor.column_select = us->cursor.column; | |||||
| } | } | ||||
| us_p->is_applied = true; | us_p->is_applied = true; | ||||
| us->text_ref.ptr = text; | us->text_ref.ptr = text; | ||||
| us->step.data_size = us->data.len; | #ifdef USE_ARRAY_STORE | ||||
| us->step.data_size = BLI_array_store_calc_size_compacted_get(um_arraystore.bs) - total_size_prev; | |||||
| #else | |||||
| us->step.data_size = us->data.buf_strlen + 1; | |||||
| #endif | |||||
| return true; | return true; | ||||
| } | } | ||||
| static void text_undosys_step_decode_undo_impl(Text *text, TextUndoStep *us) | static void text_undosys_step_decode(struct bContext *C, | ||||
| { | struct Main *UNUSED(bmain), | ||||
| BLI_assert(us->step.is_applied == true); | UndoStep *us_p, | ||||
| TextUndoBuf data = us->data; | int UNUSED(dir), | ||||
| while (data.pos > -1) { | bool UNUSED(is_final)) | ||||
| txt_do_undo(text, &data); | |||||
| } | |||||
| BLI_assert(data.pos == -1); | |||||
| us->step.is_applied = false; | |||||
| } | |||||
| static void text_undosys_step_decode_redo_impl(Text *text, TextUndoStep *us) | |||||
| { | |||||
| BLI_assert(us->step.is_applied == false); | |||||
| TextUndoBuf data = us->data; | |||||
| data.pos = -1; | |||||
| while (data.pos < us->data.pos) { | |||||
| txt_do_redo(text, &data); | |||||
| } | |||||
| BLI_assert(data.pos == us->data.pos); | |||||
| us->step.is_applied = true; | |||||
| } | |||||
| static void text_undosys_step_decode_undo(TextUndoStep *us, bool is_final) | |||||
| { | |||||
| TextUndoStep *us_iter = us; | |||||
| while (us_iter->step.next && (us_iter->step.next->type == us_iter->step.type)) { | |||||
| if (us_iter->step.next->is_applied == false) { | |||||
| break; | |||||
| } | |||||
| us_iter = (TextUndoStep *)us_iter->step.next; | |||||
| } | |||||
| Text *text_prev = NULL; | |||||
| while ((us_iter != us) || (is_final && us_iter == us)) { | |||||
| Text *text = us_iter->text_ref.ptr; | |||||
| text_undosys_step_decode_undo_impl(text, us_iter); | |||||
| if (text_prev != text) { | |||||
| text_update_edited(text); | |||||
| text_prev = text; | |||||
| } | |||||
| if (is_final) { | |||||
| break; | |||||
| } | |||||
| us_iter = (TextUndoStep *)us_iter->step.prev; | |||||
| } | |||||
| } | |||||
| static void text_undosys_step_decode_redo(TextUndoStep *us) | |||||
| { | |||||
| TextUndoStep *us_iter = us; | |||||
| while (us_iter->step.prev && (us_iter->step.prev->type == us_iter->step.type)) { | |||||
| if (us_iter->step.prev->is_applied == true) { | |||||
| break; | |||||
| } | |||||
| us_iter = (TextUndoStep *)us_iter->step.prev; | |||||
| } | |||||
| Text *text_prev = NULL; | |||||
| while (us_iter && (us_iter->step.is_applied == false)) { | |||||
| Text *text = us_iter->text_ref.ptr; | |||||
| text_undosys_step_decode_redo_impl(text, us_iter); | |||||
| if (text_prev != text) { | |||||
| text_update_edited(text); | |||||
| text_prev = text; | |||||
| } | |||||
| if (us_iter == us) { | |||||
| break; | |||||
| } | |||||
| us_iter = (TextUndoStep *)us_iter->step.next; | |||||
| } | |||||
| } | |||||
| static void text_undosys_step_decode( | |||||
| struct bContext *C, struct Main *UNUSED(bmain), UndoStep *us_p, int dir, bool is_final) | |||||
| { | { | ||||
| TextUndoStep *us = (TextUndoStep *)us_p; | TextUndoStep *us = (TextUndoStep *)us_p; | ||||
| Text *text = us->text_ref.ptr; | |||||
| if (dir < 0) { | #ifdef USE_ARRAY_STORE | ||||
| text_undosys_step_decode_undo(us, is_final); | size_t buf_strlen; | ||||
| } | const uchar *buf = (const uchar *)BLI_array_store_state_data_get_alloc(us->data.state, | ||||
| else { | &buf_strlen); | ||||
| text_undosys_step_decode_redo(us); | #else | ||||
| const uchar *buf = us->data.buf; | |||||
| const size_t buf_strlen = us->data.buf_strlen; | |||||
| #endif | |||||
| BKE_text_reload_from_buf(text, buf, buf_strlen); | |||||
| #ifdef USE_ARRAY_STORE | |||||
| MEM_freeN((void *)buf); | |||||
| #endif | |||||
| const bool has_select = ((us->cursor.line != us->cursor.line_select) || | |||||
| (us->cursor.column != us->cursor.column_select)); | |||||
| if (has_select) { | |||||
| txt_move_to(text, us->cursor.line_select, us->cursor.column_select, 0); | |||||
Done Inline Actions0 -> false brecht: 0 -> false | |||||
| } | } | ||||
| txt_move_to(text, us->cursor.line, us->cursor.column, has_select); | |||||
| Text *text = us->text_ref.ptr; | |||||
| SpaceText *st = CTX_wm_space_text(C); | SpaceText *st = CTX_wm_space_text(C); | ||||
| if (st) { | if (st) { | ||||
| /* Not essential, always show text being undo where possible. */ | /* Not essential, always show text being undo where possible. */ | ||||
| st->text = text; | st->text = text; | ||||
| } | } | ||||
| text_update_cursor_moved(C); | text_update_cursor_moved(C); | ||||
| text_drawcache_tag_update(st, 1); | text_drawcache_tag_update(st, 1); | ||||
| WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text); | WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text); | ||||
| } | } | ||||
| static void text_undosys_step_free(UndoStep *us_p) | static void text_undosys_step_free(UndoStep *us_p) | ||||
| { | { | ||||
| TextUndoStep *us = (TextUndoStep *)us_p; | TextUndoStep *us = (TextUndoStep *)us_p; | ||||
| MEM_SAFE_FREE(us->data.buf); | |||||
| #ifdef USE_ARRAY_STORE | |||||
| BLI_array_store_state_remove(um_arraystore.bs, us->data.state); | |||||
| um_arraystore.users -= 1; | |||||
| if (um_arraystore.users == 0) { | |||||
| BLI_array_store_destroy(um_arraystore.bs); | |||||
| um_arraystore.bs = NULL; | |||||
| } | |||||
| #endif | |||||
Done Inline ActionsThe non-array store case is leaking memory here. brecht: The non-array store case is leaking memory here. | |||||
| } | } | ||||
| static void text_undosys_foreach_ID_ref(UndoStep *us_p, | static void text_undosys_foreach_ID_ref(UndoStep *us_p, | ||||
| UndoTypeForEachIDRefFn foreach_ID_ref_fn, | UndoTypeForEachIDRefFn foreach_ID_ref_fn, | ||||
| void *user_data) | void *user_data) | ||||
| { | { | ||||
| TextUndoStep *us = (TextUndoStep *)us_p; | TextUndoStep *us = (TextUndoStep *)us_p; | ||||
| foreach_ID_ref_fn(user_data, ((UndoRefID *)&us->text_ref)); | foreach_ID_ref_fn(user_data, ((UndoRefID *)&us->text_ref)); | ||||
| Show All 19 Lines | |||||
| /** \} */ | /** \} */ | ||||
| /* -------------------------------------------------------------------- */ | /* -------------------------------------------------------------------- */ | ||||
| /** \name Utilities | /** \name Utilities | ||||
| * \{ */ | * \{ */ | ||||
| /* Use operator system to finish the undo step. */ | /* Use operator system to finish the undo step. */ | ||||
| TextUndoBuf *ED_text_undo_push_init(bContext *C) | UndoStep *ED_text_undo_push_init(bContext *C) | ||||
| { | { | ||||
| UndoStack *ustack = ED_undo_stack_get(); | UndoStack *ustack = ED_undo_stack_get(); | ||||
| UndoStep *us_p = BKE_undosys_step_push_init_with_type(ustack, C, NULL, BKE_UNDOSYS_TYPE_TEXT); | UndoStep *us_p = BKE_undosys_step_push_init_with_type(ustack, C, NULL, BKE_UNDOSYS_TYPE_TEXT); | ||||
| TextUndoStep *us = (TextUndoStep *)us_p; | return us_p; | ||||
| return &us->data; | |||||
| } | } | ||||
| /** \} */ | /** \} */ | ||||
I'm not a fan of all these #ifdefs, code is easier to read and refactor if there's just a single code path.