/* * Mr. 4th Dimention - Allen Webster * * 03.01.2017 * * File layer for 4coder * */ // TOP internal String_Const_u8 string_from_file_name(Editing_File_Name *name){ return(SCu8(name->name_space, name->name_size)); } //////////////////////////////// internal void file_edit_positions_set_cursor(File_Edit_Positions *edit_pos, i32 pos){ edit_pos->cursor_pos = pos; edit_pos->last_set_type = EditPos_CursorSet; } internal void file_edit_positions_set_scroll(File_Edit_Positions *edit_pos, GUI_Scroll_Vars scroll, i32 max_y){ scroll.target_y = clamp(0, scroll.target_y, max_y); edit_pos->scroll = scroll; edit_pos->last_set_type = EditPos_ScrollSet; } internal void file_edit_positions_push(Editing_File *file, File_Edit_Positions edit_pos){ if (file->state.edit_pos_stack_top + 1 < ArrayCount(file->state.edit_pos_stack)){ file->state.edit_pos_stack_top += 1; file->state.edit_pos_stack[file->state.edit_pos_stack_top] = edit_pos; } } internal File_Edit_Positions file_edit_positions_pop(Editing_File *file){ File_Edit_Positions edit_pos = {}; if (file->state.edit_pos_stack_top >= 0){ edit_pos = file->state.edit_pos_stack[file->state.edit_pos_stack_top]; file->state.edit_pos_stack_top -= 1; } else{ edit_pos = file->state.edit_pos_most_recent; } return(edit_pos); } //////////////////////////////// internal u32 file_get_access_flags(Editing_File *file){ u32 flags = 0; if (file->settings.read_only){ flags |= AccessProtected; } return(flags); } internal b32 file_needs_save(Editing_File *file){ b32 result = false; if (HasFlag(file->state.dirty, DirtyState_UnsavedChanges)){ result = true; } return(result); } internal b32 file_can_save(Editing_File *file){ b32 result = false; if (HasFlag(file->state.dirty, DirtyState_UnsavedChanges) || HasFlag(file->state.dirty, DirtyState_UnloadedChanges)){ result = true; } return(result); } internal b32 file_is_ready(Editing_File *file){ b32 result = false; if (file != 0 && file->is_loading == 0){ result = true; } return(result); } internal void file_set_unimportant(Editing_File *file, b32 val){ if (val){ file->state.dirty = DirtyState_UpToDate; } file->settings.unimportant = (b8)(val); } internal void file_set_to_loading(Editing_File *file){ block_zero_struct(&file->state); block_zero_struct(&file->settings); file->is_loading = true; } internal void file_add_dirty_flag(Editing_File *file, Dirty_State state){ if (!file->settings.unimportant){ file->state.dirty |= state; } else{ file->state.dirty = DirtyState_UpToDate; } } internal void file_clear_dirty_flags(Editing_File *file){ file->state.dirty = DirtyState_UpToDate; } //////////////////////////////// internal void file_name_terminate(Editing_File_Name *name){ umem size = name->name_size; size = clamp_top(size, sizeof(name->name_space) - 1); name->name_space[size] = 0; name->name_size = size; } //////////////////////////////// // TODO(allen): file_name should be String_Const_u8 internal b32 save_file_to_name(System_Functions *system, Models *models, Editing_File *file, u8 *file_name){ b32 result = false; b32 using_actual_file_name = false; if (file_name == 0){ file_name_terminate(&file->canon); file_name = file->canon.name_space; using_actual_file_name = true; } if (file_name != 0){ Mem_Options *mem = &models->mem; if (models->hook_save_file != 0){ models->hook_save_file(&models->app_links, file->id); } Gap_Buffer *buffer = &file->state.buffer; b32 dos_write_mode = file->settings.dos_write_mode; i32 max = 0; if (dos_write_mode){ max = buffer_size(buffer) + buffer->line_count + 1; } else{ max = buffer_size(buffer); } Arena *scratch = &mem->arena; Temp_Memory temp = begin_temp(scratch); char empty = 0; char *data = 0; if (max == 0){ data = ∅ } else{ data = (char*)push_array(scratch, char, max); } Assert(data != 0); i32 size = 0; if (dos_write_mode){ size = buffer_convert_out(buffer, data, max); } else{ size = max; buffer_stringify(buffer, 0, size, data); } if (!using_actual_file_name){ String_Const_u8 s_file_name = SCu8(file_name); String_Const_u8 canonical_file_name = system->get_canonical(scratch, s_file_name); if (string_match(canonical_file_name, string_from_file_name(&file->canon))){ using_actual_file_name = true; } } File_Attributes new_attributes = system->save_file(scratch, (char*)file_name, data, size); if (new_attributes.last_write_time > 0){ if (using_actual_file_name){ file->state.ignore_behind_os = 1; } file->attributes = new_attributes; } LogEventF(log_string(M), scratch, file->id, 0, system->thread_get_id(), "save file [last_write_time=0x%llx]", new_attributes.last_write_time); file_clear_dirty_flags(file); end_temp(temp); } return(result); } internal b32 save_file(System_Functions *system, Models *models, Editing_File *file){ b32 result = save_file_to_name(system, models, file, 0); return(result); } //////////////////////////////// internal Partial_Cursor file_compute_partial_cursor(Editing_File *file, Buffer_Seek seek){ Partial_Cursor result = {}; switch (seek.type){ case buffer_seek_pos: { result = buffer_partial_from_pos(&file->state.buffer, (i32)seek.pos); }break; case buffer_seek_line_char: { result = buffer_partial_from_line_character(&file->state.buffer, (i32)seek.line, (i32)seek.character); }break; // TODO(allen): do(support buffer_seek_character_pos and character_pos coordiantes in partial cursor system) } return(result); } internal Full_Cursor file_compute_cursor__inner(Models *models, Editing_File *file, Buffer_Seek seek, b32 return_hint){ System_Functions *system = models->system; Face *face = font_set_face_from_id(&models->font_set, file->settings.font_id); Assert(face != 0); Full_Cursor result = {}; Buffer_Cursor_Seek_Params params; params.buffer = &file->state.buffer; params.seek = seek; params.system = system; params.face = face; 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 = {}; Buffer_Layout_Stop stop = {}; i32 size = buffer_size(params.buffer); f32 line_shift = 0.f; b32 do_wrap = false; i32 wrap_unit_end = 0; b32 first_wrap_determination = true; 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 = false; 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 = true; wrap_unit_end = file->state.wrap_positions[wrap_array_index]; } else{ do_wrap = false; wrap_unit_end = file->state.wrap_positions[wrap_array_index]; } first_wrap_determination = false; } else{ Assert(stop.pos == wrap_unit_end); do_wrap = true; ++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 Full_Cursor file_compute_cursor(Models *models, Editing_File *file, Buffer_Seek seek){ return(file_compute_cursor__inner(models, file, seek, false)); } internal Full_Cursor file_compute_cursor_hint(Models *models, Editing_File *file, Buffer_Seek seek){ return(file_compute_cursor__inner(models, file, seek, true)); } //////////////////////////////// internal i32 file_grow_starts_as_needed(Heap *heap, 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 = round_up_i32(target_lines + max, KB(1)); i32 *new_lines = heap_array(heap, i32, max); if (new_lines != 0){ result = GROW_SUCCESS; memcpy(new_lines, buffer->line_starts, sizeof(*new_lines)*count); heap_free(heap, buffer->line_starts); buffer->line_starts = new_lines; buffer->line_max = max; } else{ result = GROW_FAILED; } } return(result); } internal void file_measure_starts(Heap *heap, Gap_Buffer *buffer){ if (buffer->line_starts == 0){ i32 max = buffer->line_max = KB(1); buffer->line_starts = heap_array(heap, i32, max); Assert(buffer->line_starts != 0); } Buffer_Measure_Starts state = {}; for (;buffer_measure_starts(&state, buffer);){ i32 count = state.count; i32 max = ((buffer->line_max + 1) << 1); i32 *new_lines = heap_array(heap, i32, max); Assert(new_lines != 0); memcpy(new_lines, buffer->line_starts, sizeof(*new_lines)*count); heap_free(heap, buffer->line_starts); buffer->line_starts = new_lines; buffer->line_max = max; } } internal void file_allocate_metadata_as_needed(Heap *heap, Gap_Buffer *buffer, void **mem, i32 *mem_max_count, i32 count, i32 item_size){ if (*mem == 0){ i32 max = round_up_i32(((count + 1)*2), KB(1)); *mem = heap_allocate(heap, max*item_size); *mem_max_count = max; Assert(*mem != 0); } else if (*mem_max_count < count){ i32 old_max = *mem_max_count; i32 max = round_up_i32(((count + 1)*2), KB(1)); void *new_mem = heap_allocate(heap, item_size*max); memcpy(new_mem, *mem, item_size*old_max); heap_free(heap, *mem); *mem = new_mem; *mem_max_count = max; Assert(*mem != 0); } } internal void file_allocate_character_starts_as_needed(Heap *heap, Editing_File *file){ file_allocate_metadata_as_needed(heap, &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(Heap *heap, Editing_File *file, i32 min_last_index){ i32 min_amount = min_last_index + 1; file_allocate_metadata_as_needed(heap, &file->state.buffer, (void**)&file->state.line_indents, &file->state.line_indent_max, min_amount, sizeof(f32)); } internal void file_allocate_wraps_as_needed(Heap *heap, Editing_File *file){ file_allocate_metadata_as_needed(heap, &file->state.buffer, (void**)&file->state.wrap_line_index, &file->state.wrap_max, file->state.buffer.line_count + 1, sizeof(f32)); } internal void file_allocate_wrap_positions_as_needed(Heap *heap, Editing_File *file, i32 min_last_index){ i32 min_amount = min_last_index + 1; file_allocate_metadata_as_needed(heap, &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_Const_u8 val, File_Attributes attributes){ Heap *heap = &models->mem.heap; Arena *scratch = &models->mem.arena; Application_Links *app_links = &models->app_links; block_zero_struct(&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 = round_up_i32(page_size, KB(4)); if (page_size < KB(4)){ page_size = KB(4); } void *data = heap_allocate(heap, page_size); buffer_init_provide_page(&init, data, page_size); } b32 init_success = buffer_end_init(&init); Assert(init_success); if (buffer_size(&file->state.buffer) < val.size){ file->settings.dos_write_mode = true; } file_clear_dirty_flags(file); file->attributes = attributes; Face_ID font_id = models->global_font_id; file->settings.font_id = font_id; Face *face = font_set_face_from_id(&models->font_set, font_id); Assert(face != 0); file_measure_starts(heap, &file->state.buffer); file_allocate_character_starts_as_needed(heap, file); buffer_measure_character_starts(system, &file->state.buffer, file->state.character_starts, 0, file->settings.virtual_white); file_measure_wraps(system, &models->mem, file, face); //adjust_views_looking_at_files_to_new_cursor(system, models, file); file->lifetime_object = lifetime_alloc_object(heap, &models->lifetime_allocator, DynamicWorkspace_Buffer, file); history_init(&models->app_links, &file->state.history); // TODO(allen): do(cleanup the create and settings dance) // Right now we have this thing where we call hook_open_file, which may or may not // trigger a lex job or serial lex, or might just flag the buffer to say it wants // tokens. Then we check if we need to lex still and do it here too. Better would // be to make sure it always happens in one way. Open_File_Hook_Function *hook_open_file = models->hook_open_file; if (hook_open_file != 0){ hook_open_file(app_links, file->id); } if (file->settings.tokens_exist){ if (file->state.token_array.tokens == 0){ file_first_lex(system, models, file); } } else{ file_mark_edit_finished(&models->working_set, file); } file->settings.is_initialized = true; { Temp_Memory temp = begin_temp(scratch); String_Const_u8 name = SCu8(file->unique_name.name_space, file->unique_name.name_size); name = string_escape(scratch, name); LogEventF(log_string(M), scratch, file->id, 0, system->thread_get_id(), "init file [lwt=0x%llx] [name=\"%.*s\"]", attributes.last_write_time, string_expand(name)); end_temp(temp); } } internal void file_free(System_Functions *system, Heap *heap, Lifetime_Allocator *lifetime_allocator, Working_Set *working_set, Editing_File *file){ if (file->state.token_array.tokens){ heap_free(heap, file->state.token_array.tokens); } lifetime_free_object(heap, lifetime_allocator, file->lifetime_object); Gap_Buffer *buffer = &file->state.buffer; if (buffer->data){ heap_free(heap, buffer->data); heap_free(heap, buffer->line_starts); } heap_free(heap, file->state.wrap_line_index); heap_free(heap, file->state.character_starts); heap_free(heap, file->state.line_indents); history_free(heap, &file->state.history); file_unmark_edit_finished(working_set, file); } //////////////////////////////// internal i32 file_get_current_record_index(Editing_File *file){ return(file->state.current_record_index); } internal b32 file_tokens_are_ready(Editing_File *file){ return(file->state.token_array.tokens != 0); } internal Managed_Scope file_get_managed_scope(Editing_File *file){ Managed_Scope scope = 0; if (file != 0){ Assert(file->lifetime_object != 0); scope = (Managed_Scope)file->lifetime_object->workspace.scope_id; } return(scope); } // BOTTOM