2018-03-26 05:19:08 +00:00
|
|
|
/*
|
|
|
|
* Mr. 4th Dimention - Allen Webster
|
|
|
|
*
|
|
|
|
* 25.03.2018
|
|
|
|
*
|
|
|
|
* High level edit procedures
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
// TOP
|
|
|
|
|
2018-09-22 00:29:32 +00:00
|
|
|
internal void
|
2019-07-24 07:41:40 +00:00
|
|
|
edit_pre_state_change(Models *models, Heap *heap, Editing_File *file){
|
|
|
|
System_Functions *system = models->system;
|
2019-02-25 23:42:13 +00:00
|
|
|
file_add_dirty_flag(file, DirtyState_UnsavedChanges);
|
2019-06-01 23:58:28 +00:00
|
|
|
file_unmark_edit_finished(&models->working_set, file);
|
2019-02-10 02:56:29 +00:00
|
|
|
Layout *layout = &models->layout;
|
|
|
|
for (Panel *panel = layout_get_first_open_panel(layout);
|
|
|
|
panel != 0;
|
|
|
|
panel = layout_get_next_open_panel(layout, panel)){
|
|
|
|
View *view = panel->view;
|
2019-02-27 05:49:35 +00:00
|
|
|
if (view->file == file){
|
2019-07-24 07:41:40 +00:00
|
|
|
Full_Cursor render_cursor = view_get_render_cursor(models, view);
|
|
|
|
Full_Cursor target_cursor = view_get_render_cursor_target(models, view);
|
2019-06-20 23:43:27 +00:00
|
|
|
view->temp_view_top_left_pos = (i32)render_cursor.pos;
|
|
|
|
view->temp_view_top_left_target_pos = (i32)target_cursor.pos;
|
2019-02-10 02:56:29 +00:00
|
|
|
}
|
|
|
|
}
|
2018-03-26 05:19:08 +00:00
|
|
|
}
|
|
|
|
|
2018-09-04 00:37:54 +00:00
|
|
|
internal void
|
2018-09-22 00:29:32 +00:00
|
|
|
edit_fix_markers__write_workspace_markers(Dynamic_Workspace *workspace, Buffer_ID buffer_id,
|
|
|
|
Cursor_With_Index *cursors, Cursor_With_Index *r_cursors, i32 *cursor_count, i32 *r_cursor_count){
|
2018-09-04 00:37:54 +00:00
|
|
|
for (Managed_Buffer_Markers_Header *node = workspace->buffer_markers_list.first;
|
|
|
|
node != 0;
|
|
|
|
node = node->next){
|
2018-09-22 00:29:32 +00:00
|
|
|
if (node->buffer_id != buffer_id) continue;
|
|
|
|
Marker *markers = (Marker*)(node + 1);
|
|
|
|
Assert(sizeof(*markers) == node->std_header.item_size);
|
|
|
|
i32 count = node->std_header.count;
|
|
|
|
for (i32 i = 0; i < count; i += 1){
|
|
|
|
if (markers[i].lean_right){
|
|
|
|
write_cursor_with_index(r_cursors, r_cursor_count, markers[i].pos);
|
|
|
|
}
|
|
|
|
else{
|
|
|
|
write_cursor_with_index(cursors , cursor_count , markers[i].pos);
|
2018-09-04 00:37:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
internal void
|
2018-09-22 00:29:32 +00:00
|
|
|
edit_fix_markers__read_workspace_markers(Dynamic_Workspace *workspace, Buffer_ID buffer_id,
|
|
|
|
Cursor_With_Index *cursors, Cursor_With_Index *r_cursors, i32 *cursor_count, i32 *r_cursor_count){
|
2018-09-04 00:37:54 +00:00
|
|
|
for (Managed_Buffer_Markers_Header *node = workspace->buffer_markers_list.first;
|
|
|
|
node != 0;
|
|
|
|
node = node->next){
|
2018-09-22 00:29:32 +00:00
|
|
|
if (node->buffer_id != buffer_id) continue;
|
|
|
|
Marker *markers = (Marker*)(node + 1);
|
|
|
|
Assert(sizeof(*markers) == node->std_header.item_size);
|
|
|
|
i32 count = node->std_header.count;
|
|
|
|
for (i32 i = 0; i < count; i += 1){
|
|
|
|
if (markers[i].lean_right){
|
|
|
|
markers[i].pos = r_cursors[(*r_cursor_count)++].pos;
|
|
|
|
}
|
|
|
|
else{
|
|
|
|
markers[i].pos = cursors[(*cursor_count)++].pos;
|
2018-09-04 00:37:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-10 02:56:29 +00:00
|
|
|
internal f32
|
|
|
|
edit_fix_markers__compute_scroll_y(i32 line_height, f32 old_y_val, f32 new_y_val_aligned){
|
2019-06-01 23:58:28 +00:00
|
|
|
f32 y_offset = mod_f32(old_y_val, line_height);
|
2019-02-10 02:56:29 +00:00
|
|
|
f32 y_position = new_y_val_aligned + y_offset;
|
|
|
|
return(y_position);
|
|
|
|
}
|
|
|
|
|
|
|
|
internal i32
|
|
|
|
edit_fix_markers__compute_scroll_y(i32 line_height, i32 old_y_val, f32 new_y_val_aligned){
|
|
|
|
return((i32)edit_fix_markers__compute_scroll_y(line_height, (f32)old_y_val, new_y_val_aligned));
|
|
|
|
}
|
|
|
|
|
2018-03-26 05:19:08 +00:00
|
|
|
internal void
|
2019-03-22 01:27:28 +00:00
|
|
|
edit_fix_markers(System_Functions *system, Models *models, Editing_File *file, Edit edit){
|
2019-02-08 10:03:48 +00:00
|
|
|
Layout *layout = &models->layout;
|
|
|
|
|
2018-10-02 22:04:57 +00:00
|
|
|
Lifetime_Object *file_lifetime_object = file->lifetime_object;
|
2019-08-12 09:16:04 +00:00
|
|
|
Buffer_ID file_id = file->id;
|
2018-10-02 22:04:57 +00:00
|
|
|
Assert(file_lifetime_object != 0);
|
|
|
|
|
2019-02-10 02:56:29 +00:00
|
|
|
i32 cursor_max = layout_get_open_panel_count(layout)*4;
|
2018-10-02 22:04:57 +00:00
|
|
|
i32 total_marker_count = 0;
|
|
|
|
{
|
|
|
|
total_marker_count += file_lifetime_object->workspace.total_marker_count;
|
|
|
|
|
|
|
|
i32 key_count = file_lifetime_object->key_count;
|
|
|
|
i32 key_index = 0;
|
|
|
|
for (Lifetime_Key_Ref_Node *key_node = file_lifetime_object->key_node_first;
|
|
|
|
key_node != 0;
|
|
|
|
key_node = key_node->next){
|
|
|
|
i32 count = clamp_top(lifetime_key_reference_per_node, key_count - key_index);
|
|
|
|
for (i32 i = 0; i < count; i += 1){
|
|
|
|
Lifetime_Key *key = key_node->keys[i];
|
|
|
|
total_marker_count += key->dynamic_workspace.total_marker_count;
|
|
|
|
}
|
|
|
|
key_index += count;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cursor_max += total_marker_count;
|
2019-06-01 23:58:28 +00:00
|
|
|
|
|
|
|
Arena *scratch = &models->mem.arena;
|
|
|
|
Temp_Memory cursor_temp = begin_temp(scratch);
|
|
|
|
Cursor_With_Index *cursors = push_array(scratch, Cursor_With_Index, cursor_max);
|
|
|
|
Cursor_With_Index *r_cursors = push_array(scratch, Cursor_With_Index, cursor_max);
|
2018-03-26 05:19:08 +00:00
|
|
|
i32 cursor_count = 0;
|
|
|
|
i32 r_cursor_count = 0;
|
2018-09-04 00:37:54 +00:00
|
|
|
Assert(cursors != 0);
|
|
|
|
Assert(r_cursors != 0);
|
2018-03-26 05:19:08 +00:00
|
|
|
|
2019-02-05 09:13:38 +00:00
|
|
|
for (Panel *panel = layout_get_first_open_panel(layout);
|
|
|
|
panel != 0;
|
|
|
|
panel = layout_get_next_open_panel(layout, panel)){
|
2018-03-26 05:19:08 +00:00
|
|
|
View *view = panel->view;
|
2019-02-27 05:49:35 +00:00
|
|
|
if (view->file == file){
|
2019-02-09 22:48:53 +00:00
|
|
|
File_Edit_Positions edit_pos = view_get_edit_pos(view);
|
2019-06-20 23:43:27 +00:00
|
|
|
write_cursor_with_index(cursors, &cursor_count, (i32)edit_pos.cursor_pos);
|
|
|
|
write_cursor_with_index(cursors, &cursor_count, (i32)view->mark);
|
2019-02-10 09:18:34 +00:00
|
|
|
write_cursor_with_index(cursors, &cursor_count, view->temp_view_top_left_pos);
|
|
|
|
write_cursor_with_index(cursors, &cursor_count, view->temp_view_top_left_target_pos);
|
2018-03-26 05:19:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-04 03:51:43 +00:00
|
|
|
edit_fix_markers__write_workspace_markers(&file_lifetime_object->workspace, file_id, cursors, r_cursors, &cursor_count, &r_cursor_count);
|
2018-09-04 00:37:54 +00:00
|
|
|
|
2018-09-04 19:16:46 +00:00
|
|
|
{
|
|
|
|
i32 key_count = file_lifetime_object->key_count;
|
|
|
|
i32 key_index = 0;
|
|
|
|
for (Lifetime_Key_Ref_Node *key_node = file_lifetime_object->key_node_first;
|
|
|
|
key_node != 0;
|
|
|
|
key_node = key_node->next){
|
|
|
|
i32 count = clamp_top(lifetime_key_reference_per_node, key_count - key_index);
|
|
|
|
for (i32 i = 0; i < count; i += 1){
|
|
|
|
Lifetime_Key *key = key_node->keys[i];
|
2019-02-04 03:51:43 +00:00
|
|
|
edit_fix_markers__write_workspace_markers(&key->dynamic_workspace, file_id, cursors, r_cursors, &cursor_count, &r_cursor_count);
|
2018-09-04 19:16:46 +00:00
|
|
|
}
|
|
|
|
key_index += count;
|
2018-03-26 05:19:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cursor_count > 0 || r_cursor_count > 0){
|
2019-02-22 12:43:12 +00:00
|
|
|
buffer_sort_cursors( cursors, cursor_count);
|
|
|
|
buffer_sort_cursors(r_cursors, r_cursor_count);
|
2019-03-22 01:27:28 +00:00
|
|
|
buffer_update_cursors( cursors, cursor_count, edit.range.first, edit.range.one_past_last, edit.length, false);
|
|
|
|
buffer_update_cursors(r_cursors, r_cursor_count, edit.range.first, edit.range.one_past_last, edit.length, true);
|
2019-02-22 12:43:12 +00:00
|
|
|
buffer_unsort_cursors( cursors, cursor_count);
|
|
|
|
buffer_unsort_cursors(r_cursors, r_cursor_count);
|
2018-03-26 05:19:08 +00:00
|
|
|
|
2019-07-31 20:43:27 +00:00
|
|
|
Face *face = font_set_face_from_id(&models->font_set, file->settings.font_id);
|
|
|
|
Assert(face != 0);
|
|
|
|
|
2018-03-26 05:19:08 +00:00
|
|
|
cursor_count = 0;
|
|
|
|
r_cursor_count = 0;
|
2019-02-05 09:13:38 +00:00
|
|
|
for (Panel *panel = layout_get_first_open_panel(layout);
|
|
|
|
panel != 0;
|
|
|
|
panel = layout_get_next_open_panel(layout, panel)){
|
2018-03-26 05:19:08 +00:00
|
|
|
View *view = panel->view;
|
2019-02-27 05:49:35 +00:00
|
|
|
if (view->file == file){
|
2018-03-26 05:19:08 +00:00
|
|
|
i32 cursor_pos = cursors[cursor_count++].pos;
|
2019-07-24 07:41:40 +00:00
|
|
|
Full_Cursor new_cursor = file_compute_cursor(models, file, seek_pos(cursor_pos));
|
2018-03-26 05:19:08 +00:00
|
|
|
|
2019-02-09 22:48:53 +00:00
|
|
|
File_Edit_Positions edit_pos = view_get_edit_pos(view);
|
|
|
|
GUI_Scroll_Vars scroll = edit_pos.scroll;
|
2018-03-26 05:19:08 +00:00
|
|
|
|
2019-02-10 09:18:34 +00:00
|
|
|
view->mark = cursors[cursor_count++].pos;
|
2019-07-31 20:43:27 +00:00
|
|
|
|
|
|
|
i32 line_height = (i32)face->height;
|
2019-02-10 02:56:29 +00:00
|
|
|
i32 top_left_pos = cursors[cursor_count++].pos;
|
|
|
|
i32 top_left_target_pos = cursors[cursor_count++].pos;
|
|
|
|
f32 new_y_val_aligned = 0;
|
2019-02-10 09:18:34 +00:00
|
|
|
if (view->temp_view_top_left_pos != top_left_pos){
|
2019-07-24 07:41:40 +00:00
|
|
|
Full_Cursor new_position_cursor = file_compute_cursor(models, file, seek_pos(top_left_pos));
|
2018-03-26 05:19:08 +00:00
|
|
|
if (file->settings.unwrapped_lines){
|
2019-02-10 02:56:29 +00:00
|
|
|
new_y_val_aligned = new_position_cursor.unwrapped_y;
|
|
|
|
}
|
|
|
|
else{
|
|
|
|
new_y_val_aligned = new_position_cursor.wrapped_y;
|
|
|
|
}
|
|
|
|
scroll.scroll_y = edit_fix_markers__compute_scroll_y(line_height, scroll.scroll_y, new_y_val_aligned);
|
|
|
|
}
|
2019-02-10 09:18:34 +00:00
|
|
|
if (view->temp_view_top_left_target_pos != top_left_target_pos){
|
2019-02-10 02:56:29 +00:00
|
|
|
if (top_left_target_pos != top_left_pos){
|
2019-07-24 07:41:40 +00:00
|
|
|
Full_Cursor new_position_cursor = file_compute_cursor(models, file, seek_pos(top_left_target_pos));
|
2019-02-10 02:56:29 +00:00
|
|
|
if (file->settings.unwrapped_lines){
|
|
|
|
new_y_val_aligned = new_position_cursor.unwrapped_y;
|
|
|
|
}
|
|
|
|
else{
|
|
|
|
new_y_val_aligned = new_position_cursor.wrapped_y;
|
|
|
|
}
|
2018-03-26 05:19:08 +00:00
|
|
|
}
|
2019-02-10 02:56:29 +00:00
|
|
|
scroll.target_y = edit_fix_markers__compute_scroll_y(line_height, scroll.target_y, new_y_val_aligned);
|
2018-03-26 05:19:08 +00:00
|
|
|
}
|
|
|
|
|
2019-02-27 05:49:35 +00:00
|
|
|
view_set_cursor_and_scroll(models, view, new_cursor, true, scroll);
|
2018-03-26 05:19:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-04 03:51:43 +00:00
|
|
|
edit_fix_markers__read_workspace_markers(&file_lifetime_object->workspace, file_id, cursors, r_cursors, &cursor_count, &r_cursor_count);
|
2018-09-04 00:37:54 +00:00
|
|
|
|
|
|
|
i32 key_count = file_lifetime_object->key_count;
|
|
|
|
i32 key_index = 0;
|
|
|
|
for (Lifetime_Key_Ref_Node *key_node = file_lifetime_object->key_node_first;
|
|
|
|
key_node != 0;
|
|
|
|
key_node = key_node->next){
|
|
|
|
i32 count = clamp_top(lifetime_key_reference_per_node, key_count - key_index);
|
|
|
|
for (i32 i = 0; i < count; i += 1){
|
|
|
|
Lifetime_Key *key = key_node->keys[i];
|
2019-02-04 03:51:43 +00:00
|
|
|
edit_fix_markers__read_workspace_markers(&key->dynamic_workspace, file_id, cursors, r_cursors, &cursor_count, &r_cursor_count);
|
2018-03-26 05:19:08 +00:00
|
|
|
}
|
2018-09-04 00:37:54 +00:00
|
|
|
key_index += count;
|
2018-03-26 05:19:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-01 23:58:28 +00:00
|
|
|
end_temp(cursor_temp);
|
2018-03-26 05:19:08 +00:00
|
|
|
}
|
|
|
|
|
2019-02-08 10:03:48 +00:00
|
|
|
internal void
|
2019-06-01 23:33:31 +00:00
|
|
|
edit_single(System_Functions *system, Models *models, Editing_File *file, Range range, String_Const_u8 string, Edit_Behaviors behaviors){
|
2019-04-05 23:30:24 +00:00
|
|
|
Edit edit = {};
|
2019-06-01 23:33:31 +00:00
|
|
|
edit.str = (char*)string.str;
|
|
|
|
edit.length = (i32)string.size;
|
2019-04-05 23:30:24 +00:00
|
|
|
edit.range = range;
|
|
|
|
|
2018-03-26 05:19:08 +00:00
|
|
|
|
2019-02-08 10:03:48 +00:00
|
|
|
Gap_Buffer *buffer = &file->state.buffer;
|
|
|
|
Assert(0 <= edit.range.first);
|
|
|
|
Assert(edit.range.first <= edit.range.one_past_last);
|
|
|
|
Assert(edit.range.one_past_last <= buffer_size(buffer));
|
|
|
|
|
2019-06-01 23:58:28 +00:00
|
|
|
Heap *heap = &models->mem.heap;
|
|
|
|
|
2019-02-08 10:03:48 +00:00
|
|
|
// NOTE(allen): history update
|
|
|
|
if (!behaviors.do_not_post_to_history){
|
2019-03-28 03:06:17 +00:00
|
|
|
// TODO(allen): if the edit number counter is not updated, maybe auto-merge edits? Wouldn't that just work?
|
2019-02-08 10:03:48 +00:00
|
|
|
history_dump_records_after_index(&file->state.history, file->state.current_record_index);
|
|
|
|
history_record_edit(heap, &models->global_history, &file->state.history, buffer, edit);
|
|
|
|
file->state.current_record_index = history_get_record_count(&file->state.history);
|
|
|
|
}
|
|
|
|
|
2018-03-26 05:19:08 +00:00
|
|
|
// NOTE(allen): fixing stuff beforewards????
|
2019-07-24 07:41:40 +00:00
|
|
|
edit_pre_state_change(models, heap, file);
|
2019-03-22 01:27:28 +00:00
|
|
|
|
|
|
|
// NOTE(allen): edit range hook
|
|
|
|
if (models->hook_file_edit_range != 0){
|
2019-08-12 09:16:04 +00:00
|
|
|
models->hook_file_edit_range(&models->app_links, file->id, edit.range, SCu8(edit.str, edit.length));
|
2019-03-22 01:27:28 +00:00
|
|
|
}
|
2018-03-26 05:19:08 +00:00
|
|
|
|
2019-02-04 03:51:43 +00:00
|
|
|
// NOTE(allen): expand spec, compute shift
|
2019-02-08 10:03:48 +00:00
|
|
|
i32 shift_amount = buffer_replace_range_compute_shift(edit.range.first, edit.range.one_past_last, edit.length);
|
2018-03-26 05:19:08 +00:00
|
|
|
|
2019-02-04 03:51:43 +00:00
|
|
|
// NOTE(allen): actual text replacement
|
2018-03-26 05:19:08 +00:00
|
|
|
i32 request_amount = 0;
|
2019-06-01 23:58:28 +00:00
|
|
|
for (;buffer_replace_range(buffer, edit.range.first, edit.range.one_past_last, edit.str, edit.length, shift_amount, &request_amount);){
|
2018-03-26 05:19:08 +00:00
|
|
|
void *new_data = 0;
|
|
|
|
if (request_amount > 0){
|
2018-08-18 08:16:52 +00:00
|
|
|
new_data = heap_allocate(heap, request_amount);
|
2018-03-26 05:19:08 +00:00
|
|
|
}
|
2019-02-08 10:03:48 +00:00
|
|
|
void *old_data = buffer_edit_provide_memory(buffer, new_data, request_amount);
|
2019-03-28 03:06:17 +00:00
|
|
|
if (old_data != 0){
|
2018-08-18 08:16:52 +00:00
|
|
|
heap_free(heap, old_data);
|
2018-03-26 05:19:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-04 03:51:43 +00:00
|
|
|
// NOTE(allen): line meta data
|
2019-02-08 10:03:48 +00:00
|
|
|
i32 line_start = buffer_get_line_number(buffer, edit.range.first);
|
|
|
|
i32 line_end = buffer_get_line_number(buffer, edit.range.one_past_last);
|
2018-03-26 05:19:08 +00:00
|
|
|
i32 replaced_line_count = line_end - line_start;
|
2019-02-08 10:03:48 +00:00
|
|
|
i32 new_line_count = buffer_count_newlines(buffer, edit.range.first, edit.range.first + edit.length);
|
2018-03-26 05:19:08 +00:00
|
|
|
i32 line_shift = new_line_count - replaced_line_count;
|
|
|
|
|
2018-08-18 08:16:52 +00:00
|
|
|
file_grow_starts_as_needed(heap, buffer, line_shift);
|
2018-03-26 05:19:08 +00:00
|
|
|
buffer_remeasure_starts(buffer, line_start, line_end, line_shift, shift_amount);
|
|
|
|
|
2018-08-18 08:16:52 +00:00
|
|
|
file_allocate_character_starts_as_needed(heap, file);
|
2019-07-21 18:16:34 +00:00
|
|
|
buffer_remeasure_character_starts(system, buffer, line_start, line_end, line_shift, file->state.character_starts, 0, file->settings.virtual_white);
|
2018-03-26 05:19:08 +00:00
|
|
|
|
2019-02-04 03:51:43 +00:00
|
|
|
// NOTE(allen): token fixing
|
|
|
|
if (file->settings.tokens_exist){
|
2019-02-08 10:03:48 +00:00
|
|
|
file_relex(system, models, file, edit.range.first, edit.range.one_past_last, shift_amount);
|
2019-02-04 03:51:43 +00:00
|
|
|
}
|
2018-03-26 05:19:08 +00:00
|
|
|
|
2019-02-07 09:28:26 +00:00
|
|
|
// NOTE(allen): wrap meta data
|
2019-07-24 07:41:40 +00:00
|
|
|
Face *face = font_set_face_from_id(&models->font_set, file->settings.font_id);
|
2019-07-21 18:16:34 +00:00
|
|
|
Assert(face != 0);
|
|
|
|
|
|
|
|
file_measure_wraps(system, &models->mem, file, face);
|
2019-02-07 09:28:26 +00:00
|
|
|
|
|
|
|
// NOTE(allen): cursor fixing
|
2019-02-08 10:03:48 +00:00
|
|
|
edit_fix_markers(system, models, file, edit);
|
2019-02-07 09:28:26 +00:00
|
|
|
|
2019-02-04 03:51:43 +00:00
|
|
|
// NOTE(allen): mark edit finished
|
|
|
|
if (file->settings.tokens_exist){
|
|
|
|
if (file->settings.virtual_white){
|
|
|
|
file_mark_edit_finished(&models->working_set, file);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else{
|
|
|
|
file_mark_edit_finished(&models->working_set, file);
|
|
|
|
}
|
2018-03-26 05:19:08 +00:00
|
|
|
}
|
|
|
|
|
2018-09-14 19:02:02 +00:00
|
|
|
internal void
|
|
|
|
file_end_file(Models *models, Editing_File *file){
|
2018-03-26 05:19:08 +00:00
|
|
|
if (models->hook_end_file != 0){
|
2019-08-12 09:16:04 +00:00
|
|
|
models->hook_end_file(&models->app_links, file->id);
|
2018-03-26 05:19:08 +00:00
|
|
|
}
|
2018-09-14 19:02:02 +00:00
|
|
|
Heap *heap = &models->mem.heap;
|
|
|
|
Lifetime_Allocator *lifetime_allocator = &models->lifetime_allocator;
|
|
|
|
lifetime_free_object(heap, lifetime_allocator, file->lifetime_object);
|
|
|
|
file->lifetime_object = lifetime_alloc_object(heap, lifetime_allocator, DynamicWorkspace_Buffer, file);
|
|
|
|
}
|
|
|
|
|
2018-03-26 05:19:08 +00:00
|
|
|
internal void
|
2019-02-08 10:03:48 +00:00
|
|
|
edit__apply_record_forward(System_Functions *system, Models *models, Editing_File *file, Record *record, Edit_Behaviors behaviors_prototype){
|
|
|
|
// NOTE(allen): // NOTE(allen): // NOTE(allen): // NOTE(allen): // NOTE(allen):
|
|
|
|
// Whenever you change this also change the backward version!
|
2018-03-26 05:19:08 +00:00
|
|
|
|
2019-02-08 10:03:48 +00:00
|
|
|
switch (record->kind){
|
|
|
|
case RecordKind_Single:
|
|
|
|
{
|
2019-06-01 23:33:31 +00:00
|
|
|
String_Const_u8 str = SCu8(record->single.str_forward, record->single.length_forward);
|
|
|
|
Range range = make_range(record->single.first, record->single.first + record->single.length_backward);
|
2019-04-05 23:30:24 +00:00
|
|
|
edit_single(system, models, file, range, str, behaviors_prototype);
|
2019-02-08 10:03:48 +00:00
|
|
|
}break;
|
|
|
|
|
|
|
|
case RecordKind_Group:
|
|
|
|
{
|
|
|
|
Node *sentinel = &record->group.children;
|
|
|
|
for (Node *node = sentinel->next;
|
|
|
|
node != sentinel;
|
|
|
|
node = node->next){
|
2019-02-12 06:11:08 +00:00
|
|
|
Record *sub_record = CastFromMember(Record, node, node);
|
|
|
|
edit__apply_record_forward(system, models, file, sub_record, behaviors_prototype);
|
2019-02-08 10:03:48 +00:00
|
|
|
}
|
|
|
|
}break;
|
2019-02-08 11:18:48 +00:00
|
|
|
|
|
|
|
default:
|
|
|
|
{
|
2019-06-01 23:58:28 +00:00
|
|
|
InvalidPath;
|
2019-02-08 11:18:48 +00:00
|
|
|
}break;
|
2019-02-08 10:03:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
internal void
|
|
|
|
edit__apply_record_backward(System_Functions *system, Models *models, Editing_File *file, Record *record, Edit_Behaviors behaviors_prototype){
|
|
|
|
// NOTE(allen): // NOTE(allen): // NOTE(allen): // NOTE(allen): // NOTE(allen):
|
|
|
|
// Whenever you change this also change the forward version!
|
|
|
|
|
|
|
|
switch (record->kind){
|
|
|
|
case RecordKind_Single:
|
|
|
|
{
|
2019-06-01 23:33:31 +00:00
|
|
|
String_Const_u8 str = SCu8(record->single.str_backward, record->single.length_backward);
|
|
|
|
Range range = make_range(record->single.first, record->single.first + record->single.length_forward);
|
2019-04-05 23:30:24 +00:00
|
|
|
edit_single(system, models, file, range, str, behaviors_prototype);
|
2019-02-08 10:03:48 +00:00
|
|
|
}break;
|
2018-03-26 05:19:08 +00:00
|
|
|
|
2019-02-08 10:03:48 +00:00
|
|
|
case RecordKind_Group:
|
|
|
|
{
|
|
|
|
Node *sentinel = &record->group.children;
|
|
|
|
for (Node *node = sentinel->prev;
|
|
|
|
node != sentinel;
|
|
|
|
node = node->prev){
|
2019-02-12 06:11:08 +00:00
|
|
|
Record *sub_record = CastFromMember(Record, node, node);
|
|
|
|
edit__apply_record_backward(system, models, file, sub_record, behaviors_prototype);
|
2019-02-08 10:03:48 +00:00
|
|
|
}
|
|
|
|
}break;
|
2019-02-08 11:18:48 +00:00
|
|
|
|
|
|
|
default:
|
|
|
|
{
|
2019-06-01 23:58:28 +00:00
|
|
|
InvalidPath;
|
2019-02-08 11:18:48 +00:00
|
|
|
}break;
|
2018-03-26 05:19:08 +00:00
|
|
|
}
|
2019-02-08 10:03:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
internal void
|
|
|
|
edit_change_current_history_state(System_Functions *system, Models *models, Editing_File *file, i32 target_index){
|
|
|
|
History *history = &file->state.history;
|
|
|
|
if (history->activated && file->state.current_record_index != target_index){
|
|
|
|
Assert(0 <= target_index && target_index <= history->record_count);
|
|
|
|
|
|
|
|
i32 current = file->state.current_record_index;
|
|
|
|
Record *record = history_get_record(history, current);
|
|
|
|
Assert(record != 0);
|
|
|
|
Record *dummy_record = history_get_dummy_record(history);
|
|
|
|
|
|
|
|
Edit_Behaviors behaviors_prototype = {};
|
|
|
|
behaviors_prototype.do_not_post_to_history = true;
|
|
|
|
|
|
|
|
if (current < target_index){
|
|
|
|
do{
|
|
|
|
current += 1;
|
|
|
|
record = CastFromMember(Record, node, record->node.next);
|
|
|
|
Assert(record != dummy_record);
|
|
|
|
edit__apply_record_forward(system, models, file, record, behaviors_prototype);
|
|
|
|
} while (current != target_index);
|
|
|
|
}
|
|
|
|
else{
|
|
|
|
do{
|
|
|
|
Assert(record != dummy_record);
|
|
|
|
edit__apply_record_backward(system, models, file, record, behaviors_prototype);
|
|
|
|
current -= 1;
|
|
|
|
record = CastFromMember(Record, node, record->node.prev);
|
|
|
|
} while (current != target_index);
|
|
|
|
}
|
|
|
|
|
|
|
|
file->state.current_record_index = current;
|
2018-03-26 05:19:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-17 20:05:14 +00:00
|
|
|
internal b32
|
|
|
|
edit_merge_history_range(Models *models, Editing_File *file, History_Record_Index first_index, History_Record_Index last_index, Record_Merge_Flag flags){
|
|
|
|
b32 result = false;
|
|
|
|
History *history = &file->state.history;
|
|
|
|
if (history_is_activated(history)){
|
|
|
|
i32 max_index = history_get_record_count(history);
|
|
|
|
first_index = clamp_bot(1, first_index);
|
|
|
|
if (first_index <= last_index && last_index <= max_index){
|
|
|
|
if (first_index < last_index){
|
|
|
|
i32 current_index = file->state.current_record_index;
|
|
|
|
if (first_index <= current_index && current_index < last_index){
|
|
|
|
System_Functions *system = models->system;
|
|
|
|
u32 in_range_handler = (flags & bitmask_2);
|
|
|
|
switch (in_range_handler){
|
|
|
|
case RecordMergeFlag_StateInRange_MoveStateForward:
|
|
|
|
{
|
|
|
|
edit_change_current_history_state(system, models, file, last_index);
|
|
|
|
current_index = last_index;
|
|
|
|
}break;
|
|
|
|
|
|
|
|
case RecordMergeFlag_StateInRange_MoveStateBackward:
|
|
|
|
{
|
|
|
|
edit_change_current_history_state(system, models, file, first_index);
|
|
|
|
current_index = first_index;
|
|
|
|
}break;
|
|
|
|
|
|
|
|
case RecordMergeFlag_StateInRange_ErrorOut:
|
|
|
|
{
|
|
|
|
goto done;
|
|
|
|
}break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
history_merge_records(&models->mem.arena, &models->mem.heap, history, first_index, last_index);
|
|
|
|
if (current_index >= last_index){
|
|
|
|
current_index -= (last_index - first_index);
|
|
|
|
}
|
|
|
|
file->state.current_record_index = current_index;
|
|
|
|
}
|
|
|
|
result = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
done:;
|
|
|
|
return(result);
|
|
|
|
}
|
|
|
|
|
|
|
|
internal b32
|
|
|
|
edit_batch(System_Functions *system, Models *models, Editing_File *file, char *str, Buffer_Edit *edits, i32 edit_count, Edit_Behaviors behaviors){
|
|
|
|
b32 result = true;
|
|
|
|
if (edit_count > 0){
|
|
|
|
History_Record_Index start_index = 0;
|
|
|
|
if (history_is_activated(&file->state.history)){
|
2019-06-21 02:31:22 +00:00
|
|
|
start_index = file->state.current_record_index;
|
2019-06-17 20:05:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Buffer_Edit *edit_in = edits;
|
|
|
|
Buffer_Edit *one_past_last = edits + edit_count;
|
|
|
|
i32 shift = 0;
|
|
|
|
for (;edit_in < one_past_last; edit_in += 1){
|
|
|
|
String_Const_u8 insert_string = SCu8(str + edit_in->str_start, edit_in->len);
|
|
|
|
Range edit_range = make_range(edit_in->start, edit_in->end);
|
|
|
|
edit_range.first += shift;
|
|
|
|
edit_range.one_past_last += shift;
|
|
|
|
i32 size = buffer_size(&file->state.buffer);
|
|
|
|
if (0 <= edit_range.first && edit_range.first <= edit_range.one_past_last && edit_range.one_past_last <= size){
|
|
|
|
edit_single(system, models, file, edit_range, insert_string, behaviors);
|
|
|
|
shift += replace_range_compute_shift(edit_range, (i32)insert_string.size);
|
|
|
|
}
|
|
|
|
else{
|
|
|
|
result = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (history_is_activated(&file->state.history)){
|
2019-06-21 02:31:22 +00:00
|
|
|
History_Record_Index last_index = file->state.current_record_index;
|
2019-06-17 20:05:14 +00:00
|
|
|
if (start_index + 1 < last_index){
|
|
|
|
edit_merge_history_range(models, file, start_index + 1, last_index, RecordMergeFlag_StateInRange_ErrorOut);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return(result);
|
|
|
|
}
|
|
|
|
|
2019-04-05 23:30:24 +00:00
|
|
|
////////////////////////////////
|
|
|
|
|
|
|
|
internal Editing_File*
|
2019-06-01 23:58:28 +00:00
|
|
|
create_file(Models *models, String_Const_u8 file_name, Buffer_Create_Flag flags){
|
2019-04-05 23:30:24 +00:00
|
|
|
Editing_File *result = 0;
|
|
|
|
|
|
|
|
if (file_name.size > 0){
|
|
|
|
System_Functions *system = models->system;
|
|
|
|
Working_Set *working_set = &models->working_set;
|
|
|
|
Heap *heap = &models->mem.heap;
|
|
|
|
|
2019-06-01 23:58:28 +00:00
|
|
|
Arena *scratch = &models->mem.arena;
|
|
|
|
Temp_Memory temp = begin_temp(scratch);
|
2019-04-05 23:30:24 +00:00
|
|
|
|
|
|
|
Editing_File *file = 0;
|
|
|
|
b32 do_empty_buffer = false;
|
|
|
|
Editing_File_Name canon = {};
|
|
|
|
b32 has_canon_name = false;
|
|
|
|
b32 buffer_is_for_new_file = false;
|
|
|
|
|
|
|
|
// NOTE(allen): Try to get the file by canon name.
|
2019-06-01 23:58:28 +00:00
|
|
|
if (HasFlag(flags, BufferCreate_NeverAttachToFile) == 0){
|
2019-08-04 00:49:40 +00:00
|
|
|
if (get_canon_name(system, scratch, file_name, &canon)){
|
2019-04-05 23:30:24 +00:00
|
|
|
has_canon_name = true;
|
2019-06-01 23:58:28 +00:00
|
|
|
file = working_set_contains_canon(working_set, string_from_file_name(&canon));
|
2019-04-05 23:30:24 +00:00
|
|
|
}
|
|
|
|
else{
|
|
|
|
do_empty_buffer = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NOTE(allen): Try to get the file by buffer name.
|
|
|
|
if ((flags & BufferCreate_MustAttachToFile) == 0){
|
|
|
|
if (file == 0){
|
|
|
|
file = working_set_contains_name(working_set, file_name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NOTE(allen): If there is still no file, create a new buffer.
|
|
|
|
if (file == 0){
|
|
|
|
Plat_Handle handle = {};
|
|
|
|
|
|
|
|
// NOTE(allen): Figure out whether this is a new file, or an existing file.
|
|
|
|
if (!do_empty_buffer){
|
|
|
|
if ((flags & BufferCreate_AlwaysNew) != 0){
|
|
|
|
do_empty_buffer = true;
|
|
|
|
}
|
|
|
|
else{
|
2019-08-16 15:01:17 +00:00
|
|
|
if (!system->load_handle(scratch, (char*)canon.name_space, &handle)){
|
2019-04-05 23:30:24 +00:00
|
|
|
do_empty_buffer = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (do_empty_buffer){
|
|
|
|
if (has_canon_name){
|
|
|
|
buffer_is_for_new_file = true;
|
|
|
|
}
|
|
|
|
if (!HasFlag(flags, BufferCreate_NeverNew)){
|
2019-08-12 09:16:04 +00:00
|
|
|
file = working_set_allocate_file(working_set, &models->lifetime_allocator);
|
2019-04-05 23:30:24 +00:00
|
|
|
if (file != 0){
|
|
|
|
if (has_canon_name){
|
2019-06-01 23:58:28 +00:00
|
|
|
file_bind_file_name(system, heap, working_set, file, string_from_file_name(&canon));
|
2019-04-05 23:30:24 +00:00
|
|
|
}
|
2019-06-01 23:58:28 +00:00
|
|
|
String_Const_u8 front = string_front_of_path(file_name);
|
|
|
|
buffer_bind_name(models, heap, scratch, working_set, file, front);
|
2019-04-05 23:30:24 +00:00
|
|
|
File_Attributes attributes = {};
|
2019-06-01 23:58:28 +00:00
|
|
|
file_create_from_string(system, models, file, SCu8(""), attributes);
|
2019-04-05 23:30:24 +00:00
|
|
|
result = file;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else{
|
|
|
|
File_Attributes attributes = system->load_attributes(handle);
|
|
|
|
b32 in_heap_mem = false;
|
2019-06-01 23:58:28 +00:00
|
|
|
char *buffer = push_array(scratch, char, (i32)attributes.size);
|
2019-04-05 23:30:24 +00:00
|
|
|
|
|
|
|
if (buffer == 0){
|
|
|
|
buffer = heap_array(heap, char, (i32)attributes.size);
|
|
|
|
Assert(buffer != 0);
|
|
|
|
in_heap_mem = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (system->load_file(handle, buffer, (i32)attributes.size)){
|
|
|
|
system->load_close(handle);
|
2019-08-12 09:16:04 +00:00
|
|
|
file = working_set_allocate_file(working_set, &models->lifetime_allocator);
|
2019-04-05 23:30:24 +00:00
|
|
|
if (file != 0){
|
2019-06-01 23:58:28 +00:00
|
|
|
file_bind_file_name(system, heap, working_set, file, string_from_file_name(&canon));
|
|
|
|
String_Const_u8 front = string_front_of_path(file_name);
|
|
|
|
buffer_bind_name(models, heap, scratch, working_set, file, front);
|
|
|
|
file_create_from_string(system, models, file, SCu8(buffer, (i32)attributes.size), attributes);
|
2019-04-05 23:30:24 +00:00
|
|
|
result = file;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else{
|
|
|
|
system->load_close(handle);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (in_heap_mem){
|
|
|
|
heap_free(heap, buffer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else{
|
|
|
|
result = file;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (file != 0 && HasFlag(flags, BufferCreate_JustChangedFile)){
|
|
|
|
file->state.ignore_behind_os = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (file != 0 && HasFlag(flags, BufferCreate_AlwaysNew)){
|
|
|
|
i32 size = buffer_size(&file->state.buffer);
|
|
|
|
if (size > 0){
|
|
|
|
Edit_Behaviors behaviors = {};
|
2019-06-01 23:33:31 +00:00
|
|
|
edit_single(system, models, file, make_range(0, size), string_u8_litexpr(""), behaviors);
|
2019-04-05 23:30:24 +00:00
|
|
|
if (has_canon_name){
|
|
|
|
buffer_is_for_new_file = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-12 09:16:04 +00:00
|
|
|
if (file != 0 && buffer_is_for_new_file &&
|
|
|
|
!HasFlag(flags, BufferCreate_SuppressNewFileHook) &&
|
|
|
|
models->hook_new_file != 0){
|
|
|
|
models->hook_new_file(&models->app_links, file->id);
|
2019-04-05 23:30:24 +00:00
|
|
|
}
|
|
|
|
|
2019-06-01 23:58:28 +00:00
|
|
|
end_temp(temp);
|
2019-04-05 23:30:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return(result);
|
|
|
|
}
|
|
|
|
|
2018-03-26 05:19:08 +00:00
|
|
|
// BOTTOM
|
|
|
|
|