From 62328cea3fef477fb266847ddfc4c1082ed62d53 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Mon, 6 Jul 2026 09:48:54 -0700 Subject: [PATCH] checkpoint on text patches --- src/raddbg/raddbg_core.c | 3 +- src/raddbg/raddbg_views.c | 216 +++++++++++++++++++++++++-- src/raddbg/raddbg_views.h | 4 + src/raddbg/raddbg_widgets.c | 176 +--------------------- src/raddbg/raddbg_widgets.h | 3 +- src/scratch/ryan_scratch.c | 2 + src/text/text.c | 287 ++++++++++++++++++++++++++++++++++-- src/text/text.h | 90 ++++++++++- src/ui/ui_core.c | 2 +- 9 files changed, 579 insertions(+), 204 deletions(-) diff --git a/src/raddbg/raddbg_core.c b/src/raddbg/raddbg_core.c index 575fcb3a..87cc8d0d 100644 --- a/src/raddbg/raddbg_core.c +++ b/src/raddbg/raddbg_core.c @@ -16583,10 +16583,11 @@ rd_frame(void) U128 hash = {0}; TXT_TextInfo info = txt_text_info_from_key_lang(access, text_key, lang_kind, &hash); String8 data = c_data_from_hash(access, hash); + TXT_PatchList patches = {0}; Rng1U64 expr_off_range = {0}; if(range.min.column != range.max.column) { - expr_off_range = r1u64(txt_off_from_info_pt(&info, range.min), txt_off_from_info_pt(&info, range.max)); + expr_off_range = r1u64(txt_off_from_pt(&info, &patches, range.min), txt_off_from_pt(&info, &patches, range.max)); } else { diff --git a/src/raddbg/raddbg_views.c b/src/raddbg/raddbg_views.c index 8db66e9d..dd212d48 100644 --- a/src/raddbg/raddbg_views.c +++ b/src/raddbg/raddbg_views.c @@ -12,6 +12,7 @@ rd_code_view_init(RD_CodeViewState *cv) { cv->initialized = 1; cv->preferred_column = 1; + cv->patch_arena = rd_push_view_arena(); cv->find_text_arena = rd_push_view_arena(); cv->center_cursor = 1; rd_store_view_loading_info(1, 0, 0); @@ -119,6 +120,201 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla cv->wrap_cache_slots = push_array(cv->wrap_arena, RD_CodeViewTLineWrapCacheSlot, cv->wrap_cache_slots_count); } + ////////////////////////////// + //- rjf: do keyboard interaction + // + B32 snap[Axis2_COUNT] = {0}; + UI_Focus(UI_FocusKind_On) if(ui_is_focus_active()) + { + U64 line_count_per_page = ClampBot(num_possible_visible_lines, 10) - 10; + TxtPt *cursor = &rd_regs()->cursor; + TxtPt *mark = &rd_regs()->mark; + S64 *preferred_column = &cv->preferred_column; + B32 change = 0; + for(UI_Event *evt = 0; ui_next_event(&evt);) + { + if(evt->kind != UI_EventKind_Navigate && evt->kind != UI_EventKind_Edit && evt->kind != UI_EventKind_Text) + { + continue; + } + B32 taken = 0; + String8 line = txt_string_from_info_data_line_num(text_info, text_data, cursor->line); + UI_TxtOp single_line_op = ui_single_line_txt_op_from_event(scratch.arena, evt, line, *cursor, *mark); + + //- rjf: invalid single-line op or endpoint units => try multiline + if(evt->delta_unit == UI_EventDeltaUnit_Whole || single_line_op.flags & UI_TxtOpFlag_Invalid) + { + U64 line_count = text_info->lines_count; + String8 prev_line = txt_string_from_info_data_line_num(text_info, text_data, cursor->line-1); + String8 next_line = txt_string_from_info_data_line_num(text_info, text_data, cursor->line+1); + Vec2S32 delta = evt->delta_2s32; + + //- rjf: wrap lines right + if(evt->delta_unit != UI_EventDeltaUnit_Whole && delta.x > 0 && cursor->column == line.size+1 && cursor->line+1 <= line_count) + { + cursor->line += 1; + cursor->column = 1; + *preferred_column = 1; + change = 1; + taken = 1; + } + + //- rjf: wrap lines left + if(evt->delta_unit != UI_EventDeltaUnit_Whole && delta.x < 0 && cursor->column == 1 && cursor->line-1 >= 1) + { + cursor->line -= 1; + cursor->column = prev_line.size+1; + *preferred_column = prev_line.size+1; + change = 1; + taken = 1; + } + + //- rjf: movement down (plain) + if(evt->delta_unit == UI_EventDeltaUnit_Char && delta.y > 0 && cursor->line+1 <= line_count) + { + cursor->line += 1; + cursor->column = Min(*preferred_column, next_line.size+1); + change = 1; + taken = 1; + } + + //- rjf: movement up (plain) + if(evt->delta_unit == UI_EventDeltaUnit_Char && delta.y < 0 && cursor->line-1 >= 1) + { + cursor->line -= 1; + cursor->column = Min(*preferred_column, prev_line.size+1); + change = 1; + taken = 1; + } + + //- rjf: movement down (chunk) + if(evt->delta_unit == UI_EventDeltaUnit_Word && delta.y > 0 && cursor->line+1 <= line_count) + { + for(S64 line_num = cursor->line+1; line_num <= line_count; line_num += 1) + { + String8 line = txt_string_from_info_data_line_num(text_info, text_data, line_num); + U64 line_size = line.size; + if(line_size == 0) + { + cursor->line = line_num; + cursor->column = 1; + break; + } + else if(line_num == line_count) + { + cursor->line = line_num; + cursor->column = line_size+1; + } + } + change = 1; + taken = 1; + } + + //- rjf: movement up (chunk) + if(evt->delta_unit == UI_EventDeltaUnit_Word && delta.y < 0 && cursor->line-1 >= 1) + { + for(S64 line_num = cursor->line-1; line_num > 0; line_num -= 1) + { + String8 line = txt_string_from_info_data_line_num(text_info, text_data, line_num); + U64 line_size = line.size; + if(line_size == 0) + { + cursor->line = line_num; + cursor->column = 1; + break; + } + else if(line_num == 1) + { + cursor->line = line_num; + cursor->column = 1; + } + } + change = 1; + taken = 1; + } + + //- rjf: movement down (page) + if(evt->delta_unit == UI_EventDeltaUnit_Page && delta.y > 0) + { + cursor->line += line_count_per_page; + cursor->column = 1; + cursor->line = Clamp(1, cursor->line, line_count); + change = 1; + taken = 1; + } + + //- rjf: movement up (page) + if(evt->delta_unit == UI_EventDeltaUnit_Page && delta.y < 0) + { + cursor->line -= line_count_per_page; + cursor->column = 1; + cursor->line = Clamp(1, cursor->line, line_count); + change = 1; + taken = 1; + } + + //- rjf: movement to endpoint (+) + if(evt->delta_unit == UI_EventDeltaUnit_Whole && (delta.y > 0 || delta.x > 0)) + { + *cursor = txt_pt(line_count, text_info->lines_count ? dim_1u64(text_info->lines_ranges[text_info->lines_count-1])+1 : 1); + change = 1; + taken = 1; + } + + //- rjf: movement to endpoint (-) + if(evt->delta_unit == UI_EventDeltaUnit_Whole && (delta.y < 0 || delta.x < 0)) + { + *cursor = txt_pt(1, 1); + change = 1; + taken = 1; + } + + //- rjf: stick mark to cursor, when we don't want to keep it in the same spot + if(!(evt->flags & UI_EventFlag_KeepMark)) + { + *mark = *cursor; + } + } + + //- rjf: valid single-line op => do single-line op + else + { +#if 0 + if(single_line_op.replace.size != 0) + { + Rng1U64 range = r1u64(txt_off_from_pt(text_info, &cv->patches, *cursor), + txt_off_from_pt(text_info, &cv->patches, *mark)); + txt_patch_list_push_new(cv->patch_arena, &cv->patches, range, single_line_op.replace); + } +#endif + *cursor = single_line_op.cursor; + *mark = single_line_op.mark; + *preferred_column = cursor->column; + change = 1; + taken = 1; + } + + //- rjf: copy + if(evt->flags & UI_EventFlag_Copy) + { + String8 text = txt_string_from_info_data_txt_rng(text_info, text_data, &cv->patches, txt_rng(*cursor, *mark)); + wm_set_clipboard_text(text); + taken = 1; + } + + //- rjf: consume + if(taken) + { + ui_eat_event(evt); + } + } + } + + ////////////////////////////// + //- rjf: compute current patched text state + // + TXT_Patched text_patched = txt_patched_from_info_data_patches(scratch.arena, text_info, text_data, &cv->patches); + ////////////////////////////// //- rjf: determine visible line range / count // @@ -180,7 +376,7 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla { CFG_Node *query = cfg_node_child_from_string_or_alloc(rd_state->cfg, view, str8_lit("query")); CFG_Node *input = cfg_node_child_from_string_or_alloc(rd_state->cfg, query, str8_lit("input")); - String8 text = txt_string_from_info_data_txt_rng(text_info, text_data, txt_rng(rd_regs()->cursor, rd_regs()->mark)); + String8 text = txt_string_from_info_data_txt_rng(text_info, text_data, &cv->patches, txt_rng(rd_regs()->cursor, rd_regs()->mark)); if(text.size < 256) { cfg_node_new_replace(rd_state->cfg, input, text); @@ -243,7 +439,7 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla visible_line_idx < visible_line_count && line_idx < text_info->lines_count; visible_line_idx += 1, line_idx += 1, line_num += 1) { - code_slice_params.line_text[visible_line_idx] = str8_substr(text_data, text_info->lines_ranges[line_idx]); + code_slice_params.line_text[visible_line_idx] = txt_string_from_info_data_line_num(text_info, text_data, line_num); code_slice_params.line_ranges[visible_line_idx] = text_info->lines_ranges[line_idx]; code_slice_params.line_tokens[visible_line_idx] = slice.line_tokens[visible_line_idx]; } @@ -611,18 +807,6 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla cv->center_cursor = !cv->force_contain_only && (!cv->contain_cursor || (line_num < target_visible_line_num_range.min+4 || target_visible_line_num_range.max-4 < line_num)); } - ////////////////////////////// - //- rjf: do keyboard interaction - // - B32 snap[Axis2_COUNT] = {0}; - UI_Focus(UI_FocusKind_On) - { - if(ui_is_focus_active() && visible_line_num_range.max >= visible_line_num_range.min) - { - snap[Axis2_X] = snap[Axis2_Y] = rd_do_txt_controls(text_info, text_data, ClampBot(num_possible_visible_lines, 10) - 10, &rd_regs()->cursor, &rd_regs()->mark, &cv->preferred_column); - } - } - ////////////////////////////// //- rjf: build container contents // @@ -662,14 +846,14 @@ rd_code_view_build(Arena *arena, RD_CodeViewState *cv, RD_CodeViewBuildFlags fla if(ui_pressed(sig.base) && sig.base.event_flags & WM_Modifier_Ctrl) { ui_kill_action(); - rd_cmd(RD_CmdKind_GoToName, .string = txt_string_from_info_data_txt_rng(text_info, text_data, sig.mouse_expr_rng)); + rd_cmd(RD_CmdKind_GoToName, .string = txt_string_from_info_data_txt_rng(text_info, text_data, &cv->patches, sig.mouse_expr_rng)); } //- rjf: watch expr at mouse if(cv->watch_expr_at_mouse) { cv->watch_expr_at_mouse = 0; - rd_cmd(RD_CmdKind_ToggleWatchExpression, .string = txt_string_from_info_data_txt_rng(text_info, text_data, sig.mouse_expr_rng)); + rd_cmd(RD_CmdKind_ToggleWatchExpression, .string = txt_string_from_info_data_txt_rng(text_info, text_data, &cv->patches, sig.mouse_expr_rng)); } } diff --git a/src/raddbg/raddbg_views.h b/src/raddbg/raddbg_views.h index 530d07b5..7e5b22ab 100644 --- a/src/raddbg/raddbg_views.h +++ b/src/raddbg/raddbg_views.h @@ -47,6 +47,10 @@ struct RD_CodeViewState B32 drifted_for_search; U128 last_hash; + // rjf: patch state + Arena *patch_arena; + TXT_PatchList patches; + // rjf: per-frame command info S64 goto_line_num; B32 center_cursor; diff --git a/src/raddbg/raddbg_widgets.c b/src/raddbg/raddbg_widgets.c index d204e7e1..b4d7c3a0 100644 --- a/src/raddbg/raddbg_widgets.c +++ b/src/raddbg/raddbg_widgets.c @@ -2186,7 +2186,7 @@ rd_code_slice(RD_CodeSliceParams *params, TxtPt *cursor, TxtPt *mark, S64 *prefe TXT_ScopeNode *cursor_scope_node = &txt_scope_node_nil; if(params->text_info != 0) { - cursor_scope_node = txt_scope_node_from_info_pt(params->text_info, rd_regs()->cursor); + cursor_scope_node = txt_scope_node_from_info_pt(params->text_info, params->patches, rd_regs()->cursor); } ////////////////////////////// @@ -2214,7 +2214,7 @@ rd_code_slice(RD_CodeSliceParams *params, TxtPt *cursor, TxtPt *mark, S64 *prefe scope_line_color.w = scope_line_color_t*0.5f; Rng1U64 token_idx_range = scope_n->token_idx_range; Rng1U64 off_range = r1u64(params->text_info->tokens.v[token_idx_range.min].range.min, params->text_info->tokens.v[token_idx_range.max].range.min); - TxtRng txt_range = txt_rng(txt_pt_from_info_off__linear_scan(params->text_info, off_range.min), txt_pt_from_info_off__linear_scan(params->text_info, off_range.max)); + TxtRng txt_range = txt_rng(txt_pt_from_off__linear_scan(params->text_info, params->patches, off_range.min), txt_pt_from_off__linear_scan(params->text_info, params->patches, off_range.max)); //- rjf: single-line scopes (underline) if(txt_range.min.line == txt_range.max.line && contains_1s64(params->line_num_range, txt_range.min.line)) @@ -2999,179 +2999,11 @@ rd_code_slicef(RD_CodeSliceParams *params, TxtPt *cursor, TxtPt *mark, S64 *pref } internal B32 -rd_do_txt_controls(TXT_TextInfo *info, String8 data, U64 line_count_per_page, TxtPt *cursor, TxtPt *mark, S64 *preferred_column) +rd_do_txt_controls(TXT_TextInfo *info, String8 data, TXT_PatchList *patches, U64 line_count_per_page, TxtPt *cursor, TxtPt *mark, S64 *preferred_column) { Temp scratch = scratch_begin(0, 0); B32 change = 0; - for(UI_Event *evt = 0; ui_next_event(&evt);) - { - if(evt->kind != UI_EventKind_Navigate && evt->kind != UI_EventKind_Edit) - { - continue; - } - B32 taken = 0; - String8 line = txt_string_from_info_data_line_num(info, data, cursor->line); - UI_TxtOp single_line_op = ui_single_line_txt_op_from_event(scratch.arena, evt, line, *cursor, *mark); - - //- rjf: invalid single-line op or endpoint units => try multiline - if(evt->delta_unit == UI_EventDeltaUnit_Whole || single_line_op.flags & UI_TxtOpFlag_Invalid) - { - U64 line_count = info->lines_count; - String8 prev_line = txt_string_from_info_data_line_num(info, data, cursor->line-1); - String8 next_line = txt_string_from_info_data_line_num(info, data, cursor->line+1); - Vec2S32 delta = evt->delta_2s32; - - //- rjf: wrap lines right - if(evt->delta_unit != UI_EventDeltaUnit_Whole && delta.x > 0 && cursor->column == line.size+1 && cursor->line+1 <= line_count) - { - cursor->line += 1; - cursor->column = 1; - *preferred_column = 1; - change = 1; - taken = 1; - } - - //- rjf: wrap lines left - if(evt->delta_unit != UI_EventDeltaUnit_Whole && delta.x < 0 && cursor->column == 1 && cursor->line-1 >= 1) - { - cursor->line -= 1; - cursor->column = prev_line.size+1; - *preferred_column = prev_line.size+1; - change = 1; - taken = 1; - } - - //- rjf: movement down (plain) - if(evt->delta_unit == UI_EventDeltaUnit_Char && delta.y > 0 && cursor->line+1 <= line_count) - { - cursor->line += 1; - cursor->column = Min(*preferred_column, next_line.size+1); - change = 1; - taken = 1; - } - - //- rjf: movement up (plain) - if(evt->delta_unit == UI_EventDeltaUnit_Char && delta.y < 0 && cursor->line-1 >= 1) - { - cursor->line -= 1; - cursor->column = Min(*preferred_column, prev_line.size+1); - change = 1; - taken = 1; - } - - //- rjf: movement down (chunk) - if(evt->delta_unit == UI_EventDeltaUnit_Word && delta.y > 0 && cursor->line+1 <= line_count) - { - for(S64 line_num = cursor->line+1; line_num <= line_count; line_num += 1) - { - String8 line = txt_string_from_info_data_line_num(info, data, line_num); - U64 line_size = line.size; - if(line_size == 0) - { - cursor->line = line_num; - cursor->column = 1; - break; - } - else if(line_num == line_count) - { - cursor->line = line_num; - cursor->column = line_size+1; - } - } - change = 1; - taken = 1; - } - - //- rjf: movement up (chunk) - if(evt->delta_unit == UI_EventDeltaUnit_Word && delta.y < 0 && cursor->line-1 >= 1) - { - for(S64 line_num = cursor->line-1; line_num > 0; line_num -= 1) - { - String8 line = txt_string_from_info_data_line_num(info, data, line_num); - U64 line_size = line.size; - if(line_size == 0) - { - cursor->line = line_num; - cursor->column = 1; - break; - } - else if(line_num == 1) - { - cursor->line = line_num; - cursor->column = 1; - } - } - change = 1; - taken = 1; - } - - //- rjf: movement down (page) - if(evt->delta_unit == UI_EventDeltaUnit_Page && delta.y > 0) - { - cursor->line += line_count_per_page; - cursor->column = 1; - cursor->line = Clamp(1, cursor->line, line_count); - change = 1; - taken = 1; - } - - //- rjf: movement up (page) - if(evt->delta_unit == UI_EventDeltaUnit_Page && delta.y < 0) - { - cursor->line -= line_count_per_page; - cursor->column = 1; - cursor->line = Clamp(1, cursor->line, line_count); - change = 1; - taken = 1; - } - - //- rjf: movement to endpoint (+) - if(evt->delta_unit == UI_EventDeltaUnit_Whole && (delta.y > 0 || delta.x > 0)) - { - *cursor = txt_pt(line_count, info->lines_count ? dim_1u64(info->lines_ranges[info->lines_count-1])+1 : 1); - change = 1; - taken = 1; - } - - //- rjf: movement to endpoint (-) - if(evt->delta_unit == UI_EventDeltaUnit_Whole && (delta.y < 0 || delta.x < 0)) - { - *cursor = txt_pt(1, 1); - change = 1; - taken = 1; - } - - //- rjf: stick mark to cursor, when we don't want to keep it in the same spot - if(!(evt->flags & UI_EventFlag_KeepMark)) - { - *mark = *cursor; - } - } - - //- rjf: valid single-line op => do single-line op - else - { - *cursor = single_line_op.cursor; - *mark = single_line_op.mark; - *preferred_column = cursor->column; - change = 1; - taken = 1; - } - - //- rjf: copy - if(evt->flags & UI_EventFlag_Copy) - { - String8 text = txt_string_from_info_data_txt_rng(info, data, txt_rng(*cursor, *mark)); - wm_set_clipboard_text(text); - taken = 1; - } - - //- rjf: consume - if(taken) - { - ui_eat_event(evt); - } - } + scratch_end(scratch); return change; diff --git a/src/raddbg/raddbg_widgets.h b/src/raddbg/raddbg_widgets.h index 6811c1f0..62d29b55 100644 --- a/src/raddbg/raddbg_widgets.h +++ b/src/raddbg/raddbg_widgets.h @@ -108,6 +108,7 @@ struct RD_CodeSliceParams D_LineList *line_infos; DI_KeyList relevant_dbgi_keys; TXT_TextInfo *text_info; + TXT_PatchList *patches; String8 text_data; // rjf: visual parameters @@ -174,7 +175,7 @@ internal UI_BOX_CUSTOM_DRAW(rd_bp_box_draw_extensions); internal RD_CodeSliceSignal rd_code_slice(RD_CodeSliceParams *params, TxtPt *cursor, TxtPt *mark, S64 *preferred_column, String8 string); internal RD_CodeSliceSignal rd_code_slicef(RD_CodeSliceParams *params, TxtPt *cursor, TxtPt *mark, S64 *preferred_column, char *fmt, ...); -internal B32 rd_do_txt_controls(TXT_TextInfo *info, String8 data, U64 line_count_per_page, TxtPt *cursor, TxtPt *mark, S64 *preferred_column); +internal B32 rd_do_txt_controls(TXT_TextInfo *info, String8 data, TXT_PatchList *patches, U64 line_count_per_page, TxtPt *cursor, TxtPt *mark, S64 *preferred_column); //////////////////////////////// //~ rjf: UI Widgets: Fancy Labels diff --git a/src/scratch/ryan_scratch.c b/src/scratch/ryan_scratch.c index 84c2256d..e20bafa9 100644 --- a/src/scratch/ryan_scratch.c +++ b/src/scratch/ryan_scratch.c @@ -44,6 +44,8 @@ entry_point(CmdLine *cmdline) // int & B // (1 + (int)&B) s("int32(*)(int32, int32)"), + s("int32 *(*)(int32, int32)"), + s("int32 **(*)(int32, int32)"), s("123(1, 2, 3)"), s("int32 (*) [100]"), s("(3 * 4) + 2"), diff --git a/src/text/text.c b/src/text/text.c index ba7d2918..1043d970 100644 --- a/src/text/text.c +++ b/src/text/text.c @@ -123,6 +123,19 @@ txt_token_array_from_list(Arena *arena, TXT_TokenList *list) return array; } +//////////////////////////////// +//~ rjf: Patch Functions + +internal void +txt_patch_list_push_new(Arena *arena, TXT_PatchList *list, Rng1U64 range, String8 replace) +{ + TXT_PatchNode *n = push_array(arena, TXT_PatchNode, 1); + n->v.range = range; + n->v.replace = str8_copy(arena, replace); + DLLPushBack(list->first, list->last, n); + list->count += 1; +} + //////////////////////////////// //~ rjf: Lexing Functions @@ -2281,19 +2294,274 @@ txt_token_array_from_string__disasm_x64_intel(Arena *arena, U64 *bytes_processed //~ rjf: Text Info Extractor Helpers internal U64 -txt_off_from_info_pt(TXT_TextInfo *info, TxtPt pt) +txt_patched_off_from_base_off(TXT_PatchList *patches, U64 base_off) +{ + U64 result = base_off; + for EachNode(n, TXT_PatchNode, patches->first) + { + // rjf: this patch occurred before our result -> shift our offset + if(n->v.range.max < result) + { + S64 delta = (S64)n->v.replace.size - (S64)dim_1u64(n->v.range); + S64 result_adjusted = (S64)result + delta; + result_adjusted = ClampBot(0, result_adjusted); + result = (U64)result_adjusted; + } + + // rjf: if this patch covers our offset -> just reset to the beginning of the patch range + else if(contains_1u64(n->v.range, result)) + { + result = n->v.range.min; + } + + // NOTE(rjf): otherwise, this patch occurs *after* our offset, and so our offset remains + // valid. + } + return result; +} + +internal Rng1U64 +txt_patched_range_from_base_range(TXT_PatchList *patches, Rng1U64 base_range) +{ + Rng1U64 result = r1u64(txt_patched_off_from_base_off(patches, base_range.min), + txt_patched_off_from_base_off(patches, base_range.max)); + return result; +} + +internal void +txt_line_map_push(Arena *arena, TXT_LineMap *map, Rng1U64 idx_range, Rng1U64 *ranges, S64 delta) +{ + TXT_LineMapRangeNode *n = push_array(arena, TXT_LineMapRangeNode, 1); + n->idx_range = idx_range; + n->ranges = ranges; + n->delta = delta; + SLLQueuePush(map->first_range, map->last_range, n); + map->total_line_count += dim_1u64(idx_range); +} + +internal U64 +txt_line_num_from_off(TXT_LineMap *map, U64 off) +{ + U64 result = 0; + for(TXT_LineMapRangeNode *n = map->first_range; n != 0; n = n->next) + { + for EachInRange(idx, n->idx_range) + { + Rng1U64 line_range = n->ranges[idx-n->idx_range.min]; + Rng1U64 line_range_shifted = r1u64(line_range.min + n->delta, line_range.max + n->delta); + if(line_range_shifted.min <= off && off <= line_range_shifted.max) + { + result = idx+1; + goto break_all; + } + } + } + break_all:; + return result; +} + +internal Rng1U64 +txt_range_from_line_idx(TXT_LineMap *map, U64 idx) +{ + Rng1U64 result = {0}; + for(TXT_LineMapRangeNode *n = map->first_range; n != 0; n = n->next) + { + if(contains_1u64(n->idx_range, idx)) + { + result = n->ranges[idx - n->idx_range.min]; + result.min = (U64)((S64)result.min + n->delta); + result.max = (U64)((S64)result.max + n->delta); + } + } + return result; +} + +internal TXT_Patched +txt_patched_from_info_data_patches(Arena *arena, TXT_TextInfo *info, String8 data, TXT_PatchList *patches) +{ + Temp scratch = scratch_begin(&arena, 1); + + // rjf: produce default case, where we just have one range which covers the original data + MemoryMap last_memory_map = {0}; + U64 last_size = data.size; + TXT_LineMap last_line_map = {0}; + memory_map_push(scratch.arena, &last_memory_map, r1u64(0, data.size), data.str); + txt_line_map_push(scratch.arena, &last_line_map, r1u64(0, info->lines_count), info->lines_ranges, 0); + + // rjf: apply the patches in order, each being able to slice/dice the previous memory map + for EachNode(n, TXT_PatchNode, patches->first) + { + MemoryMap next_memory_map = {0}; + TXT_LineMap next_line_map = {0}; + + // rjf: compute portion of memory before/after this replace-range + Rng1U64 pre_replace_range = r1u64(0, n->v.range.min); + Rng1U64 post_replace_range = r1u64(n->v.range.max, last_size); + + // rjf: map this replace range -> range of replaced newlines + Rng1U64 replace_line_num_range = {0}; + { + replace_line_num_range.min = txt_line_num_from_off(&last_line_map, n->v.range.min); + replace_line_num_range.max = txt_line_num_from_off(&last_line_map, n->v.range.max); + } + + // rjf: compute portion of lines before/after this replace-range + Rng1U64 pre_replace_line_num_range = r1u64(1, replace_line_num_range.min); + Rng1U64 post_replace_line_num_range = r1u64(replace_line_num_range.max+1, last_line_map.total_line_count+1); + + // rjf: compute delta & next size + S64 size_delta = (S64)n->v.replace.size - (S64)dim_1u64(n->v.range); + U64 next_size = (U64)((S64)last_size + size_delta); + + // rjf: compute line count delta, + list of line ranges inside of replace + S64 line_delta = 0; + Rng1U64List replace_line_ranges = {0}; + { + U64 last_line_start_off = 0; + line_delta -= (S64)dim_1u64(replace_line_num_range); + for EachIndex(idx, n->v.replace.size) + { + if(n->v.replace.str[idx] == '\n') + { + line_delta += 1; + Rng1U64 line_range = r1u64(last_line_start_off, idx); + if(idx > 0 && n->v.replace.str[idx-1] == '\r') + { + line_range.max -= 1; + } + rng1u64_list_push(scratch.arena, &replace_line_ranges, line_range); + last_line_start_off = idx+1; + } + } + if(last_line_start_off < n->v.replace.size) + { + rng1u64_list_push(scratch.arena, &replace_line_ranges, r1u64(last_line_start_off, n->v.replace.size)); + } + } + + // rjf: push all portions of pre-replace / post-replace ranges in previous memory map + { + for EachNode(map_n, MemoryMapRangeNode, last_memory_map.first_range) + { + Rng1U64 range_x_pre = intersect_1u64(pre_replace_range, map_n->v.vaddr_range); + Rng1U64 range_x_post = intersect_1u64(post_replace_range, map_n->v.vaddr_range); + if(range_x_pre.max > range_x_pre.min) + { + memory_map_push(scratch.arena, &next_memory_map, range_x_pre, (U8 *)map_n->v.base + (range_x_pre.min - map_n->v.vaddr_range.min)); + } + if(range_x_post.max > range_x_post.min) + { + Rng1U64 range_x_post_shifted = range_x_post; + range_x_post_shifted.min = (U64)((S64)range_x_post_shifted.min + size_delta); + range_x_post_shifted.max = (U64)((S64)range_x_post_shifted.max + size_delta); + memory_map_push(scratch.arena, &next_memory_map, range_x_post_shifted, (U8 *)map_n->v.base + (range_x_post.min - map_n->v.vaddr_range.min)); + } + } + } + + // rjf: push replaced range + if(n->v.replace.size != 0) + { + memory_map_push(scratch.arena, &next_memory_map, r1u64(n->v.range.min, n->v.range.min + n->v.replace.size), n->v.replace.str); + } + + // rjf: push all portions of pre-replace / post-replace ranges in previous line map + { + for EachNode(map_n, TXT_LineMapRangeNode, last_line_map.first_range) + { + Rng1U64 num_range = shift_1u64(map_n->idx_range, 1); + Rng1U64 range_x_pre = intersect_1u64(pre_replace_line_num_range, num_range); + Rng1U64 range_x_post = intersect_1u64(post_replace_line_num_range, num_range); + if(range_x_pre.max > range_x_pre.min) + { + txt_line_map_push(scratch.arena, &next_line_map, r1u64(range_x_pre.min-1, range_x_pre.max-1), map_n->ranges + (range_x_pre.min - num_range.min), map_n->delta); + } + if(range_x_post.max > range_x_post.min) + { + Rng1U64 range_x_post_shifted = range_x_post; + range_x_post_shifted.min = (U64)((S64)range_x_post_shifted.min + line_delta); + range_x_post_shifted.max = (U64)((S64)range_x_post_shifted.max + line_delta); + txt_line_map_push(scratch.arena, &next_line_map, r1u64(range_x_post_shifted.min-1, range_x_post_shifted.max-1), map_n->ranges + (range_x_post.min - num_range.min), map_n->delta + size_delta); + } + } + } + + // rjf: compute affected line ranges + U64 affected_line_count = dim_1u64(replace_line_num_range)+1; + Rng1U64 *affected_line_ranges = push_array(arena, Rng1U64, affected_line_count); + { + Rng1U64Node *replace_line_range_n = replace_line_ranges.first; + for EachIndex(affected_line_idx, affected_line_count) + { + Rng1U64 replace_line_range = replace_line_range_n->v; + Rng1U64 affected_line_range = r1u64(replace_line_range.min + n->v.range.min, replace_line_range.max + n->v.range.min); + + // rjf: the first line in the range -> take min from original line map + if(affected_line_idx == 0) + { + Rng1U64 og_line_range = txt_range_from_line_idx(&last_line_map, replace_line_num_range.min + affected_line_idx - 1); + affected_line_range.min = og_line_range.min; + } + + // rjf: the last line in the range -> take max from original line map, shift + if(affected_line_idx == affected_line_count-1) + { + Rng1U64 og_line_range = txt_range_from_line_idx(&last_line_map, replace_line_num_range.min + affected_line_idx - 1); + affected_line_range.max = og_line_range.max + size_delta; + } + + // rjf: commit + affected_line_ranges[affected_line_idx] = affected_line_range; + + replace_line_range_n = replace_line_range_n->next; + } + } + + // rjf: push affected line ranges + txt_line_map_push(scratch.arena, &next_line_map, r1u64(replace_line_num_range.min-1, (replace_line_num_range.max+1) - 1), affected_line_ranges, 0); + + // rjf: advance to the next memory map + last_memory_map = next_memory_map; + last_size = next_size; + last_line_map = next_line_map; + } + + // rjf: fill result + TXT_Patched result = {0}; + { + for EachNode(n, MemoryMapRangeNode, last_memory_map.first_range) + { + memory_map_push(arena, &result.memory_map, n->v.vaddr_range, n->v.base); + } + result.size = last_size; + for EachNode(n, TXT_LineMapRangeNode, last_line_map.first_range) + { + txt_line_map_push(arena, &result.line_map, n->idx_range, n->ranges, n->delta); + } + } + + scratch_end(scratch); + return result; +} + +//~ TODO(rjf): old unpatched text viz code: + +internal U64 +txt_off_from_pt(TXT_TextInfo *info, TXT_PatchList *patches, TxtPt pt) { U64 off = 0; - if(1 <= pt.line && pt.line <= info->lines_count) { - Rng1U64 line_range = info->lines_ranges[pt.line-1]; - off = line_range.min + (pt.column-1); + if(1 <= pt.line && pt.line <= info->lines_count) + { + Rng1U64 line_range = info->lines_ranges[pt.line-1]; + off = line_range.min + (pt.column-1); + } } return off; } internal TxtPt -txt_pt_from_info_off__linear_scan(TXT_TextInfo *info, U64 off) +txt_pt_from_off__linear_scan(TXT_TextInfo *info, TXT_PatchList *patches, U64 off) { TxtPt pt = {0}; { @@ -2303,6 +2571,7 @@ txt_pt_from_info_off__linear_scan(TXT_TextInfo *info, U64 off) { pt.line = (S64)line_idx + 1; pt.column = (S64)(off - info->lines_ranges[line_idx].min) + 1; + break; } } } @@ -2491,9 +2760,9 @@ txt_expr_off_range_from_info_data_pt(TXT_TextInfo *info, String8 data, TxtPt pt) } internal String8 -txt_string_from_info_data_txt_rng(TXT_TextInfo *info, String8 data, TxtRng rng) +txt_string_from_info_data_txt_rng(TXT_TextInfo *info, String8 data, TXT_PatchList *patches, TxtRng rng) { - Rng1U64 rng_off = r1u64(txt_off_from_info_pt(info, rng.min), txt_off_from_info_pt(info, rng.max)); + Rng1U64 rng_off = r1u64(txt_off_from_pt(info, patches, rng.min), txt_off_from_pt(info, patches, rng.max)); String8 result = str8_substr(data, rng_off); return result; } @@ -2658,9 +2927,9 @@ txt_scope_node_from_info_off(TXT_TextInfo *info, U64 off) } internal TXT_ScopeNode * -txt_scope_node_from_info_pt(TXT_TextInfo *info, TxtPt pt) +txt_scope_node_from_info_pt(TXT_TextInfo *info, TXT_PatchList *patches, TxtPt pt) { - U64 off = txt_off_from_info_pt(info, pt); + U64 off = txt_off_from_pt(info, patches, pt); TXT_ScopeNode *result = txt_scope_node_from_info_off(info, off); return result; } diff --git a/src/text/text.h b/src/text/text.h index 6425f848..efe94ad4 100644 --- a/src/text/text.h +++ b/src/text/text.h @@ -163,6 +163,73 @@ struct TXT_LineTokensSlice TXT_TokenArray *line_tokens; }; +//////////////////////////////// +//~ rjf: Value Modification Patches + +typedef struct TXT_Patch TXT_Patch; +struct TXT_Patch +{ + Rng1U64 range; + String8 replace; +}; + +typedef struct TXT_PatchNode TXT_PatchNode; +struct TXT_PatchNode +{ + TXT_PatchNode *next; + TXT_PatchNode *prev; + TXT_Patch v; +}; + +typedef struct TXT_PatchList TXT_PatchList; +struct TXT_PatchList +{ + TXT_PatchNode *first; + TXT_PatchNode *last; + U64 count; +}; + +//////////////////////////////// +//~ rjf: Value Reading Types + +typedef struct TXT_LineMapRangeNode TXT_LineMapRangeNode; +struct TXT_LineMapRangeNode +{ + TXT_LineMapRangeNode *next; + Rng1U64 idx_range; + Rng1U64 *ranges; + S64 delta; +}; + +typedef struct TXT_LineMap TXT_LineMap; +struct TXT_LineMap +{ + TXT_LineMapRangeNode *first_range; + TXT_LineMapRangeNode *last_range; + U64 total_line_count; +}; + +typedef struct TXT_Patched TXT_Patched; +struct TXT_Patched +{ + MemoryMap memory_map; + U64 size; + TXT_LineMap line_map; +}; + +typedef struct TXT_Line TXT_Line; +struct TXT_Line +{ + Rng1U64 range; +}; + +typedef struct TXT_LineArray TXT_LineArray; +struct TXT_LineArray +{ + TXT_Line *v; + U64 count; +}; + //////////////////////////////// //~ rjf: Generated Code @@ -202,6 +269,11 @@ internal void txt_token_list_push(Arena *arena, TXT_TokenList *list, TXT_Token * internal TXT_TokenArray txt_token_array_from_chunk_list(Arena *arena, TXT_TokenChunkList *list); internal TXT_TokenArray txt_token_array_from_list(Arena *arena, TXT_TokenList *list); +//////////////////////////////// +//~ rjf: Patch Functions + +internal void txt_patch_list_push_new(Arena *arena, TXT_PatchList *list, Rng1U64 range, String8 replace); + //////////////////////////////// //~ rjf: Lexing Functions @@ -217,17 +289,27 @@ internal TXT_TokenArray txt_token_array_from_string__disasm_x64_intel(Arena *are //////////////////////////////// //~ rjf: Text Info Extractor Helpers -internal U64 txt_off_from_info_pt(TXT_TextInfo *info, TxtPt pt); -internal TxtPt txt_pt_from_info_off__linear_scan(TXT_TextInfo *info, U64 off); +internal U64 txt_patched_off_from_base_off(TXT_PatchList *patches, U64 base_off); +internal Rng1U64 txt_patched_range_from_base_range(TXT_PatchList *patches, Rng1U64 base_range); +internal void txt_line_map_push(Arena *arena, TXT_LineMap *map, Rng1U64 idx_range, Rng1U64 *ranges, S64 delta); +internal U64 txt_line_num_from_off(TXT_LineMap *map, U64 off); +internal Rng1U64 txt_range_from_line_idx(TXT_LineMap *map, U64 idx); +internal TXT_Patched txt_patched_from_info_data_patches(Arena *arena, TXT_TextInfo *info, String8 data, TXT_PatchList *patches); + + +//~ TODO(rjf): old unpatched text viz code: + +internal U64 txt_off_from_pt(TXT_TextInfo *info, TXT_PatchList *patches, TxtPt pt); +internal TxtPt txt_pt_from_off__linear_scan(TXT_TextInfo *info, TXT_PatchList *patches, U64 off); internal TXT_TokenArray txt_token_array_from_info_line_num__linear_scan(TXT_TextInfo *info, S64 line_num); internal Rng1U64 txt_expr_off_range_from_line_off_range_string_tokens(U64 off, Rng1U64 line_range, String8 line_text, TXT_TokenArray *line_tokens); internal Rng1U64 txt_expr_off_range_from_info_data_pt(TXT_TextInfo *info, String8 data, TxtPt pt); -internal String8 txt_string_from_info_data_txt_rng(TXT_TextInfo *info, String8 data, TxtRng rng); +internal String8 txt_string_from_info_data_txt_rng(TXT_TextInfo *info, String8 data, TXT_PatchList *patches, TxtRng rng); internal String8 txt_string_from_info_data_line_num(TXT_TextInfo *info, String8 data, S64 line_num); internal TXT_LineTokensSlice txt_line_tokens_slice_from_info_data_line_range(Arena *arena, TXT_TextInfo *info, String8 data, Rng1S64 line_range); internal TXT_ScopeNode *txt_scope_node_from_info_num(TXT_TextInfo *info, U64 num); internal TXT_ScopeNode *txt_scope_node_from_info_off(TXT_TextInfo *info, U64 off); -internal TXT_ScopeNode *txt_scope_node_from_info_pt(TXT_TextInfo *info, TxtPt pt); +internal TXT_ScopeNode *txt_scope_node_from_info_pt(TXT_TextInfo *info, TXT_PatchList *patches, TxtPt pt); //////////////////////////////// //~ rjf: Artifact Cache Hooks / Lookups diff --git a/src/ui/ui_core.c b/src/ui/ui_core.c index a632976d..34aa9a66 100644 --- a/src/ui/ui_core.c +++ b/src/ui/ui_core.c @@ -262,7 +262,7 @@ ui_single_line_txt_op_from_event(Arena *arena, UI_Event *event, String8 string, //- rjf: determine if this event should be taken, based on bounds of cursor { - if(next_cursor.column > string.size+1 || 1 > next_cursor.column || event->delta_2s32.y != 0) + if(next_cursor.column > string.size+replace.size+1 || 1 > next_cursor.column || event->delta_2s32.y != 0) { flags |= UI_TxtOpFlag_Invalid; }