4coder/4ed_file.cpp

756 lines
24 KiB
C++

/*
* Mr. 4th Dimention - Allen Webster
*
* 03.01.2017
*
* File layer for 4coder
*
*/
// TOP
inline Buffer_Slot_ID
to_file_id(i32 id){
Buffer_Slot_ID result;
result.id = id;
return(result);
}
////////////////////////////////
internal void
init_file_markers_state(Editing_File_Markers *markers){
Marker_Array *sentinel = &markers->sentinel;
dll_init_sentinel(sentinel);
markers->array_count = 0;
markers->marker_count = 0;
}
internal void
clear_file_markers_state(Application_Links *app, General_Memory *general, Editing_File_Markers *markers){
Marker_Array *sentinel = &markers->sentinel;
for (Marker_Array *marker_array = sentinel->next;
marker_array != sentinel;
marker_array = sentinel->next){
if (marker_array->callback != 0){
marker_array->callback(app, marker_array, marker_array + 1, marker_array->user_data_size);
}
dll_remove(marker_array);
general_memory_free(general, marker_array);
}
Assert(sentinel->next == sentinel);
Assert(sentinel->prev == sentinel);
markers->array_count = 0;
markers->marker_count = 0;
}
internal void*
allocate_markers_state(General_Memory *general, Editing_File *file, u32 new_array_max){
u32 memory_size = sizeof(Marker_Array) + sizeof(Marker)*new_array_max;
Marker_Array *array = (Marker_Array*)general_memory_allocate(general, memory_size);
dll_insert_back(&file->markers.sentinel, array);
array->buffer_id = file->id;
array->count = 0;
array->sim_max = new_array_max;
array->max = new_array_max;
++file->markers.array_count;
return(array);
}
internal Buffer_ID
get_buffer_id_from_marker_handle(void *handle){
Marker_Array *markers = (Marker_Array*)handle;
Buffer_Slot_ID result = markers->buffer_id;
return(result.id);
}
internal Data
get_user_data_from_marker_handle(void *handle){
Marker_Array *markers = (Marker_Array*)handle;
Data data;
data.data = (u8*)markers + 1;
data.size = markers->user_data_size;
return(data);
}
internal b32
markers_set(Editing_File *file, void *handle, u32 first_index, u32 count, Marker *source){
Assert(file != 0);
if (handle == 0){
return(false);
}
Marker_Array *markers = (Marker_Array*)handle;
if (markers->buffer_id.id != file->id.id){
return(false);
}
if (first_index + count > markers->sim_max){
return(false);
}
u32 new_count = first_index + count;
if (new_count > markers->count){
file->markers.marker_count += new_count - markers->count;
markers->count = new_count;
}
Marker *dst = MarkerArrayBase(markers);
memcpy(dst + first_index, source, sizeof(Marker)*count);
return(true);
}
internal b32
markers_get(Editing_File *file, void *handle, u32 first_index, u32 count, Marker *output){
Assert(file != 0);
if (handle == 0){
return(false);
}
Marker_Array *markers = (Marker_Array*)handle;
if (markers->buffer_id.id != file->id.id){
return(false);
}
if (first_index + count > markers->count){
return(false);
}
Marker *src = MarkerArrayBase(markers);
memcpy(output, src + first_index, sizeof(Marker)*count);
return(true);
}
internal b32
markers_free(General_Memory *general, Editing_File *file, void *handle){
Assert(file != 0);
if (handle == 0){
return(false);
}
Marker_Array *markers = (Marker_Array*)handle;
if (markers->buffer_id.id != file->id.id){
return(false);
}
dll_remove(markers);
file->markers.marker_count -= markers->count;
--file->markers.array_count;
general_memory_free(general, markers);
return(true);
}
////////////////////////////////
internal void
edit_pos_set_cursor(File_Edit_Positions *edit_pos, Full_Cursor cursor, b32 set_preferred_x, b32 unwrapped_lines){
edit_pos->cursor = cursor;
if (set_preferred_x){
edit_pos->preferred_x = cursor.wrapped_x;
if (unwrapped_lines){
edit_pos->preferred_x = cursor.unwrapped_x;
}
}
edit_pos->last_set_type = EditPos_CursorSet;
}
internal void
edit_pos_set_scroll(File_Edit_Positions *edit_pos, GUI_Scroll_Vars scroll){
edit_pos->scroll = scroll;
edit_pos->last_set_type = EditPos_ScrollSet;
}
internal i32
edit_pos_get_index(Editing_File *file, File_Edit_Positions *edit_pos){
i32 edit_pos_index = -1;
i32 count = file->state.edit_poss_count;
File_Edit_Positions **edit_poss = file->state.edit_poss;
for (i32 i = 0; i < count; ++i){
if (edit_poss[i] == edit_pos){
edit_pos_index = i;
break;
}
}
return(edit_pos_index);
}
internal b32
edit_pos_move_to_front(Editing_File *file, File_Edit_Positions *edit_pos){
b32 result = false;
if (file && edit_pos){
i32 edit_pos_index = edit_pos_get_index(file, edit_pos);
Assert(edit_pos_index != -1);
File_Edit_Positions **edit_poss = file->state.edit_poss;
memmove(edit_poss + 1, edit_poss, edit_pos_index*sizeof(*edit_poss));
edit_poss[0] = edit_pos;
result = true;
}
return(result);
}
internal b32
edit_pos_unset(Editing_File *file, File_Edit_Positions *edit_pos){
b32 result = false;
if (file && edit_pos){
i32 edit_pos_index = edit_pos_get_index(file, edit_pos);
Assert(edit_pos_index != -1);
i32 count = file->state.edit_poss_count;
File_Edit_Positions **edit_poss = file->state.edit_poss;
memmove(edit_poss + edit_pos_index,
edit_poss + edit_pos_index + 1,
(count - edit_pos_index - 1)*sizeof(*edit_poss));
edit_pos->in_view = false;
if (file->state.edit_poss_count > 1){
file->state.edit_poss_count -= 1;
}
result = true;
}
return(result);
}
internal File_Edit_Positions*
edit_pos_get_new(Editing_File *file, i32 index){
File_Edit_Positions *result = 0;
if (file && 0 <= index && index < 16){
result = file->state.edit_pos_space + index;
i32 edit_pos_index = edit_pos_get_index(file, result);
if (edit_pos_index == -1){
File_Edit_Positions **edit_poss = file->state.edit_poss;
i32 count = file->state.edit_poss_count;
if (count > 0){
if (edit_poss[0]->in_view){
memcpy(result, edit_poss[0], sizeof(*result));
memmove(edit_poss+1, edit_poss, sizeof(*edit_poss)*count);
file->state.edit_poss_count = count + 1;
}
else{
Assert(count == 1);
memcpy(result, edit_poss[0], sizeof(*result));
}
}
else{
memset(result, 0, sizeof(*result));
file->state.edit_poss_count = 1;
}
edit_poss[0] = result;
}
result->in_view = true;
}
return(result);
}
////////////////////////////////
inline b32
buffer_needs_save(Editing_File *file){
b32 result = false;
if (file->state.dirty == DirtyState_UnsavedChanges){
result = true;
}
return(result);
}
inline b32
buffer_can_save(Editing_File *file){
b32 result = false;
if (file->state.dirty == DirtyState_UnsavedChanges ||
file->state.dirty == DirtyState_UnloadedChanges){
result = true;
}
return(result);
}
inline b32
file_is_ready(Editing_File *file){
b32 result = false;
if (file != 0 && file->is_loading == 0){
result = true;
}
return(result);
}
inline void
file_set_unimportant(Editing_File *file, b32 val){
if (val){
file->state.dirty = DirtyState_UpToDate;
}
file->settings.unimportant = (b8)(val != false);
}
inline void
file_set_to_loading(Editing_File *file){
memset(&file->state, 0, sizeof(file->state));
memset(&file->settings, 0, sizeof(file->settings));
file->is_loading = true;
}
inline void
file_set_dirty_flag(Editing_File *file, Dirty_State state){
if (!file->settings.unimportant){
file->state.dirty = state;
}
else{
file->state.dirty = DirtyState_UpToDate;
}
}
////////////////////////////////
internal b32
save_file_to_name(System_Functions *system, Models *models, Editing_File *file, char *filename){
b32 result = false;
b32 using_actual_filename = false;
if (filename == 0){
terminate_with_null(&file->canon.name);
filename = file->canon.name.str;
using_actual_filename = true;
}
if (filename != 0){
Mem_Options *mem = &models->mem;
if (models->hook_save_file){
models->hook_save_file(&models->app_links, file->id.id);
}
i32 max = 0, size = 0;
b32 dos_write_mode = file->settings.dos_write_mode;
char *data = 0;
Gap_Buffer *buffer = &file->state.buffer;
if (dos_write_mode){
max = buffer_size(buffer) + buffer->line_count + 1;
}
else{
max = buffer_size(buffer);
}
b32 used_general = 0;
Temp_Memory temp = begin_temp_memory(&mem->part);
char empty = 0;
if (max == 0){
data = &empty;
}
else{
data = (char*)push_array(&mem->part, char, max);
if (!data){
used_general = 1;
data = (char*)general_memory_allocate(&mem->general, max);
}
}
Assert(data != 0);
if (dos_write_mode){
size = buffer_convert_out(buffer, data, max);
}
else{
size = max;
buffer_stringify(buffer, 0, size, data);
}
if (!using_actual_filename && file->canon.name.str != 0){
char space[512];
u32 length = str_size(filename);
system->get_canonical(filename, length, space, sizeof(space));
char *source_path = file->canon.name.str;
if (match(space, source_path)){
using_actual_filename = true;
}
}
result = system->save_file(filename, data, size);
if (result && using_actual_filename){
file->state.ignore_behind_os = 1;
}
file_set_dirty_flag(file, DirtyState_UpToDate);
if (used_general){
general_memory_free(&mem->general, data);
}
end_temp_memory(temp);
}
return(result);
}
inline b32
save_file(System_Functions *system, Models *models, Editing_File *file){
b32 result = save_file_to_name(system, models, file, 0);
return(result);
}
////////////////////////////////
inline b32
file_compute_partial_cursor(Editing_File *file, Buffer_Seek seek, Partial_Cursor *cursor){
b32 result = true;
switch (seek.type){
case buffer_seek_pos:
{
*cursor = buffer_partial_from_pos(&file->state.buffer, seek.pos);
}break;
case buffer_seek_line_char:
{
*cursor = buffer_partial_from_line_character(&file->state.buffer, seek.line, seek.character);
}break;
default:
{
result = false;
}break;
}
return(result);
}
internal Full_Cursor
file_compute_cursor(System_Functions *system, Editing_File *file, Buffer_Seek seek, b32 return_hint){
Font_Pointers font = system->font.get_pointers_by_id(file->settings.font_id);
Assert(font.valid);
Full_Cursor result = {0};
Buffer_Cursor_Seek_Params params;
params.buffer = &file->state.buffer;
params.seek = seek;
params.system = system;
params.font = font;
params.wrap_line_index = file->state.wrap_line_index;
params.character_starts = file->state.character_starts;
params.virtual_white = file->settings.virtual_white;
params.return_hint = return_hint;
params.cursor_out = &result;
Buffer_Cursor_Seek_State state = {0};
Buffer_Layout_Stop stop = {0};
i32 size = buffer_size(params.buffer);
f32 line_shift = 0.f;
b32 do_wrap = 0;
i32 wrap_unit_end = 0;
b32 first_wrap_determination = 1;
i32 wrap_array_index = 0;
do{
stop = buffer_cursor_seek(&state, params, line_shift, do_wrap, wrap_unit_end);
switch (stop.status){
case BLStatus_NeedWrapDetermination:
{
if (stop.pos >= size){
do_wrap = 0;
wrap_unit_end = max_i32;
}
else{
if (first_wrap_determination){
wrap_array_index = binary_search(file->state.wrap_positions, stop.pos, 0, file->state.wrap_position_count);
++wrap_array_index;
if (file->state.wrap_positions[wrap_array_index] == stop.pos){
do_wrap = 1;
wrap_unit_end = file->state.wrap_positions[wrap_array_index];
}
else{
do_wrap = 0;
wrap_unit_end = file->state.wrap_positions[wrap_array_index];
}
first_wrap_determination = 0;
}
else{
Assert(stop.pos == wrap_unit_end);
do_wrap = 1;
++wrap_array_index;
wrap_unit_end = file->state.wrap_positions[wrap_array_index];
}
}
}break;
case BLStatus_NeedWrapLineShift:
case BLStatus_NeedLineShift:
{
line_shift = file->state.line_indents[stop.wrap_line_index];
}break;
}
}while(stop.status != BLStatus_Finished);
return(result);
}
////////////////////////////////
internal i32
file_grow_starts_as_needed(General_Memory *general, Gap_Buffer *buffer, i32 additional_lines){
b32 result = GROW_NOT_NEEDED;
i32 max = buffer->line_max;
i32 count = buffer->line_count;
i32 target_lines = count + additional_lines;
if (target_lines > max || max == 0){
max = l_round_up_i32(target_lines + max, KB(1));
i32 *new_lines = (i32*)general_memory_reallocate(general, buffer->line_starts, sizeof(i32)*count, sizeof(f32)*max);
if (new_lines){
result = GROW_SUCCESS;
buffer->line_max = max;
buffer->line_starts = new_lines;
}
else{
result = GROW_FAILED;
}
}
return(result);
}
internal void
file_measure_starts(General_Memory *general, Gap_Buffer *buffer){
if (buffer->line_starts == 0){
i32 max = buffer->line_max = KB(1);
buffer->line_starts = (i32*)general_memory_allocate(general, max*sizeof(i32));
Assert(buffer->line_starts != 0);
}
Buffer_Measure_Starts state = {0};
for (;buffer_measure_starts(&state, buffer);){
i32 count = state.count;
i32 max = ((buffer->line_max + 1) << 1);
i32 *new_lines = (i32*)general_memory_reallocate(general, buffer->line_starts, sizeof(i32)*count, sizeof(i32)*max);
Assert(new_lines);
buffer->line_starts = new_lines;
buffer->line_max = max;
}
}
internal void
file_allocate_metadata_as_needed(General_Memory *general, Gap_Buffer *buffer, void **mem, i32 *mem_max_count, i32 count, i32 item_size){
if (*mem == 0){
i32 max = l_round_up_i32(((count + 1)*2), KB(1));
*mem = general_memory_allocate(general, max*item_size);
*mem_max_count = max;
Assert(*mem != 0);
}
else if (*mem_max_count < count){
i32 old_max = *mem_max_count;
i32 max = l_round_up_i32(((count + 1)*2), KB(1));
void *new_mem = general_memory_reallocate(general, *mem, item_size*old_max, item_size*max);
*mem = new_mem;
*mem_max_count = max;
Assert(*mem != 0);
}
}
inline void
file_allocate_character_starts_as_needed(General_Memory *general, Editing_File *file){
file_allocate_metadata_as_needed(general,
&file->state.buffer, (void**)&file->state.character_starts,
&file->state.character_start_max, file->state.buffer.line_count + 1, sizeof(i32));
}
internal void
file_allocate_indents_as_needed(General_Memory *general, Editing_File *file, i32 min_last_index){
i32 min_amount = min_last_index + 1;
file_allocate_metadata_as_needed(general,
&file->state.buffer, (void**)&file->state.line_indents,
&file->state.line_indent_max, min_amount, sizeof(f32));
}
inline void
file_allocate_wraps_as_needed(General_Memory *general, Editing_File *file){
file_allocate_metadata_as_needed(general,
&file->state.buffer, (void**)&file->state.wrap_line_index,
&file->state.wrap_max, file->state.buffer.line_count + 1, sizeof(f32));
}
inline void
file_allocate_wrap_positions_as_needed(General_Memory *general, Editing_File *file, i32 min_last_index){
i32 min_amount = min_last_index + 1;
file_allocate_metadata_as_needed(general,
&file->state.buffer, (void**)&file->state.wrap_positions,
&file->state.wrap_position_max, min_amount, sizeof(f32));
}
////////////////////////////////
internal void
file_create_from_string(System_Functions *system, Models *models, Editing_File *file, String val, u32 flags){
General_Memory *general = &models->mem.general;
Partition *part = &models->mem.part;
Open_File_Hook_Function *hook_open_file = models->hook_open_file;
Application_Links *app_links = &models->app_links;
memset(&file->state, 0, sizeof(file->state));
Gap_Buffer_Init init = buffer_begin_init(&file->state.buffer, val.str, val.size);
for (; buffer_init_need_more(&init); ){
i32 page_size = buffer_init_page_size(&init);
page_size = l_round_up_i32(page_size, KB(4));
if (page_size < KB(4)){
page_size = KB(4);
}
void *data = general_memory_allocate(general, page_size);
buffer_init_provide_page(&init, data, page_size);
}
i32 scratch_size = partition_remaining(part);
Assert(scratch_size > 0);
b32 init_success = buffer_end_init(&init, part->base + part->pos, scratch_size);
AllowLocal(init_success); Assert(init_success);
if (buffer_size(&file->state.buffer) < val.size){
file->settings.dos_write_mode = 1;
}
file_set_dirty_flag(file, DirtyState_UpToDate);
Face_ID font_id = models->global_font_id;
file->settings.font_id = font_id;
Font_Pointers font = system->font.get_pointers_by_id(font_id);
Assert(font.valid);
{
file_measure_starts(general, &file->state.buffer);
file_allocate_character_starts_as_needed(general, file);
buffer_measure_character_starts(system, font, &file->state.buffer, file->state.character_starts, 0, file->settings.virtual_white);
file_measure_wraps(system, &models->mem, file, font);
//adjust_views_looking_at_files_to_new_cursor(system, models, file);
}
file->settings.read_only = ((flags & FileCreateFlag_ReadOnly) != 0);
if (!file->settings.read_only){
// TODO(allen): Redo undo system (if you don't mind the pun)
i32 request_size = KB(64);
file->state.undo.undo.max = request_size;
file->state.undo.undo.strings = (u8*)general_memory_allocate(general, request_size);
file->state.undo.undo.edit_max = request_size / sizeof(Edit_Step);
file->state.undo.undo.edits = (Edit_Step*)general_memory_allocate(general, request_size);
file->state.undo.redo.max = request_size;
file->state.undo.redo.strings = (u8*)general_memory_allocate(general, request_size);
file->state.undo.redo.edit_max = request_size / sizeof(Edit_Step);
file->state.undo.redo.edits = (Edit_Step*)general_memory_allocate(general, request_size);
file->state.undo.history.max = request_size;
file->state.undo.history.strings = (u8*)general_memory_allocate(general, request_size);
file->state.undo.history.edit_max = request_size / sizeof(Edit_Step);
file->state.undo.history.edits = (Edit_Step*)general_memory_allocate(general, request_size);
file->state.undo.children.max = request_size;
file->state.undo.children.strings = (u8*)general_memory_allocate(general, request_size);
file->state.undo.children.edit_max = request_size / sizeof(Buffer_Edit);
file->state.undo.children.edits = (Buffer_Edit*)general_memory_allocate(general, request_size);
file->state.undo.history_block_count = 1;
file->state.undo.history_head_block = 0;
file->state.undo.current_block_normal = 1;
}
if (hook_open_file != 0){
hook_open_file(app_links, file->id.id);
}
file->settings.is_initialized = true;
}
internal void
file_free(System_Functions *system, Application_Links *app, General_Memory *general, Editing_File *file){
if (file->state.still_lexing){
system->cancel_job(BACKGROUND_THREADS, file->state.lex_job);
if (file->state.swap_array.tokens){
general_memory_free(general, file->state.swap_array.tokens);
file->state.swap_array.tokens = 0;
}
}
if (file->state.token_array.tokens){
general_memory_free(general, file->state.token_array.tokens);
}
clear_file_markers_state(app, general, &file->markers);
Gap_Buffer *buffer = &file->state.buffer;
if (buffer->data){
general_memory_free(general, buffer->data);
general_memory_free(general, buffer->line_starts);
}
general_memory_free(general, file->state.wrap_line_index);
general_memory_free(general, file->state.character_starts);
general_memory_free(general, file->state.line_indents);
if (file->state.undo.undo.edits){
general_memory_free(general, file->state.undo.undo.strings);
general_memory_free(general, file->state.undo.undo.edits);
general_memory_free(general, file->state.undo.redo.strings);
general_memory_free(general, file->state.undo.redo.edits);
general_memory_free(general, file->state.undo.history.strings);
general_memory_free(general, file->state.undo.history.edits);
general_memory_free(general, file->state.undo.children.strings);
general_memory_free(general, file->state.undo.children.edits);
}
}
internal void
init_normal_file(System_Functions *system, Models *models,
char *buffer, i32 size,
Editing_File *file){
String val = make_string(buffer, size);
file_create_from_string(system, models, file, val, 0);
if (file->settings.tokens_exist && file->state.token_array.tokens == 0){
if (!file->settings.virtual_white){
file_first_lex_parallel(system, models, file);
}
else{
file_first_lex_serial(models, file);
}
}
}
internal void
init_read_only_file(System_Functions *system, Models *models, Editing_File *file){
String val = null_string;
file_create_from_string(system, models, file, val, FileCreateFlag_ReadOnly);
if (file->settings.tokens_exist && file->state.token_array.tokens == 0){
if (!file->settings.virtual_white){
file_first_lex_parallel(system, models, file);
}
else{
file_first_lex_serial(models, file);
}
}
}
// BOTTOM