Add 'code/' from commit '1459ef7cbce753b056ea148b29c9dc3f8a721261'

git-subtree-dir: code
git-subtree-mainline: cf6a50363e
git-subtree-split: 1459ef7cbc
This commit is contained in:
Jack Punter 2022-11-18 13:37:07 +00:00
commit 0e47ccd2ce
290 changed files with 95773 additions and 0 deletions

4
code/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
vc120.pdb
4ed_data.ctm
.DS_Store
*.dSYM

881
code/4ed.cpp Normal file
View File

@ -0,0 +1,881 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 12.12.2014
*
* Application layer for project codename "4ed"
*
*/
// TOP
internal void
init_command_line_settings(App_Settings *settings, Plat_Settings *plat_settings, i32 argc, char **argv){
char *arg = 0;
Command_Line_Mode mode = CLMode_App;
Command_Line_Action action = CLAct_Nothing;
b32 strict = false;
settings->init_files_max = ArrayCount(settings->init_files);
for (i32 i = 1; i <= argc; ++i){
if (i == argc){
arg = "";
}
else{
arg = argv[i];
}
if (arg[0] == '-' && arg[1] == '-'){
char *long_arg_name = arg+2;
if (string_match(SCu8(long_arg_name), string_u8_litexpr("custom"))){
mode = CLMode_Custom;
continue;
}
}
switch (mode){
case CLMode_App:
{
switch (action){
case CLAct_Nothing:
{
if (arg[0] == '-'){
action = CLAct_Ignore;
switch (arg[1]){
case 'd': action = CLAct_CustomDLL; strict = false; break;
case 'D': action = CLAct_CustomDLL; strict = true; break;
case 'w': action = CLAct_WindowSize; break;
case 'W': action = CLAct_WindowMaximize; break;
case 'p': action = CLAct_WindowPosition; break;
case 'F': action = CLAct_WindowFullscreen; break;
case 'f': action = CLAct_FontSize; break;
case 'h': action = CLAct_FontUseHinting; --i; break;
case 'U': action = CLAct_UserDirectory; break;
case 'L': action = CLAct_Nothing; break;
//case 'L': enables log, parsed before this is called (because I'm a dumbass)
}
}
else if (arg[0] != 0){
if (settings->init_files_count < settings->init_files_max){
i32 index = settings->init_files_count++;
settings->init_files[index] = arg;
}
}
}break;
case CLAct_CustomDLL:
{
plat_settings->custom_dll_is_strict = (b8)strict;
if (i < argc){
plat_settings->custom_dll = argv[i];
}
action = CLAct_Nothing;
}break;
case CLAct_WindowSize:
{
if (i + 1 < argc){
plat_settings->set_window_size = true;
i32 w = (i32)string_to_integer(SCu8(argv[i]), 10);
i32 h = (i32)string_to_integer(SCu8(argv[i + 1]), 10);
if (w > 0){
plat_settings->window_w = w;
}
if (h > 0){
plat_settings->window_h = h;
}
++i;
}
action = CLAct_Nothing;
}break;
case CLAct_WindowMaximize:
{
--i;
plat_settings->maximize_window = true;
action = CLAct_Nothing;
}break;
case CLAct_WindowPosition:
{
if (i + 1 < argc){
plat_settings->set_window_pos = true;
i32 x = (i32)string_to_integer(SCu8(argv[i]), 10);
i32 y = (i32)string_to_integer(SCu8(argv[i + 1]), 10);
if (x > 0){
plat_settings->window_x = x;
}
if (y > 0){
plat_settings->window_y = y;
}
++i;
}
action = CLAct_Nothing;
}break;
case CLAct_WindowFullscreen:
{
--i;
plat_settings->fullscreen_window = true;
action = CLAct_Nothing;
}break;
case CLAct_FontSize:
{
if (i < argc){
settings->font_size = (i32)string_to_integer(SCu8(argv[i]), 10);
}
action = CLAct_Nothing;
}break;
case CLAct_FontUseHinting:
{
plat_settings->use_hinting = true;
settings->use_hinting = plat_settings->use_hinting;
action = CLAct_Nothing;
}break;
case CLAct_UserDirectory:
{
if (i < argc){
plat_settings->user_directory = argv[i];
}
action = CLAct_Nothing;
}break;
}
}break;
case CLMode_Custom:
{
settings->custom_flags = argv + i;
settings->custom_flags_count = argc - i;
i = argc;
mode = CLMode_App;
}break;
}
}
}
////////////////////////////////
internal Models*
models_init(void){
Arena arena = make_arena_system();
Models *models = push_array_zero(&arena, Models, 1);
models->arena_ = arena;
models->arena = &models->arena_;
heap_init(&models->heap, get_base_allocator_system());
return(models);
}
internal void
app_load_vtables(API_VTable_system *vtable_system, API_VTable_font *vtable_font, API_VTable_graphics *vtable_graphics){
system_api_read_vtable(vtable_system);
font_api_read_vtable(vtable_font);
graphics_api_read_vtable(vtable_graphics);
}
internal Log_Function*
app_get_logger(void){
log_init();
return(log_string);
}
App_Read_Command_Line_Sig(app_read_command_line){
Models *models = models_init();
App_Settings *settings = &models->settings;
block_zero_struct(settings);
if (argc > 1){
init_command_line_settings(&models->settings, plat_settings, argc, argv);
}
*files = models->settings.init_files;
*file_count = &models->settings.init_files_count;
return(models);
}
App_Init_Sig(app_init){
Models *models = (Models*)base_ptr;
models->keep_playing = true;
models->hard_exit = false;
models->config_api = api;
models->virtual_event_arena = make_arena_system();
profile_init(&models->profile_list);
managed_ids_init(tctx->allocator, &models->managed_id_set);
API_VTable_custom custom_vtable = {};
custom_api_fill_vtable(&custom_vtable);
API_VTable_system system_vtable = {};
system_api_fill_vtable(&system_vtable);
Custom_Layer_Init_Type *custom_init = api.init_apis(&custom_vtable, &system_vtable);
Assert(custom_init != 0);
// NOTE(allen): coroutines
coroutine_system_init(&models->coroutines);
// NOTE(allen): font set
font_set_init(&models->font_set);
// NOTE(allen): live set
Arena *arena = models->arena;
{
models->view_set.count = 0;
models->view_set.max = MAX_VIEWS;
models->view_set.views = push_array(arena, View, models->view_set.max);
//dll_init_sentinel
models->view_set.free_sentinel.next = &models->view_set.free_sentinel;
models->view_set.free_sentinel.prev = &models->view_set.free_sentinel;
i32 max = models->view_set.max;
View *view = models->view_set.views;
for (i32 i = 0; i < max; ++i, ++view){
//dll_insert(&models->view_set.free_sentinel, view);
view->next = models->view_set.free_sentinel.next;
view->prev = &models->view_set.free_sentinel;
models->view_set.free_sentinel.next = view;
view->next->prev = view;
}
}
lifetime_allocator_init(tctx->allocator, &models->lifetime_allocator);
dynamic_workspace_init(&models->lifetime_allocator, DynamicWorkspace_Global, 0, &models->dynamic_workspace);
// NOTE(allen): file setup
working_set_init(models, &models->working_set);
Mutex_Lock file_order_lock(models->working_set.mutex);
// NOTE(allen):
global_history_init(&models->global_history);
text_layout_init(tctx, &models->text_layouts);
// NOTE(allen): style setup
{
Scratch_Block scratch(tctx, arena);
String8 binary_path = system_get_path(scratch, SystemPath_Binary);
String8 full_path = push_u8_stringf(arena, "%.*sfonts/liberation-mono.ttf", string_expand(binary_path));
Face_Description description = {};
description.font.file_name = full_path;
description.parameters.pt_size = 12;
Face *new_face = font_set_new_face(&models->font_set, &description);
if (new_face == 0){
system_error_box("Could not load the required fallback font");
}
models->global_face_id = new_face->id;
}
// NOTE(allen): title space
models->has_new_title = true;
models->title_capacity = KB(4);
models->title_space = push_array(arena, char, models->title_capacity);
block_copy(models->title_space, WINDOW_NAME, sizeof(WINDOW_NAME));
// NOTE(allen): miscellaneous init
hot_directory_init(arena, &models->hot_directory, current_directory);
child_process_container_init(tctx->allocator, &models->child_processes);
models->period_wakeup_timer = system_wake_up_timer_create();
// NOTE(allen): custom layer init
Application_Links app = {};
app.tctx = tctx;
app.cmd_context = models;
custom_init(&app);
// NOTE(allen): init baked in buffers
File_Init init_files[] = {
{ str8_lit("*messages*"), &models->message_buffer , true , },
{ str8_lit("*scratch*") , &models->scratch_buffer , false, },
{ str8_lit("*log*") , &models->log_buffer , true , },
{ str8_lit("*keyboard*"), &models->keyboard_buffer, true , },
};
Buffer_Hook_Function *begin_buffer_func = models->begin_buffer;
models->begin_buffer = 0;
Heap *heap = &models->heap;
for (i32 i = 0; i < ArrayCount(init_files); ++i){
Editing_File *file = working_set_allocate_file(&models->working_set, &models->lifetime_allocator);
buffer_bind_name(tctx, models, arena, &models->working_set, file, init_files[i].name);
if (init_files[i].ptr != 0){
*init_files[i].ptr = file;
}
File_Attributes attributes = {};
file_create_from_string(tctx, models, file, SCu8(), attributes);
if (init_files[i].read_only){
file->settings.read_only = true;
history_free(tctx, &file->state.history);
}
file->settings.never_kill = true;
file_set_unimportant(file, true);
}
models->begin_buffer = begin_buffer_func;
// NOTE(allen): setup first panel
{
Panel *panel = layout_initialize(arena, &models->layout);
View *new_view = live_set_alloc_view(&models->lifetime_allocator, &models->view_set, panel);
view_init(tctx, models, new_view, models->scratch_buffer, models->view_event_handler);
}
}
App_Step_Sig(app_step){
Models *models = (Models*)base_ptr;
Mutex_Lock file_order_lock(models->working_set.mutex);
Scratch_Block scratch(tctx);
models->next_animate_delay = max_u32;
models->animate_next_frame = false;
// NOTE(allen): per-frame update of models state
begin_frame(target, &models->font_set);
models->target = target;
models->input = input;
// NOTE(allen): OS clipboard event handling
if (input->clipboard.str != 0){
co_send_core_event(tctx, models, CoreCode_NewClipboardContents, input->clipboard);
}
// NOTE(allen): reorganizing panels on screen
Vec2_i32 prev_dim = layout_get_root_size(&models->layout);
Vec2_i32 current_dim = V2i32(target->width, target->height);
layout_set_root_size(&models->layout, current_dim);
// NOTE(allen): update child processes
f32 dt = input->dt;
if (dt > 0){
Temp_Memory_Block temp(scratch);
Child_Process_Container *child_processes = &models->child_processes;
Child_Process **processes_to_free = push_array(scratch, Child_Process*, child_processes->active_child_process_count);
i32 processes_to_free_count = 0;
u32 max = KB(128);
char *dest = push_array(scratch, char, max);
for (Node *node = child_processes->child_process_active_list.next;
node != &child_processes->child_process_active_list;
node = node->next){
Child_Process *child_process = CastFromMember(Child_Process, node, node);
Editing_File *file = child_process->out_file;
CLI_Handles *cli = &child_process->cli;
// TODO(allen): do(call a 'child process updated hook' let that hook populate the buffer if it so chooses)
b32 edited_file = false;
u32 amount = 0;
system_cli_begin_update(cli);
if (system_cli_update_step(cli, dest, max, &amount)){
if (file != 0 && amount > 0){
output_file_append(tctx, models, file, SCu8(dest, amount));
edited_file = true;
}
}
if (system_cli_end_update(cli)){
if (file != 0){
String_Const_u8 str = push_u8_stringf(scratch, "exited with code %d", cli->exit);
output_file_append(tctx, models, file, str);
edited_file = true;
}
processes_to_free[processes_to_free_count++] = child_process;
child_process_set_return_code(models, child_processes, child_process->id, cli->exit);
}
if (child_process->cursor_at_end && file != 0){
file_cursor_to_end(tctx, models, file);
}
}
for (i32 i = 0; i < processes_to_free_count; ++i){
child_process_free(child_processes, processes_to_free[i]->id);
}
}
// NOTE(allen): simulated events
Input_List input_list = input->events;
Input_Modifier_Set modifiers = system_get_keyboard_modifiers(scratch);
if (input->mouse.press_l){
Input_Event event = {};
event.kind = InputEventKind_MouseButton;
event.mouse.code = MouseCode_Left;
event.mouse.p = input->mouse.p;
event.mouse.modifiers = copy_modifier_set(scratch, &modifiers);
push_input_event(scratch, &input_list, &event);
}
else if (input->mouse.release_l){
Input_Event event = {};
event.kind = InputEventKind_MouseButtonRelease;
event.mouse.code = MouseCode_Left;
event.mouse.p = input->mouse.p;
event.mouse.modifiers = copy_modifier_set(scratch, &modifiers);
push_input_event(scratch, &input_list, &event);
}
if (input->mouse.press_r){
Input_Event event = {};
event.kind = InputEventKind_MouseButton;
event.mouse.code = MouseCode_Right;
event.mouse.p = input->mouse.p;
event.mouse.modifiers = copy_modifier_set(scratch, &modifiers);
push_input_event(scratch, &input_list, &event);
}
else if (input->mouse.release_r){
Input_Event event = {};
event.kind = InputEventKind_MouseButtonRelease;
event.mouse.code = MouseCode_Right;
event.mouse.p = input->mouse.p;
event.mouse.modifiers = copy_modifier_set(scratch, &modifiers);
push_input_event(scratch, &input_list, &event);
}
if (input->mouse.wheel != 0){
Input_Event event = {};
event.kind = InputEventKind_MouseWheel;
event.mouse_wheel.value = (f32)(input->mouse.wheel);
event.mouse_wheel.p = input->mouse.p;
event.mouse_wheel.modifiers = copy_modifier_set(scratch, &modifiers);
push_input_event(scratch, &input_list, &event);
}
if (input->mouse.p != models->prev_p){
b32 was_in_window = rect_contains_point(Ri32(0, 0, prev_dim.x, prev_dim.y), models->prev_p);
b32 is_in_window = rect_contains_point(Ri32(0, 0, current_dim.x, current_dim.y), input->mouse.p);
if (is_in_window || was_in_window){
Input_Event event = {};
event.kind = InputEventKind_MouseMove;
event.mouse_move.p = input->mouse.p;
event.mouse_move.modifiers = copy_modifier_set(scratch, &modifiers);
push_input_event(scratch, &input_list, &event);
}
}
if (models->animated_last_frame){
Input_Event event = {};
event.kind = InputEventKind_Core;
event.core.code = CoreCode_Animate;
push_input_event(scratch, &input_list, &event);
}
// NOTE(allen): expose layout
Layout *layout = &models->layout;
// NOTE(allen): mouse hover status
Panel *mouse_panel = 0;
Panel *divider_panel = 0;
b32 mouse_in_margin = false;
Vec2_i32 mouse = input->mouse.p;
{
for (Panel *panel = layout_get_first_open_panel(layout);
panel != 0;
panel = layout_get_next_open_panel(layout, panel)){
if (rect_contains_point(panel->rect_full, mouse)){
mouse_panel = panel;
if (!rect_contains_point(panel->rect_inner, mouse)){
mouse_in_margin = true;
for (divider_panel = mouse_panel->parent;
divider_panel != 0;
divider_panel = divider_panel->parent){
if (rect_contains_point(divider_panel->rect_inner, mouse)){
break;
}
}
}
}
if (mouse_panel != 0){
break;
}
}
}
// NOTE(allen): First frame initialization
if (input->first_step){
Temp_Memory_Block temp(scratch);
String_Const_u8_Array file_names = {};
file_names.count = models->settings.init_files_count;
file_names.vals = push_array(scratch, String_Const_u8, file_names.count);
for (i32 i = 0; i < file_names.count; i += 1){
file_names.vals[i] = SCu8(models->settings.init_files[i]);
}
String_Const_u8_Array flags = {};
flags.count = models->settings.custom_flags_count;
flags.vals = push_array(scratch, String_Const_u8, flags.count);
for (i32 i = 0; i < flags.count; i += 1){
flags.vals[i] = SCu8(models->settings.custom_flags[i]);
}
Input_Event event = {};
event.kind = InputEventKind_Core;
event.core.code = CoreCode_Startup;
event.core.flag_strings = flags;
event.core.file_names = file_names;
co_send_event(tctx, models, &event);
// NOTE(allen): Actually do the buffer settings for the built ins now.
Buffer_Hook_Function *begin_buffer_func = models->begin_buffer;
if (begin_buffer_func != 0){
Application_Links app = {};
app.tctx = tctx;
app.cmd_context = models;
begin_buffer_func(&app, models->message_buffer->id);
begin_buffer_func(&app, models->scratch_buffer->id);
begin_buffer_func(&app, models->log_buffer->id);
begin_buffer_func(&app, models->keyboard_buffer->id);
}
}
// NOTE(allen): consume event stream
Input_Event_Node *input_node = input_list.first;
Input_Event_Node *input_node_next = 0;
for (;; input_node = input_node_next){
// NOTE(allen): first handle any events coming from the view command
// function queue
Model_View_Command_Function cmd_func = models_pop_view_command_function(models);
if (cmd_func.custom_func != 0){
View *view = imp_get_view(models, cmd_func.view_id);
if (view != 0){
input_node_next = input_node;
Input_Event cmd_func_event = {};
cmd_func_event.kind = InputEventKind_CustomFunction;
cmd_func_event.custom_func = cmd_func.custom_func;
co_send_event(tctx, models, view, &cmd_func_event);
continue;
}
}
Temp_Memory_Block temp(scratch);
Input_Event *simulated_input = 0;
Input_Event virtual_event = models_pop_virtual_event(scratch, models);
if (virtual_event.kind != InputEventKind_None){
virtual_event.virtual_event = true;
simulated_input = &virtual_event;
}
else{
if (input_node == 0){
break;
}
input_node_next = input_node->next;
simulated_input = &input_node->event;
if (simulated_input->kind == InputEventKind_TextInsert && simulated_input->text.blocked){
continue;
}
// NOTE(allen): record to keyboard history
if (simulated_input->kind == InputEventKind_KeyStroke ||
simulated_input->kind == InputEventKind_KeyRelease ||
simulated_input->kind == InputEventKind_TextInsert){
Temp_Memory_Block temp_key_line(scratch);
String_Const_u8 key_line = stringize_keyboard_event(scratch, simulated_input);
output_file_append(tctx, models, models->keyboard_buffer, key_line);
}
}
b32 event_was_handled = false;
Input_Event *event = simulated_input;
Panel *active_panel = layout_get_active_panel(layout);
View *view = active_panel->view;
Assert(view != 0);
switch (models->state){
case APP_STATE_EDIT:
{
typedef i32 Event_Consume_Rule;
enum{
EventConsume_None,
EventConsume_BeginResize,
EventConsume_ClickChangeView,
EventConsume_CustomCommand,
};
Event_Consume_Rule consume_rule = EventConsume_CustomCommand;
if (match_mouse_code(event, MouseCode_Left) && (divider_panel != 0)){
consume_rule = EventConsume_BeginResize;
}
else if (match_mouse_code(event, MouseCode_Left) &&
mouse_panel != 0 && mouse_panel != active_panel){
consume_rule = EventConsume_ClickChangeView;
}
switch (consume_rule){
case EventConsume_BeginResize:
{
models->state = APP_STATE_RESIZING;
models->resizing_intermediate_panel = divider_panel;
event_was_handled = true;
}break;
case EventConsume_ClickChangeView:
{
// NOTE(allen): run deactivate command
co_send_core_event(tctx, models, view, CoreCode_ClickDeactivateView);
layout->active_panel = mouse_panel;
models->animate_next_frame = true;
active_panel = mouse_panel;
view = active_panel->view;
// NOTE(allen): run activate command
co_send_core_event(tctx, models, view, CoreCode_ClickActivateView);
event_was_handled = true;
}break;
case EventConsume_CustomCommand:
{
event_was_handled = co_send_event(tctx, models, view, event);
}break;
}
}break;
case APP_STATE_RESIZING:
{
Event_Property event_flags = get_event_properties(event);
if (HasFlag(event_flags, EventProperty_AnyKey) ||
match_mouse_code_release(event, MouseCode_Left)){
models->state = APP_STATE_EDIT;
}
else if (event->kind == InputEventKind_MouseMove){
if (input->mouse.l){
Panel *split = models->resizing_intermediate_panel;
Range_i32 limits = layout_get_limiting_range_on_split(layout, split);
i32 mouse_position = (split->vertical_split)?(mouse.x):(mouse.y);
mouse_position = clamp(limits.min, mouse_position, limits.max);
layout_set_split_absolute_position(layout, split, mouse_position);
}
else{
models->state = APP_STATE_EDIT;
}
}
}break;
}
if (event_was_handled && event->kind == InputEventKind_KeyStroke){
for (Input_Event *dependent_text = event->key.first_dependent_text;
dependent_text != 0;
dependent_text = dependent_text->text.next_text){
Assert(dependent_text->kind == InputEventKind_TextInsert);
dependent_text->text.blocked = true;
}
}
}
linalloc_clear(&models->virtual_event_arena);
models->free_virtual_event = 0;
models->first_virtual_event = 0;
models->last_virtual_event = 0;
// NOTE(allen): send panel size update
if (models->layout.panel_state_dirty){
models->layout.panel_state_dirty = false;
if (models->buffer_viewer_update != 0){
Application_Links app = {};
app.tctx = tctx;
app.cmd_context = models;
models->buffer_viewer_update(&app);
}
}
// NOTE(allen): dt
f32 literal_dt = 0.f;
u64 now_usecond_stamp = system_now_time();
if (!input->first_step){
u64 elapsed_useconds = now_usecond_stamp - models->last_render_usecond_stamp;
literal_dt = (f32)((f64)(elapsed_useconds)/1000000.f);
}
models->last_render_usecond_stamp = now_usecond_stamp;
f32 animation_dt = 0.f;
if (models->animated_last_frame){
animation_dt = literal_dt;
}
// NOTE(allen): on the first frame there should be no scrolling
if (input->first_step){
for (Panel *panel = layout_get_first_open_panel(layout);
panel != 0;
panel = layout_get_next_open_panel(layout, panel)){
View *view = panel->view;
File_Edit_Positions edit_pos = view_get_edit_pos(view);
edit_pos.scroll.position = view_normalize_buffer_point(tctx, models, view, edit_pos.scroll.target);
block_zero_struct(&edit_pos.scroll.target);
view_set_edit_pos(view, edit_pos);
}
}
// NOTE(allen): hook for files reloaded
{
Working_Set *working_set = &models->working_set;
Assert(working_set->has_external_mod_sentinel.next != 0);
if (working_set->has_external_mod_sentinel.next != &working_set->has_external_mod_sentinel){
for (Node *node = working_set->has_external_mod_sentinel.next, *next = 0;
node != &working_set->has_external_mod_sentinel;
node = next){
next = node->next;
Editing_File *file = CastFromMember(Editing_File, external_mod_node, node);
dll_remove(node);
block_zero_struct(node);
co_send_core_event(tctx, models, CoreCode_FileExternallyModified, file->id);
}
}
}
// NOTE(allen): if the exit signal has been sent, run the exit hook.
if (!models->keep_playing || input->trying_to_kill){
co_send_core_event(tctx, models, CoreCode_TryExit);
models->keep_playing = true;
}
// NOTE(allen): rendering
{
Frame_Info frame = {};
frame.index = models->frame_counter;
frame.literal_dt = literal_dt;
frame.animation_dt = animation_dt;
Application_Links app = {};
app.tctx = tctx;
app.cmd_context = models;
if (models->tick != 0){
models->tick(&app, frame);
}
begin_render_section(target, models->frame_counter, literal_dt, animation_dt);
models->in_render_mode = true;
Live_Views *live_views = &models->view_set;
for (Node *node = layout->open_panels.next;
node != &layout->open_panels;
node = node->next){
Panel *panel = CastFromMember(Panel, node, node);
View *view = panel->view;
View_Context_Node *ctx = view->ctx;
if (ctx != 0){
Render_Caller_Function *render_caller = ctx->ctx.render_caller;
if (render_caller != 0){
render_caller(&app, frame, view_get_id(live_views, view));
}
}
}
if (models->whole_screen_render_caller != 0){
models->whole_screen_render_caller(&app, frame);
}
models->in_render_mode = false;
end_render_section(target);
}
// TODO(allen): This is dumb. Let's rethink view cleanup strategy.
// NOTE(allen): wind down coroutines
for (;;){
Model_Wind_Down_Co *node = models->wind_down_stack;
if (node == 0){
break;
}
sll_stack_pop(models->wind_down_stack);
Coroutine *co = node->co;
for (i32 j = 0; co != 0; j += 1){
Co_In in = {};
in.user_input.abort = true;
Co_Out ignore = {};
co = co_run(tctx, models, co, &in, &ignore);
if (j == 100){
Application_Links app = {};
app.tctx = tctx;
app.cmd_context = models;
#define M "SERIOUS ERROR: coroutine wind down did not complete\n"
print_message(&app, string_u8_litexpr(M));
#undef M
break;
}
}
sll_stack_push(models->free_wind_downs, node);
}
// NOTE(allen): flush the log
log_flush(tctx, models);
// NOTE(allen): set the app_result
Application_Step_Result app_result = {};
app_result.mouse_cursor_type = APP_MOUSE_CURSOR_DEFAULT;
app_result.lctrl_lalt_is_altgr = models->settings.lctrl_lalt_is_altgr;
// NOTE(allen): get new window title
if (models->has_new_title){
models->has_new_title = false;
app_result.has_new_title = true;
app_result.title_string = models->title_space;
}
// NOTE(allen): get cursor type
if (mouse_panel != 0 && !mouse_in_margin){
app_result.mouse_cursor_type = APP_MOUSE_CURSOR_ARROW;
}
else if (divider_panel != 0){
if (divider_panel->vertical_split){
app_result.mouse_cursor_type = APP_MOUSE_CURSOR_LEFTRIGHT;
}
else{
app_result.mouse_cursor_type = APP_MOUSE_CURSOR_UPDOWN;
}
}
else{
app_result.mouse_cursor_type = APP_MOUSE_CURSOR_ARROW;
}
models->prev_mouse_panel = mouse_panel;
app_result.lctrl_lalt_is_altgr = models->settings.lctrl_lalt_is_altgr;
app_result.perform_kill = models->hard_exit;
app_result.animating = models->animate_next_frame;
if (models->animate_next_frame){
// NOTE(allen): Silence the timer, because we're going to do another frame right away anyways.
system_wake_up_timer_set(models->period_wakeup_timer, max_u32);
}
else{
// NOTE(allen): Set the timer's wakeup period, possibly to max_u32 thus effectively silencing it.
system_wake_up_timer_set(models->period_wakeup_timer, models->next_animate_delay);
}
// NOTE(allen): Update Frame to Frame States
models->prev_p = input->mouse.p;
models->animated_last_frame = app_result.animating;
models->frame_counter += 1;
// end-of-app_step
return(app_result);
}
extern "C" App_Get_Functions_Sig(app_get_functions){
App_Functions result = {};
result.load_vtables = app_load_vtables;
result.get_logger = app_get_logger;
result.read_command_line = app_read_command_line;
result.init = app_init;
result.step = app_step;
return(result);
}
// BOTTOM

108
code/4ed.h Normal file
View File

@ -0,0 +1,108 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 12.12.2014
*
* Application Layer for 4coder
*
*/
// TOP
#if !defined(FRED_H)
#define FRED_H
#define MAX_VIEWS 16
struct Plat_Settings{
char *custom_dll;
b8 custom_dll_is_strict;
b8 fullscreen_window;
i32 window_w;
i32 window_h;
i32 window_x;
i32 window_y;
b8 set_window_pos;
b8 set_window_size;
b8 maximize_window;
b8 use_hinting;
char *user_directory;
};
#define App_Read_Command_Line_Sig(name) \
void *name(Thread_Context *tctx,\
String_Const_u8 current_directory,\
Plat_Settings *plat_settings,\
char ***files, \
i32 **file_count,\
i32 argc, \
char **argv)
typedef App_Read_Command_Line_Sig(App_Read_Command_Line);
struct Custom_API{
_Get_Version_Type *get_version;
_Init_APIs_Type *init_apis;
};
#define App_Init_Sig(name) \
void name(Thread_Context *tctx, \
Render_Target *target, \
void *base_ptr, \
String_Const_u8 current_directory,\
Custom_API api)
typedef App_Init_Sig(App_Init);
#include "4ed_cursor_codes.h"
struct Application_Step_Result{
Application_Mouse_Cursor mouse_cursor_type;
b32 lctrl_lalt_is_altgr;
b32 perform_kill;
b32 animating;
b32 has_new_title;
char *title_string;
};
struct Application_Step_Input{
b32 first_step;
f32 dt;
Mouse_State mouse;
Input_List events;
String_Const_u8 clipboard;
b32 trying_to_kill;
};
#define App_Step_Sig(name) Application_Step_Result \
name(Thread_Context *tctx, \
Render_Target *target, \
void *base_ptr, \
Application_Step_Input *input)
typedef App_Step_Sig(App_Step);
typedef b32 Log_Function(String_Const_u8 str);
typedef Log_Function *App_Get_Logger(void);
typedef void App_Load_VTables(API_VTable_system *vtable_system,
API_VTable_font *vtable_font,
API_VTable_graphics *vtable_graphics);
struct App_Functions{
App_Load_VTables *load_vtables;
App_Get_Logger *get_logger;
App_Read_Command_Line *read_command_line;
App_Init *init;
App_Step *step;
};
#define App_Get_Functions_Sig(name) App_Functions name()
typedef App_Get_Functions_Sig(App_Get_Functions);
#endif
// BOTTOM

104
code/4ed_api_check.cpp Normal file
View File

@ -0,0 +1,104 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 06.10.2019
*
* Type checker that lists errors between two api parses.
*
*/
// TOP
#include "4coder_base_types.h"
#include "4coder_token.h"
#include "generated/lexer_cpp.h"
#include "4ed_api_definition.h"
#include "4coder_base_types.cpp"
#include "4coder_stringf.cpp"
#include "4coder_malloc_allocator.cpp"
#include "4coder_token.cpp"
#include "generated/lexer_cpp.cpp"
#include "4coder_file.cpp"
#include "4ed_api_definition.cpp"
#include "4ed_api_parser.cpp"
#include <stdio.h>
////////////////////////////////
function void
print_usage(void){
printf("usage: <script> <source-1> {<source-1>} : <source-2> {<source-2>}\n"
" source-1 : the authoritative/master api source file(s)\n"
" source-2 : the 'remote' api source file(s) to check against the master\n");
exit(1);
}
int
main(int argc, char **argv){
Arena arena = make_arena_malloc();
if (argc < 4){
print_usage();
}
API_Definition_List master_list = {};
API_Definition_List remote_list = {};
{
i32 i = 1;
for (;i < argc; i += 1){
char *file_name = argv[i];
if (string_match(SCu8(file_name), string_u8_litexpr(":"))){
i += 1;
break;
}
FILE *file = fopen(file_name, "rb");
if (file == 0){
printf("error: could not open input file: '%s'\n", file_name);
continue;
}
String_Const_u8 text = data_from_file(&arena, file);
fclose(file);
if (text.size > 0){
api_parse_source_add_to_list(&arena, SCu8(file_name), text, &master_list);
}
}
for (;i < argc; i += 1){
char *file_name = argv[i];
FILE *file = fopen(file_name, "rb");
if (file == 0){
printf("error: could not open input file: '%s'\n", file_name);
continue;
}
String_Const_u8 text = data_from_file(&arena, file);
fclose(file);
if (text.size > 0){
api_parse_source_add_to_list(&arena, SCu8(file_name), text, &remote_list);
}
}
}
if (master_list.count == 0){
printf("error: no apis in master list\n");
exit(1);
}
if (remote_list.count == 0){
printf("error: no apis in remote list\n");
exit(1);
}
List_String_Const_u8 errors = {};
api_list_check(&arena, &master_list, &remote_list, APICheck_ReportAll, &errors);
String_Const_u8 string = string_list_flatten(&arena, errors, StringFill_NullTerminate);
printf("%.*s", string_expand(string));
if (string.size > 0){
exit(1);
}
return(0);
}
// BOTTOM

649
code/4ed_api_definition.cpp Normal file
View File

@ -0,0 +1,649 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 03.10.2019
*
* System API definition program.
*
*/
// TOP
function API_Definition*
begin_api(Arena *arena, char *name){
API_Definition *api = push_array_zero(arena, API_Definition, 1);
api->name = SCu8(name);
return(api);
}
function API_Call*
api_call_with_location(Arena *arena, API_Definition *api, String_Const_u8 name, String_Const_u8 type, String_Const_u8 location){
API_Call *call = push_array_zero(arena, API_Call, 1);
sll_queue_push(api->first_call, api->last_call, call);
api->call_count += 1;
call->name = name;
call->return_type = type;
call->location_string = location;
return(call);
}
function API_Call*
api_call_with_location(Arena *arena, API_Definition *api, char *name, char *type, char *location){
return(api_call_with_location(arena, api, SCu8(name), SCu8(type), SCu8(location)));
}
function API_Type*
api_type_structure_with_location(Arena *arena, API_Definition *api, API_Type_Structure_Kind kind, String_Const_u8 name, List_String_Const_u8 member_list, String_Const_u8 definition, String_Const_u8 location){
API_Type *type = push_array_zero(arena, API_Type, 1);
sll_queue_push(api->first_type, api->last_type, type);
api->type_count += 1;
type->kind = APITypeKind_Structure;
type->name = name;
type->location_string = location;
type->struct_type.kind = kind;
type->struct_type.member_names = member_list;
type->struct_type.definition_string = definition;
return(type);
}
function API_Type*
api_type_structure_with_location(Arena *arena, API_Definition *api, API_Type_Structure_Kind kind, char *name, List_String_Const_u8 member_list, char *definition, char *location){
return(api_type_structure_with_location(arena, api, kind, name, member_list, definition, location));
}
#define api_call(arena, api, name, type) \
api_call_with_location((arena), (api), (name), (type), file_name_line_number)
function API_Param*
api_param(Arena *arena, API_Call *call, char *type_name, char *name){
API_Param *param = push_array_zero(arena, API_Param, 1);
sll_queue_push(call->params.first, call->params.last, param);
call->params.count += 1;
param->type_name = SCu8(type_name);
param->name = SCu8(name);
return(param);
}
function void
api_set_param_list(API_Call *call, API_Param_List list){
call->params = list;
}
function API_Definition*
api_get_api(API_Definition_List *list, String_Const_u8 name){
API_Definition *result = 0;
for (API_Definition *node = list->first;
node != 0;
node = node->next){
if (string_match(name, node->name)){
result = node;
break;
}
}
return(result);
}
function API_Definition*
api_get_api(Arena *arena, API_Definition_List *list, String_Const_u8 name){
API_Definition *result = api_get_api(list, name);
if (result == 0){
result = push_array_zero(arena, API_Definition, 1);
sll_queue_push(list->first, list->last, result);
list->count += 1;
result->name = name;
}
return(result);
}
function API_Call*
api_get_call(API_Definition *api, String_Const_u8 name){
API_Call *result = 0;
for (API_Call *call = api->first_call;
call != 0;
call = call->next){
if (string_match(name, call->name)){
result = call;
break;
}
}
return(result);
}
function b32
api_call_match_sigs(API_Call *a, API_Call *b){
b32 result = false;
if (a->params.count == b->params.count &&
string_match(a->return_type, b->return_type)){
result = true;
for (API_Param *a_param = a->params.first, *b_param = b->params.first;
a_param != 0 && b_param != 0;
a_param = a_param->next, b_param = b_param->next){
if (!string_match(a_param->name, b_param->name) ||
!string_match(a_param->type_name, b_param->type_name)){
result = false;
break;
}
}
}
return(result);
}
function API_Type*
api_get_type(API_Definition *api, String_Const_u8 name){
API_Type *result = 0;
for (API_Type *type = api->first_type;
type != 0;
type = type->next){
if (string_match(type->name, name)){
result = type;
break;
}
}
return(result);
}
function b32
api_type_match(API_Type *a, API_Type *b){
b32 result = false;
if (a->kind == b->kind && string_match(a->name, b->name)){
switch (a->kind){
case APITypeKind_Structure:
{
if (a->kind == b->kind &&
string_list_match(a->struct_type.member_names, b->struct_type.member_names) &&
string_match(a->struct_type.definition_string, b->struct_type.definition_string)){
result = true;
}
}break;
case APITypeKind_Enum:
{
if (a->enum_type.val_count == b->enum_type.val_count &&
string_match(a->enum_type.type_name, b->enum_type.type_name)){
result = true;
for (API_Enum_Value *a_node = a->enum_type.first_val, *b_node = b->enum_type.first_val;
a_node != 0 && b_node != 0;
a_node = a_node->next, b_node = b_node->next){
if (!string_match(a_node->name, b_node->name) ||
!string_match(a_node->val, b_node->val)){
result = false;
break;
}
}
}
}break;
case APITypeKind_Typedef:
{
if (string_match(a->typedef_type.name, b->typedef_type.name) &&
string_match(a->typedef_type.definition_text, b->typedef_type.definition_text)){
result = false;
}
}break;
}
}
return(result);
}
////////////////////////////////
#if !defined(SKIP_STDIO)
#include <stdio.h>
#include "4coder_stringf.cpp"
function String_Const_u8
api_get_callable_name(Arena *arena, String_Const_u8 api_name, String_Const_u8 name, API_Generation_Flag flags){
String_Const_u8 result = {};
if (HasFlag(flags, APIGeneration_NoAPINameOnCallables)){
result = push_u8_stringf(arena, "%.*s", string_expand(name));
}
else{
result = push_u8_stringf(arena, "%.*s_%.*s",
string_expand(api_name),
string_expand(name));
}
return(result);
}
////////////////////////////////
function void
generate_api_master_list(Arena *scratch, API_Definition *api, API_Generation_Flag flags, FILE *out){
for (API_Call *call = api->first_call;
call != 0;
call = call->next){
fprintf(out, "api(%.*s) function %.*s %.*s(",
string_expand(api->name),
string_expand(call->return_type),
string_expand(call->name));
if (call->params.count == 0){
fprintf(out, "void");
}
else{
for (API_Param *param = call->params.first;
param != 0;
param = param->next){
fprintf(out, "%.*s %.*s",
string_expand(param->type_name),
string_expand(param->name));
if (param->next != 0){
fprintf(out, ", ");
}
}
}
fprintf(out, ");\n");
}
}
function void
generate_header(Arena *scratch, API_Definition *api, API_Generation_Flag flags, FILE *out){
for (API_Call *call = api->first_call;
call != 0;
call = call->next){
fprintf(out, "#define %.*s_%.*s_sig() %.*s %.*s_%.*s(",
string_expand(api->name),
string_expand(call->name),
string_expand(call->return_type),
string_expand(api->name),
string_expand(call->name));
if (call->params.count == 0){
fprintf(out, "void");
}
else{
for (API_Param *param = call->params.first;
param != 0;
param = param->next){
fprintf(out, "%.*s %.*s",
string_expand(param->type_name),
string_expand(param->name));
if (param->next != 0){
fprintf(out, ", ");
}
}
}
fprintf(out, ")\n");
}
for (API_Call *call = api->first_call;
call != 0;
call = call->next){
fprintf(out, "typedef %.*s %.*s_%.*s_type(",
string_expand(call->return_type),
string_expand(api->name),
string_expand(call->name));
if (call->params.count == 0){
fprintf(out, "void");
}
else{
for (API_Param *param = call->params.first;
param != 0;
param = param->next){
fprintf(out, "%.*s %.*s",
string_expand(param->type_name),
string_expand(param->name));
if (param->next != 0){
fprintf(out, ", ");
}
}
}
fprintf(out, ");\n");
}
fprintf(out, "struct API_VTable_%.*s{\n", string_expand(api->name));
for (API_Call *call = api->first_call;
call != 0;
call = call->next){
fprintf(out, "%.*s_%.*s_type *",
string_expand(api->name),
string_expand(call->name));
fprintf(out, "%.*s",
string_expand(call->name));
fprintf(out, ";\n");
}
fprintf(out, "};\n");
fprintf(out, "#if defined(STATIC_LINK_API)\n");
for (API_Call *call = api->first_call;
call != 0;
call = call->next){
String_Const_u8 callable_name = api_get_callable_name(scratch, api->name, call->name, flags);
fprintf(out, "internal %.*s %.*s(",
string_expand(call->return_type),
string_expand(callable_name));
if (call->params.count == 0){
fprintf(out, "void");
}
else{
for (API_Param *param = call->params.first;
param != 0;
param = param->next){
fprintf(out, "%.*s %.*s",
string_expand(param->type_name),
string_expand(param->name));
if (param->next != 0){
fprintf(out, ", ");
}
}
}
fprintf(out, ");\n");
}
fprintf(out, "#undef STATIC_LINK_API\n");
fprintf(out, "#elif defined(DYNAMIC_LINK_API)\n");
for (API_Call *call = api->first_call;
call != 0;
call = call->next){
String_Const_u8 callable_name = api_get_callable_name(scratch, api->name, call->name, flags);
fprintf(out, "global %.*s_%.*s_type *%.*s = 0;\n",
string_expand(api->name),
string_expand(call->name),
string_expand(callable_name));
}
fprintf(out, "#undef DYNAMIC_LINK_API\n");
fprintf(out, "#endif\n");
}
function void
generate_cpp(Arena *scratch, API_Definition *api, API_Generation_Flag flags, FILE *out){
fprintf(out, "function void\n");
fprintf(out, "%.*s_api_fill_vtable(API_VTable_%.*s *vtable){\n",
string_expand(api->name),
string_expand(api->name));
for (API_Call *call = api->first_call;
call != 0;
call = call->next){
String_Const_u8 callable_name = api_get_callable_name(scratch, api->name, call->name, flags);
fprintf(out, "vtable->%.*s = %.*s;\n",
string_expand(call->name),
string_expand(callable_name));
}
fprintf(out, "}\n");
fprintf(out, "#if defined(DYNAMIC_LINK_API)\n");
fprintf(out, "function void\n");
fprintf(out, "%.*s_api_read_vtable(API_VTable_%.*s *vtable){\n",
string_expand(api->name),
string_expand(api->name));
for (API_Call *call = api->first_call;
call != 0;
call = call->next){
String_Const_u8 callable_name = api_get_callable_name(scratch, api->name, call->name, flags);
fprintf(out, "%.*s = vtable->%.*s;\n",
string_expand(callable_name),
string_expand(call->name));
}
fprintf(out, "}\n");
fprintf(out, "#undef DYNAMIC_LINK_API\n");
fprintf(out, "#endif\n");
}
function void
generate_constructor(Arena *scratch, API_Definition *api, API_Generation_Flag flags, FILE *out){
fprintf(out, "function API_Definition*\n");
fprintf(out, "%.*s_api_construct(Arena *arena){\n",
string_expand(api->name));
fprintf(out, "API_Definition *result = begin_api(arena, \"%.*s\");\n",
string_expand(api->name));
for (API_Call *call = api->first_call;
call != 0;
call = call->next){
fprintf(out, "{\n");
fprintf(out, "API_Call *call = api_call_with_location(arena, result, "
"string_u8_litexpr(\"%.*s\"), "
"string_u8_litexpr(\"%.*s\"), "
"string_u8_litexpr(\"\"));\n",
string_expand(call->name),
string_expand(call->return_type));
if (call->params.count == 0){
fprintf(out, "(void)call;\n");
}
else{
for (API_Param *param = call->params.first;
param != 0;
param = param->next){
fprintf(out, "api_param(arena, call, \"%.*s\", \"%.*s\");\n",
string_expand(param->type_name),
string_expand(param->name));
}
}
fprintf(out, "}\n");
}
fprintf(out, "return(result);\n");
fprintf(out, "}\n");
}
////////////////////////////////
function b32
api_definition_generate_api_includes(Arena *arena, API_Definition *api, Generated_Group group, API_Generation_Flag flags){
// NOTE(allen): Arrange output files
String_Const_u8 path_to_self = string_u8_litexpr(__FILE__);
path_to_self = string_remove_last_folder(path_to_self);
String_Const_u8 fname_ml = {};
String_Const_u8 fname_h = {};
String_Const_u8 fname_cpp = {};
String_Const_u8 fname_con = {};
String_Const_u8 root = {};
switch (group){
case GeneratedGroup_Core:
{
root = string_u8_litexpr("generated/");
}break;
case GeneratedGroup_Custom:
{
root = string_u8_litexpr("custom/generated/");
}break;
}
fname_ml = push_u8_stringf(arena, "%.*s%.*s%.*s_api_master_list.h",
string_expand(path_to_self),
string_expand(root),
string_expand(api->name));
fname_h = push_u8_stringf(arena, "%.*s%.*s%.*s_api.h",
string_expand(path_to_self),
string_expand(root),
string_expand(api->name));
fname_cpp = push_u8_stringf(arena, "%.*s%.*s%.*s_api.cpp",
string_expand(path_to_self),
string_expand(root),
string_expand(api->name));
fname_con = push_u8_stringf(arena, "%.*s%.*s%.*s_api_constructor.cpp",
string_expand(path_to_self),
string_expand(root),
string_expand(api->name));
FILE *out_file_ml = fopen((char*)fname_ml.str, "wb");
if (out_file_ml == 0){
printf("could not open output file: '%s'\n", fname_ml.str);
return(false);
}
FILE *out_file_h = fopen((char*)fname_h.str, "wb");
if (out_file_h == 0){
printf("could not open output file: '%s'\n", fname_h.str);
return(false);
}
FILE *out_file_cpp = fopen((char*)fname_cpp.str, "wb");
if (out_file_cpp == 0){
printf("could not open output file: '%s'\n", fname_cpp.str);
return(false);
}
FILE *out_file_con = fopen((char*)fname_con.str, "wb");
if (out_file_cpp == 0){
printf("could not open output file: '%s'\n", fname_con.str);
return(false);
}
printf("%s:1:\n", fname_ml.str);
printf("%s:1:\n", fname_h.str);
printf("%s:1:\n", fname_cpp.str);
printf("%s:1:\n", fname_con.str);
////////////////////////////////
// NOTE(allen): Generate output
generate_api_master_list(arena, api, flags, out_file_ml);
generate_header(arena, api, flags, out_file_h);
generate_cpp(arena, api, flags, out_file_cpp);
generate_constructor(arena, api, flags, out_file_con);
////////////////////////////////
fclose(out_file_ml);
fclose(out_file_h);
fclose(out_file_cpp);
return(true);
}
////////////////////////////////
function void
api_definition_error(Arena *arena, List_String_Const_u8 *list,
char *e1, API_Call *c1, char *e2, API_Call *c2){
Assert(e1 != 0);
Assert(c1 != 0);
string_list_pushf(arena, list,
"%.*s error: %s '%.*s'",
string_expand(c1->location_string),
e1, string_expand(c1->name));
if (e2 != 0){
string_list_pushf(arena, list, " %s", e2);
if (c2 != 0){
string_list_pushf(arena, list, " '%.*s'", string_expand(c2->name));
}
}
string_list_push(arena, list, string_u8_litexpr("\n"));
if (c2 != 0){
string_list_push(arena, list, c2->location_string);
string_list_pushf(arena, list, " note: see declaration of '%.*s'\n", string_expand(c2->name));
}
}
function void
api_definition_error(Arena *arena, List_String_Const_u8 *list,
char *e1, API_Call *c1, char *e2){
api_definition_error(arena, list, e1, c1, e2, 0);
}
function void
api_definition_error(Arena *arena, List_String_Const_u8 *list,
char *e1, API_Call *c1){
api_definition_error(arena, list, e1, c1, 0, 0);
}
function void
api_definition_error(Arena *arena, List_String_Const_u8 *list,
char *e1, API_Definition *api1, char *e2){
Assert(e1 != 0);
Assert(api1 != 0);
string_list_pushf(arena, list, "error: %s '%.*s'",
e1, string_expand(api1->name));
if (e2 != 0){
string_list_pushf(arena, list, " %s", e2);
}
string_list_push(arena, list, string_u8_litexpr("\n"));
}
function void
api_definition_error(Arena *arena, List_String_Const_u8 *list,
char *e1, API_Definition *api1){
api_definition_error(arena, list, e1, api1, 0);
}
function void
api_definition_check(Arena *arena, API_Definition *correct, API_Definition *remote, API_Check_Flag flags, List_String_Const_u8 *error_list){
b32 report_missing = HasFlag(flags, APICheck_ReportMissingAPI);
b32 report_extra = HasFlag(flags, APICheck_ReportExtraAPI);
b32 report_mismatch = HasFlag(flags, APICheck_ReportMismatchAPI);
b32 iterate_correct = (report_missing || report_mismatch);
if (iterate_correct){
for (API_Call *call = correct->first_call;
call != 0;
call = call->next){
API_Call *remote_call = api_get_call(remote, call->name);
if (remote_call == 0 && report_missing){
api_definition_error(arena, error_list,
"no remote call for", call);
}
if (remote_call != 0 && !api_call_match_sigs(call, remote_call) && report_mismatch){
api_definition_error(arena, error_list,
"remote call", remote_call,
"does not match signature for", call);
}
}
}
b32 iterate_remote = (report_extra);
if (iterate_remote){
for (API_Call *call = remote->first_call;
call != 0;
call = call->next){
API_Call *correct_call = api_get_call(correct, call->name);
if (correct_call == 0 && report_extra){
api_definition_error(arena, error_list,
"remote call", call,
"does not exist in api master");
}
}
}
}
function void
api_list_check(Arena *arena, API_Definition_List *correct, API_Definition_List *remote, API_Check_Flag flags, List_String_Const_u8 *error_list){
b32 report_missing = HasFlag(flags, APICheck_ReportMissingAPI);
b32 report_extra = HasFlag(flags, APICheck_ReportExtraAPI);
b32 iterate_correct = (report_missing);
if (iterate_correct){
for (API_Definition *api = correct->first;
api != 0;
api = api->next){
API_Definition *remote_api = api_get_api(remote, api->name);
if (remote_api == 0 && report_missing){
api_definition_error(arena, error_list,
"no remote api for", api);
}
}
}
b32 iterate_remote = (report_extra);
if (iterate_remote){
for (API_Definition *api = remote->first;
api != 0;
api = api->next){
API_Definition *correct_api = api_get_api(correct, api->name);
if (correct_api == 0 && report_extra){
api_definition_error(arena, error_list,
"remote api", api,
"does not have a master");
}
}
}
for (API_Definition *api = correct->first;
api != 0;
api = api->next){
API_Definition *remote_api = api_get_api(remote, api->name);
if (remote_api != 0){
api_definition_check(arena, api, remote_api, flags, error_list);
}
}
}
#endif
// BOTTOM

119
code/4ed_api_definition.h Normal file
View File

@ -0,0 +1,119 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 03.10.2019
*
* System API definition types.
*
*/
// TOP
#if !defined(FRED_API_DEFINITION_H)
#define FRED_API_DEFINITION_H
struct API_Param{
API_Param *next;
String_Const_u8 type_name;
String_Const_u8 name;
};
struct API_Param_List{
API_Param *first;
API_Param *last;
i32 count;
};
struct API_Call{
API_Call *next;
String_Const_u8 name;
String_Const_u8 return_type;
String_Const_u8 location_string;
API_Param_List params;
};
typedef i32 API_Type_Structure_Kind;
enum{
APITypeStructureKind_Struct,
APITypeStructureKind_Union,
};
struct API_Type_Structure{
API_Type_Structure_Kind kind;
List_String_Const_u8 member_names;
String_Const_u8 definition_string;
};
struct API_Enum_Value{
API_Enum_Value *next;
String_Const_u8 name;
String_Const_u8 val;
};
struct API_Type_Enum{
String_Const_u8 type_name;
API_Enum_Value *first_val;
API_Enum_Value *last_val;
i32 val_count;
};
struct API_Type_Typedef{
String_Const_u8 name;
String_Const_u8 definition_text;
};
typedef i32 API_Type_Kind;
enum{
APITypeKind_Structure,
APITypeKind_Enum,
APITypeKind_Typedef,
};
struct API_Type{
API_Type *next;
API_Type_Kind kind;
String_Const_u8 name;
String_Const_u8 location_string;
union{
API_Type_Structure struct_type;
API_Type_Enum enum_type;
API_Type_Typedef typedef_type;
};
};
struct API_Definition{
API_Definition *next;
API_Call *first_call;
API_Call *last_call;
i32 call_count;
API_Type *first_type;
API_Type *last_type;
i32 type_count;
String_Const_u8 name;
};
struct API_Definition_List{
API_Definition *first;
API_Definition *last;
i32 count;
};
typedef u32 API_Generation_Flag;
enum{
APIGeneration_NoAPINameOnCallables = 1,
};
typedef u32 API_Check_Flag;
enum{
APICheck_ReportMissingAPI = 1,
APICheck_ReportExtraAPI = 2,
APICheck_ReportMismatchAPI = 4,
};
enum{
APICheck_ReportAll = APICheck_ReportMissingAPI|APICheck_ReportExtraAPI|APICheck_ReportMismatchAPI,
};
#endif
// BOTTOM

View File

@ -0,0 +1,41 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 02.10.2019
*
* System API definition program.
*
*/
// TOP
#include "4coder_base_types.h"
#include "4ed_api_definition.h"
#include "4coder_base_types.cpp"
#include "4ed_api_definition.cpp"
#include "4coder_stringf.cpp"
#include "4coder_malloc_allocator.cpp"
#include <stdio.h>
////////////////////////////////
function API_Definition*
define_api(Arena *arena);
function Generated_Group
get_api_group(void);
int
main(void){
Arena arena = make_arena_malloc();
API_Definition *api = define_api(&arena);
if (!api_definition_generate_api_includes(&arena, api, get_api_group(), 0)){
return(1);
}
return(0);
}
// BOTTOM

File diff suppressed because it is too large Load Diff

322
code/4ed_api_parser.cpp Normal file
View File

@ -0,0 +1,322 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 03.10.2019
*
* Parser that extracts an API from C++ source code.
*
*/
// TOP
/*
function:
api ( <identifier> ) function <identifier> {*} <identifier> ( <param_list> )
param_list:
void |
<identifier> {*} <identifier> [, <identifier> {*} <identifier>]
anything_else:
***
api_source:
{function|anything_else} EOF
*/
function Token*
api_parse__token_pos(Token_Iterator *it){
return(token_it_read(it));
}
function b32
api_parse__match(Token_Iterator *it, Token_Cpp_Kind sub_kind){
b32 match = false;
Token *token = token_it_read(it);
if (token != 0 && token->sub_kind == sub_kind){
if (token_it_inc(it)){
match = true;
}
}
return(match);
}
function b32
api_parse__match_identifier(Token_Iterator *it, String_Const_u8 source, String_Const_u8 *lexeme){
b32 match = false;
Token *token = token_it_read(it);
if (token->kind == TokenBaseKind_Identifier ||
token->kind == TokenBaseKind_Keyword){
if (token_it_inc(it)){
*lexeme = string_substring(source, Ii64(token));
match = true;
}
}
return(match);
}
function b32
api_parse__match_identifier(Token_Iterator *it, String_Const_u8 source, char *lexeme){
b32 match = false;
Token *token = token_it_read(it);
if ((token->kind == TokenBaseKind_Identifier ||
token->kind == TokenBaseKind_Keyword) &&
string_match(SCu8(lexeme), string_substring(source, Ii64(token)))){
if (token_it_inc(it)){
match = true;
}
}
return(match);
}
function String_Const_u8
api_parse__type_name_with_stars(Arena *arena, String_Const_u8 type, i32 star_counter){
if (star_counter > 0){
i32 type_full_size = (i32)(type.size) + star_counter;
u8 *type_buffer = push_array(arena, u8, type_full_size + 1);
block_copy(type_buffer, type.str, type.size);
block_fill_u8(type_buffer + type.size, star_counter, (u8)'*');
type_buffer[type_full_size] = 0;
type = SCu8(type_buffer, type_full_size);
}
return(type);
}
function void
api_parse_add_param(Arena *arena, API_Param_List *list, String_Const_u8 type, i32 star_counter, String_Const_u8 name){
type = api_parse__type_name_with_stars(arena, type, star_counter);
API_Param *param = push_array(arena, API_Param, 1);
sll_queue_push(list->first, list->last, param);
list->count += 1;
param->type_name = type;
param->name = name;
}
function void
api_parse_add_function(Arena *arena, API_Definition_List *list,
String_Const_u8 api_name, String_Const_u8 func_name,
String_Const_u8 type, i32 star_counter, API_Param_List param_list,
String_Const_u8 location){
API_Definition *api = api_get_api(arena, list, api_name);
type = api_parse__type_name_with_stars(arena, type, star_counter);
API_Call *call = api_call_with_location(arena, api, func_name, type, location);
api_set_param_list(call, param_list);
}
function void
api_parse_add_structure(Arena *arena, API_Definition_List *list,
String_Const_u8 api_name, API_Type_Structure_Kind kind,
String_Const_u8 name, List_String_Const_u8 member_list,
String_Const_u8 definition, String_Const_u8 location){
API_Definition *api = api_get_api(arena, list, api_name);
api_type_structure_with_location(arena, api, kind, name, member_list, definition, location);
}
function String_Const_u8
api_parse_location(Arena *arena, String_Const_u8 source_name, String_Const_u8 source, u8 *pos){
i32 line_number = 1;
i32 col_number = 1;
if (source.str <= pos && pos < source.str + source.size){
for (u8 *ptr = source.str;;){
if (ptr == pos){
break;
}
if (*ptr == '\n'){
line_number += 1;
col_number = 1;
}
else{
col_number += 1;
}
ptr += 1;
}
}
return(push_u8_stringf(arena, "%.*s:%d:%d:", string_expand(source_name), line_number, col_number));
}
function b32
api_parse_source__function(Arena *arena, String_Const_u8 source_name, String_Const_u8 source, Token_Iterator *token_it, String_Const_u8 api_name, API_Definition_List *list){
b32 result = false;
String_Const_u8 ret_type = {};
i32 ret_type_star_counter = 0;
String_Const_u8 func_name = {};
API_Param_List param_list = {};
if (api_parse__match_identifier(token_it, source, &ret_type)){
for (;api_parse__match(token_it, TokenCppKind_Star);){
ret_type_star_counter += 1;
}
if (api_parse__match_identifier(token_it, source, &func_name)){
if (api_parse__match(token_it, TokenCppKind_ParenOp)){
b32 param_list_success = false;
if (api_parse__match_identifier(token_it, source, "void")){
param_list_success = true;
}
else{
for (;;){
String_Const_u8 type = {};
i32 star_counter = 0;
String_Const_u8 name = {};
if (api_parse__match_identifier(token_it, source, &type)){
for (;api_parse__match(token_it, TokenCppKind_Star);){
star_counter += 1;
}
if (api_parse__match_identifier(token_it, source, &name)){
param_list_success = true;
}
else{
break;
}
}
else{
break;
}
if (param_list_success){
api_parse_add_param(arena, &param_list, type, star_counter, name);
}
if (api_parse__match(token_it, TokenCppKind_Comma)){
param_list_success = false;
}
else{
break;
}
}
}
if (param_list_success){
if (api_parse__match(token_it, TokenCppKind_ParenCl)){
result = true;
}
}
}
}
}
if (result){
String_Const_u8 location = api_parse_location(arena, source_name, source, func_name.str);
api_parse_add_function(arena, list, api_name, func_name, ret_type, ret_type_star_counter, param_list, location);
}
return(result);
}
function String_Const_u8
api_parse__restringize_token_range(Arena *arena, String_Const_u8 source, Token *token, Token *token_end){
List_String_Const_u8 list = {};
for (Token *t = token; t < token_end; t += 1){
if (t->kind == TokenBaseKind_Comment){
continue;
}
if (t->kind == TokenBaseKind_Whitespace){
// TODO(allen): if there is a newline, emit it, all other whitespace is managed automatically.
continue;
}
String_Const_u8 str = string_substring(source, Ii64(t));
string_list_push(arena, &list, str);
}
return(string_list_flatten(arena, list));
}
function b32
api_parse_source__structure(Arena *arena, String_Const_u8 source_name, String_Const_u8 source, API_Type_Structure_Kind kind, Token_Iterator *token_it, String_Const_u8 api_name, API_Definition_List *list){
b32 result = false;
String_Const_u8 name = {};
List_String_Const_u8 member_list = {};
Token *token = api_parse__token_pos(token_it);
(void)token;
if (api_parse__match_identifier(token_it, source, &name)){
if (api_parse__match(token_it, TokenCppKind_Semicolon)){
result = true;
}
else if (api_parse__match(token_it, TokenCppKind_BraceOp)){
b32 member_list_success = false;
for (;;){
String_Const_u8 member_name = {};
if (api_parse__match(token_it, TokenCppKind_BraceCl)){
member_list_success = true;
break;
}
else if (api_parse__match_identifier(token_it, source, &member_name)){
if (api_parse__match(token_it, TokenCppKind_Semicolon)){
string_list_push(arena, &member_list, member_name);
}
}
else{
if (!token_it_inc(token_it)){
break;
}
}
}
if (member_list_success){
if (api_parse__match(token_it, TokenCppKind_BraceCl)){
if (api_parse__match(token_it, TokenCppKind_Semicolon)){
result = true;
}
}
}
}
}
if (result){
Token *token_end = api_parse__token_pos(token_it);
(void)token_end;
// TODO(allen):
String_Const_u8 definition = {};
String_Const_u8 location = api_parse_location(arena, source_name, source, name.str);
api_parse_add_structure(arena, list, api_name, kind, name, member_list, definition, location);
}
return(result);
}
function b32
api_parse_source__struct(Arena *arena, String_Const_u8 source_name, String_Const_u8 source, Token_Iterator *token_it, String_Const_u8 api_name, API_Definition_List *list){
return(api_parse_source__structure(arena, source_name, source, APITypeStructureKind_Struct, token_it, api_name, list));
}
function b32
api_parse_source__union(Arena *arena, String_Const_u8 source_name, String_Const_u8 source, Token_Iterator *token_it, String_Const_u8 api_name, API_Definition_List *list){
return(api_parse_source__structure(arena, source_name, source, APITypeStructureKind_Union, token_it, api_name, list));
}
function void
api_parse_source_add_to_list(Arena *arena, String_Const_u8 source_name, String_Const_u8 source, API_Definition_List *list){
Token_List token_list = lex_full_input_cpp(arena, source);
Token_Iterator token_it = token_iterator(token_iterator(0, &token_list));
for (;;){
Token *token = token_it_read(&token_it);
if (token->sub_kind == TokenCppKind_EOF){
break;
}
if (api_parse__match_identifier(&token_it, source, "api")){
String_Const_u8 api_name = {};
if (api_parse__match(&token_it, TokenCppKind_ParenOp)){
if (api_parse__match_identifier(&token_it, source, &api_name)){
if (api_parse__match(&token_it, TokenCppKind_ParenCl)){
if (api_parse__match_identifier(&token_it, source, "function")){
api_parse_source__function(arena, source_name, source, &token_it, api_name, list);
}
else if (api_parse__match_identifier(&token_it, source, "struct")){
api_parse_source__struct(arena, source_name, source, &token_it, api_name, list);
}
else if (api_parse__match_identifier(&token_it, source, "union")){
api_parse_source__union(arena, source_name, source, &token_it, api_name, list);
}
}
}
}
}
else{
if (!token_it_inc(&token_it)){
break;
}
}
}
}
function API_Definition_List
api_parse_source(Arena *arena, String_Const_u8 source_name, String_Const_u8 source){
API_Definition_List list = {};
api_parse_source_add_to_list(arena, source_name, source, &list);
return(list);
}
// BOTTOM

View File

@ -0,0 +1,64 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 06.10.2019
*
* Parser that extracts an API from C++ source code.
*
*/
// TOP
#include "4coder_base_types.h"
#include "4coder_token.h"
#include "generated/lexer_cpp.h"
#include "4ed_api_definition.h"
#include "4coder_base_types.cpp"
#include "4coder_stringf.cpp"
#include "4coder_malloc_allocator.cpp"
#include "4coder_token.cpp"
#include "generated/lexer_cpp.cpp"
#include "4coder_file.cpp"
#include "4ed_api_definition.cpp"
#include "4ed_api_parser.cpp"
#include <stdio.h>
////////////////////////////////
int
main(int argc, char **argv){
Arena arena = make_arena_malloc();
if (argc < 2){
printf("usage: <script> <source> {<source>}\n"
" source : file to load and parse into the output list\n");
exit(1);
}
API_Definition_List list = {};
for (i32 i = 1; i < argc; i += 1){
char *file_name = argv[i];
FILE *file = fopen(file_name, "rb");
if (file == 0){
printf("error: could not open input file: '%s'\n", argv[i]);
continue;
}
String_Const_u8 text = data_from_file(&arena, file);
fclose(file);
if (text.size > 0){
api_parse_source_add_to_list(&arena, SCu8(file_name), text, &list);
}
}
for (API_Definition *node = list.first;
node != 0;
node = node->next){
api_definition_generate_api_includes(&arena, node, GeneratedGroup_Custom, APIGeneration_NoAPINameOnCallables);
}
}
// BOTTOM

77
code/4ed_app_models.cpp Normal file
View File

@ -0,0 +1,77 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 01.05.2020
*
* Implementation of the root models features.
*
*/
// TOP
function void
models_push_view_command_function(Models *models, View_ID view_id, Custom_Command_Function *custom_func){
Model_View_Command_Function *node = models->free_view_cmd_funcs;
if (node == 0){
node = push_array(models->arena, Model_View_Command_Function, 1);
}
else{
sll_stack_pop(models->free_view_cmd_funcs);
}
sll_queue_push(models->first_view_cmd_func, models->last_view_cmd_func, node);
node->view_id = view_id;
node->custom_func = custom_func;
}
function Model_View_Command_Function
models_pop_view_command_function(Models *models){
Model_View_Command_Function result = {};
if (models->first_view_cmd_func != 0){
Model_View_Command_Function *node = models->first_view_cmd_func;
result.custom_func = node->custom_func;
result.view_id = node->view_id;
sll_queue_pop(models->first_view_cmd_func, models->last_view_cmd_func);
sll_stack_push(models->free_view_cmd_funcs, node);
}
return(result);
}
function void
models_push_virtual_event(Models *models, Input_Event *event){
Model_Input_Event_Node *node = models->free_virtual_event;
if (node == 0){
node = push_array(&models->virtual_event_arena, Model_Input_Event_Node, 1);
}
else{
sll_stack_pop(models->free_virtual_event);
}
sll_queue_push(models->first_virtual_event, models->last_virtual_event, node);
node->event = copy_input_event(&models->virtual_event_arena, event);
}
function Input_Event
models_pop_virtual_event(Arena *arena, Models *models){
Input_Event result = {};
if (models->first_virtual_event != 0){
Model_Input_Event_Node *node = models->first_virtual_event;
result = copy_input_event(arena, &node->event);
sll_queue_pop(models->first_virtual_event, models->last_virtual_event);
sll_stack_push(models->free_virtual_event, node);
}
return(result);
}
function void
models_push_wind_down(Models *models, Coroutine *co){
Model_Wind_Down_Co *node = models->free_wind_downs;
if (node != 0){
sll_stack_pop(models->free_wind_downs);
}
else{
node = push_array(models->arena, Model_Wind_Down_Co, 1);
}
sll_stack_push(models->wind_down_stack, node);
node->co = co;
}
// BOTTOM

203
code/4ed_app_models.h Normal file
View File

@ -0,0 +1,203 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 06.05.2016 (dd.mm.yyyy)
*
* Global app level settings definition
*
*/
// TOP
#if !defined(FRED_APP_MODELS_H)
#define FRED_APP_MODELS_H
struct App_Settings{
char *init_files[8];
i32 init_files_count;
i32 init_files_max;
char **custom_flags;
i32 custom_flags_count;
b32 lctrl_lalt_is_altgr;
i32 font_size;
b8 use_hinting;
};
enum App_State{
APP_STATE_EDIT,
APP_STATE_RESIZING,
// never below this
APP_STATE_COUNT
};
struct Model_View_Command_Function{
Model_View_Command_Function *next;
Custom_Command_Function *custom_func;
View_ID view_id;
};
struct Model_Input_Event_Node{
Model_Input_Event_Node *next;
Input_Event event;
};
struct Model_Wind_Down_Co{
Model_Wind_Down_Co *next;
Coroutine *co;
};
struct Models{
Arena arena_;
Arena *arena;
Heap heap;
App_Settings settings;
App_State state;
Face_ID global_face_id;
Coroutine_Group coroutines;
Model_Wind_Down_Co *wind_down_stack;
Model_Wind_Down_Co *free_wind_downs;
Child_Process_Container child_processes;
Custom_API config_api;
Tick_Function *tick;
Render_Caller_Function *render_caller;
Whole_Screen_Render_Caller_Function *whole_screen_render_caller;
Delta_Rule_Function *delta_rule;
u64 delta_rule_memory_size;
Hook_Function *buffer_viewer_update;
Custom_Command_Function *view_event_handler;
Buffer_Name_Resolver_Function *buffer_name_resolver;
Buffer_Hook_Function *begin_buffer;
Buffer_Hook_Function *end_buffer;
Buffer_Hook_Function *new_file;
Buffer_Hook_Function *save_file;
Buffer_Edit_Range_Function *buffer_edit_range;
Buffer_Region_Function *buffer_region;
Layout_Function *layout_func;
View_Change_Buffer_Function *view_change_buffer;
Color_Table color_table_;
Model_View_Command_Function *free_view_cmd_funcs;
Model_View_Command_Function *first_view_cmd_func;
Model_View_Command_Function *last_view_cmd_func;
Arena virtual_event_arena;
Model_Input_Event_Node *free_virtual_event;
Model_Input_Event_Node *first_virtual_event;
Model_Input_Event_Node *last_virtual_event;
Layout layout;
Working_Set working_set;
Live_Views view_set;
Global_History global_history;
Text_Layout_Container text_layouts;
Font_Set font_set;
Managed_ID_Set managed_id_set;
Dynamic_Workspace dynamic_workspace;
Lifetime_Allocator lifetime_allocator;
Editing_File *message_buffer;
Editing_File *scratch_buffer;
Editing_File *log_buffer;
Editing_File *keyboard_buffer;
Hot_Directory hot_directory;
b8 keep_playing;
b8 hard_exit;
b8 has_new_title;
i32 title_capacity;
char *title_space;
Panel *resizing_intermediate_panel;
Plat_Handle period_wakeup_timer;
i32 frame_counter;
u32 next_animate_delay;
b32 animate_next_frame;
Profile_Global_List profile_list;
// Last frame state
Vec2_i32 prev_p;
Panel *prev_mouse_panel;
b32 animated_last_frame;
u64 last_render_usecond_stamp;
// Event Context
Application_Step_Input *input;
i64 current_input_sequence_number;
User_Input current_input;
b8 current_input_unhandled;
b8 in_render_mode;
Render_Target *target;
};
////////////////////////////////
typedef i32 Dynamic_Workspace_Type;
enum{
DynamicWorkspace_Global = 0,
DynamicWorkspace_Unassociated = 1,
DynamicWorkspace_Buffer = 2,
DynamicWorkspace_View = 3,
DynamicWorkspace_Intersected = 4,
};
enum Input_Types{
Input_AnyKey,
Input_Esc,
Input_MouseMove,
Input_MouseLeftButton,
Input_MouseRightButton,
Input_MouseWheel,
Input_Count
};
struct Consumption_Record{
b32 consumed;
char consumer[32];
};
struct File_Init{
String_Const_u8 name;
Editing_File **ptr;
b32 read_only;
};
enum Command_Line_Action{
CLAct_Nothing,
CLAct_Ignore,
CLAct_CustomDLL,
CLAct_WindowSize,
CLAct_WindowMaximize,
CLAct_WindowPosition,
CLAct_WindowFullscreen,
CLAct_FontSize,
CLAct_FontUseHinting,
CLAct_UserDirectory,
//
CLAct_COUNT,
};
enum Command_Line_Mode{
CLMode_App,
CLMode_Custom
};
#endif
// BOTTOM

127
code/4ed_app_target.cpp Normal file
View File

@ -0,0 +1,127 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 13.11.2015
*
* Application layer build target
*
*/
// TOP
#define REMOVE_OLD_STRING
#include "4coder_base_types.h"
#include "4coder_version.h"
#include "4coder_table.h"
#include "4coder_events.h"
#include "4coder_types.h"
#include "4coder_doc_content_types.h"
#include "4coder_default_colors.h"
#define STATIC_LINK_API
#include "generated/custom_api.h"
#include "4coder_string_match.h"
#include "4coder_token.h"
#include "4coder_system_types.h"
#define DYNAMIC_LINK_API
#include "generated/system_api.h"
#include "4ed_font_interface.h"
#define DYNAMIC_LINK_API
#include "generated/graphics_api.h"
#define DYNAMIC_LINK_API
#include "generated/font_api.h"
#include "4coder_profile.h"
#include "4coder_command_map.h"
#include "4ed_render_target.h"
#include "4ed.h"
#include "4ed_buffer_model.h"
#include "4ed_coroutine.h"
#include "4ed_dynamic_variables.h"
#include "4ed_buffer_model.h"
#include "4ed_translation.h"
#include "4ed_buffer.h"
#include "4ed_history.h"
#include "4ed_file.h"
#include "4ed_working_set.h"
#include "4ed_hot_directory.h"
#include "4ed_cli.h"
#include "4ed_layout.h"
#include "4ed_view.h"
#include "4ed_edit.h"
#include "4ed_text_layout.h"
#include "4ed_font_set.h"
#include "4ed_log.h"
#include "4ed_app_models.h"
#include "generated/lexer_cpp.h"
#include "4ed_api_definition.h"
#include "docs/4ed_doc_helper.h"
////////////////////////////////
#include "4coder_base_types.cpp"
#include "4coder_layout.cpp"
#include "4coder_string_match.cpp"
#include "4coder_stringf.cpp"
#include "4coder_events.cpp"
#include "4coder_system_helpers.cpp"
#include "4coder_app_links_allocator.cpp"
#include "4coder_system_allocator.cpp"
#include "4coder_profile.cpp"
#include "4coder_profile_static_enable.cpp"
#include "4coder_hash_functions.cpp"
#include "4coder_table.cpp"
#include "4coder_log.cpp"
#include "4coder_buffer_seek_constructors.cpp"
#include "4coder_command_map.cpp"
#include "4coder_codepoint_map.cpp"
#include "generated/custom_api.cpp"
#define DYNAMIC_LINK_API
#include "generated/system_api.cpp"
#define DYNAMIC_LINK_API
#include "generated/graphics_api.cpp"
#define DYNAMIC_LINK_API
#include "generated/font_api.cpp"
#include "4coder_token.cpp"
#include "generated/lexer_cpp.cpp"
#include "4ed_api_definition.cpp"
#include "generated/custom_api_constructor.cpp"
#include "4ed_api_parser.cpp"
#include "4coder_doc_content_types.cpp"
#include "docs/4ed_doc_helper.cpp"
#include "docs/4ed_doc_custom_api.cpp"
#include "4ed_log.cpp"
#include "4ed_coroutine.cpp"
#include "4ed_mem.cpp"
#include "4ed_dynamic_variables.cpp"
#include "4ed_font_set.cpp"
#include "4ed_translation.cpp"
#include "4ed_render_target.cpp"
#include "4ed_app_models.cpp"
#include "4ed_buffer.cpp"
#include "4ed_string_matching.cpp"
#include "4ed_history.cpp"
#include "4ed_file.cpp"
#include "4ed_working_set.cpp"
#include "4ed_hot_directory.cpp"
#include "4ed_cli.cpp"
#include "4ed_layout.cpp"
#include "4ed_view.cpp"
#include "4ed_edit.cpp"
#include "4ed_text_layout.cpp"
#include "4ed_api_implementation.cpp"
#include "4ed.cpp"
// BOTTOM

721
code/4ed_buffer.cpp Normal file
View File

@ -0,0 +1,721 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 06.01.2017
*
* The 4coder base buffer data structure.
*
*/
// TOP
//
// Buffer low level operations
//
internal void
write_cursor_with_index(Cursor_With_Index *positions, i32 *count, i64 pos){
positions[*count].index = *count;
positions[*count].pos = pos;
++(*count);
}
internal void
buffer_quick_sort_cursors(Cursor_With_Index *positions, i32 first, i32 one_past_last){
if (first + 1 < one_past_last){
i32 pivot = one_past_last - 1;
i64 pivot_pos = positions[pivot].pos;
i32 j = first;
for (i32 i = first; i < pivot; i += 1){
i64 pos = positions[i].pos;
if (pos < pivot_pos){
Swap(Cursor_With_Index, positions[j], positions[i]);
j += 1;
}
}
Swap(Cursor_With_Index, positions[j], positions[pivot]);
buffer_quick_sort_cursors(positions, first, j);
buffer_quick_sort_cursors(positions, j + 1, one_past_last);
}
}
internal void
buffer_sort_cursors(Cursor_With_Index *positions, i32 count){
if (count > 0){
buffer_quick_sort_cursors(positions, 0, count);
}
}
internal void
buffer_unsort_cursors(Cursor_With_Index *positions, i32 count){
if (count > 0){
i32 i = 0;
for (;;){
if (positions[i].index == i){
i += 1;
if (i >= count){
break;
}
}
else{
i32 j = positions[i].index;
Swap(Cursor_With_Index, positions[i], positions[j]);
}
}
}
}
#if 0
function void
buffer_sort_batch(Edit *batch, i32 first, i32 one_past_last){
if (first + 1 < one_past_last){
i32 pivot = one_past_last - 1;
i64 pivot_pos = batch[pivot].range.first;
i32 j = first;
for (i32 i = first; i < pivot; i += 1){
i64 pos = batch[i].range.first;
if (pos < pivot_pos){
Swap(Edit, batch[j], batch[i]);
j += 1;
}
}
Swap(Edit, batch[j], batch[pivot]);
buffer_sort_batch(batch, first, j);
buffer_sort_batch(batch, j + 1, one_past_last);
}
}
function Edit_Array
buffer_batch_array_from_linked_list(Arena *arena, Batch_Edit *batch, i32 count){
Edit_Array result = {};
result.count = count;
result.vals = push_array(arena, Edit, count);
i32 counter = 0;
for (Batch_Edit *node = batch;
counter < count && node != 0;
node = node->next){
result.vals[counter] = node->edit;
counter += 1;
}
return(result);
}
function Edit_Array
buffer_sort_batch(Arena *arena, Batch_Edit *batch, i32 count){
Edit_Array result = buffer_batch_array_from_linked_list(arena, batch, count);
buffer_sort_batch(result.vals, 0, result.count);
return(result);
}
#endif
internal void
buffer_update_cursors_lean_l(Cursor_With_Index *sorted_positions, i32 count,
Batch_Edit *batch){
Cursor_With_Index *pos = sorted_positions;
Cursor_With_Index *end_pos = sorted_positions + count;
i64 shift_amount = 0;
for (; batch != 0 && pos < end_pos;
batch = batch->next){
Range_i64 range = batch->edit.range;
i64 len = batch->edit.text.size;
if (shift_amount != 0){
for (;pos < end_pos && pos->pos < range.first; pos += 1){
pos->pos += shift_amount;
}
}
else{
for (;pos < end_pos && pos->pos < range.first; pos += 1);
}
i64 new_pos = range.first + shift_amount;
for (;pos < end_pos && pos->pos <= range.one_past_last; pos += 1){
pos->pos = new_pos;
}
shift_amount += len - (range.one_past_last - range.first);
}
if (shift_amount != 0){
for (;pos < end_pos; pos += 1){
pos->pos += shift_amount;
}
}
}
internal void
buffer_update_cursors_lean_r(Cursor_With_Index *sorted_positions, i32 count,
Batch_Edit *batch){
Cursor_With_Index *pos = sorted_positions;
Cursor_With_Index *end_pos = sorted_positions + count;
i64 shift_amount = 0;
for (; batch != 0 && pos < end_pos;
batch = batch->next){
Range_i64 range = batch->edit.range;
i64 len = batch->edit.text.size;
if (shift_amount != 0){
for (;pos < end_pos && pos->pos < range.first; pos += 1){
pos->pos += shift_amount;
}
}
else{
for (;pos < end_pos && pos->pos < range.first; pos += 1);
}
i64 new_pos = range.first + len + shift_amount;
for (;pos < end_pos && pos->pos < range.one_past_last; pos += 1){
pos->pos = new_pos;
}
shift_amount += len - (range.one_past_last - range.first);
}
if (shift_amount != 0){
for (;pos < end_pos; pos += 1){
pos->pos += shift_amount;
}
}
}
//////////////////////////////////////
internal b32
buffer_good(Gap_Buffer *buffer){
return(buffer->data != 0);
}
internal i64
buffer_size(Gap_Buffer *buffer){
return(buffer->size1 + buffer->size2);
}
internal i64
buffer_line_count(Gap_Buffer *buffer){
return(buffer->line_start_count - 1);
}
internal void
buffer_init(Gap_Buffer *buffer, u8 *data, u64 size, Base_Allocator *allocator){
block_zero_struct(buffer);
buffer->allocator = allocator;
u64 capacity = round_up_u64(size*2, KB(4));
String_Const_u8 memory = base_allocate(allocator, capacity);
buffer->data = (u8*)memory.str;
buffer->size1 = size/2;
buffer->gap_size = capacity - size;
buffer->size2 = size - buffer->size1;
buffer->max = capacity;
block_copy(buffer->data, data, buffer->size1);
block_copy(buffer->data + buffer->size1 + buffer->gap_size, data + buffer->size1, buffer->size2);
}
internal b32
buffer_replace_range(Gap_Buffer *buffer, Range_i64 range, String_Const_u8 text, i64 shift_amount){
i64 size = buffer_size(buffer);
Assert(0 <= range.start);
Assert(range.start <= range.end);
Assert(range.end <= size);
if (shift_amount + size > buffer->max){
i64 new_max = round_up_i64(2*(shift_amount + size), KB(4));
i64 new_gap_size = new_max - size;
String_Const_u8 new_memory_data = base_allocate(buffer->allocator, new_max);
u8 *new_memory = (u8*)new_memory_data.str;
block_copy(new_memory, buffer->data, buffer->size1);
block_copy(new_memory + buffer->size1 + new_gap_size, buffer->data + buffer->size1 + buffer->gap_size,
buffer->size2);
base_free(buffer->allocator, buffer->data);
buffer->data = new_memory;
buffer->gap_size = new_gap_size;
buffer->max = new_max;
}
Assert(shift_amount + size <= buffer->max);
b32 result = false;
if (range.end < buffer->size1){
i64 move_size = buffer->size1 - range.end;
block_copy(buffer->data + buffer->size1 + buffer->gap_size - move_size,
buffer->data + range.end,
move_size);
buffer->size1 -= move_size;
buffer->size2 += move_size;
}
if (range.start > buffer->size1){
i64 move_size = range.start - buffer->size1;
block_copy(buffer->data + buffer->size1,
buffer->data + buffer->size1 + buffer->gap_size,
move_size);
buffer->size1 += move_size;
buffer->size2 -= move_size;
}
block_copy(buffer->data + range.start, text.str, text.size);
buffer->size2 = size - range.end;
buffer->size1 = range.start + text.size;
buffer->gap_size -= shift_amount;
Assert(buffer->size1 + buffer->size2 == size + shift_amount);
Assert(buffer->size1 + buffer->gap_size + buffer->size2 == buffer->max);
return(result);
}
////////////////////////////////
internal List_String_Const_u8
buffer_get_chunks(Arena *arena, Gap_Buffer *buffer){
List_String_Const_u8 list = {};
if (buffer->size1 > 0){
string_list_push(arena, &list, SCu8(buffer->data, buffer->size1));
}
if (buffer->size2 > 0){
u64 gap_2_pos = buffer->size1 + buffer->gap_size;
string_list_push(arena, &list, SCu8(buffer->data + gap_2_pos, buffer->size2));
}
return(list);
}
internal void
buffer_chunks_clamp(List_String_Const_u8 *chunks, Range_i64 range){
i64 p = 0;
List_String_Const_u8 list = {};
for (Node_String_Const_u8 *node = chunks->first, *next = 0;
node != 0;
node = next){
next = node->next;
Range_i64 node_range = Ii64(p, p + node->string.size);
if (range_overlap(range, node_range)){
i64 first = Max(node_range.first, range.first) - node_range.first;
i64 one_past_last = Min(node_range.one_past_last, range.one_past_last) - node_range.first;
String_Const_u8 s = string_prefix(node->string, one_past_last);
node->string = string_skip(s, first);
sll_queue_push(list.first, list.last, node);
list.total_size += node->string.size;
list.node_count += 1;
}
p = node_range.one_past_last;
}
*chunks = list;
}
internal String_Const_u8
buffer_stringify(Arena *arena, Gap_Buffer *buffer, Range_i64 range){
List_String_Const_u8 list = buffer_get_chunks(arena, buffer);
buffer_chunks_clamp(&list, range);
return(string_list_flatten(arena, list, StringFill_NullTerminate));
}
internal String_Const_u8
buffer_eol_convert_out(Arena *arena, Gap_Buffer *buffer, Range_i64 range){
List_String_Const_u8 list = buffer_get_chunks(arena, buffer);
buffer_chunks_clamp(&list, range);
u64 cap = list.total_size*2;
u8 *memory = push_array(arena, u8, cap);
u8 *memory_opl = memory + cap;
u8 *ptr = memory;
for (Node_String_Const_u8 *node = list.first;
node != 0;
node = node->next){
u8 *byte = node->string.str;
u8 *byte_opl = byte + node->string.size;
for (;byte < byte_opl; byte += 1){
if (*byte == '\n'){
*ptr = '\r';
ptr += 1;
*ptr = '\n';
ptr += 1;
}
else{
*ptr = *byte;
ptr += 1;
}
}
}
linalloc_pop(arena, (memory_opl - ptr));
push_align(arena, 8);
return(SCu8(memory, ptr));
}
#if 0
internal i64
buffer_count_newlines(Arena *scratch, Gap_Buffer *buffer, i64 start, i64 end){
Temp_Memory temp = begin_temp(scratch);
List_String_Const_u8 list = buffer_get_chunks(scratch, buffer);
buffer_chunks_clamp(&list, Ii64(start, end));
i64 count = 0;
for (Node_String_Const_u8 *node = list.first;
node != 0;
node = node->next){
u8 *byte = node->string.str;
u8 *byte_opl = byte + node->string.size;
for (;byte < byte_opl; byte += 1){
if (*byte == '\n'){
count += 1;
}
}
}
end_temp(temp);
return(count);
}
#endif
internal void
buffer_starts__ensure_max_size(Gap_Buffer *buffer, i64 max_size){
if (max_size > buffer->line_start_max){
i64 new_max = round_up_i64(max_size*2, KB(1));
String_Const_u8 memory = base_allocate(buffer->allocator, sizeof(*buffer->line_starts)*new_max);
i64 *new_line_starts = (i64*)memory.str;
block_copy_dynamic_array(new_line_starts, buffer->line_starts, buffer->line_start_count);
buffer->line_start_max = new_max;
base_free(buffer->allocator, buffer->line_starts);
buffer->line_starts = new_line_starts;
}
}
internal void
buffer_measure_starts__write(Gap_Buffer *buffer, i64 pos){
buffer_starts__ensure_max_size(buffer, buffer->line_start_count + 1);
buffer->line_starts[buffer->line_start_count] = pos;
buffer->line_start_count += 1;
}
internal void
buffer_measure_starts(Arena *scratch, Gap_Buffer *buffer){
Temp_Memory temp = begin_temp(scratch);
List_String_Const_u8 list = buffer_get_chunks(scratch, buffer);
buffer->line_start_count = 0;
buffer_measure_starts__write(buffer, 0);
i64 index = 0;
for (Node_String_Const_u8 *node = list.first;
node != 0;
node = node->next){
u8 *byte = node->string.str;
u8 *byte_opl = byte + node->string.size;
for (;byte < byte_opl; byte += 1){
index += 1;
if (*byte == '\n'){
buffer_measure_starts__write(buffer, index);
}
}
}
buffer_measure_starts__write(buffer, buffer_size(buffer));
end_temp(temp);
}
internal i64
buffer_get_line_index(Gap_Buffer *buffer, i64 pos){
i64 i = 0;
if (buffer->line_start_count > 2){
i64 start = 0;
i64 one_past_last = buffer->line_start_count - 1;
i64 *array = buffer->line_starts;
pos = clamp_bot(0, pos);
for (;;){
i = (start + one_past_last) >> 1;
if (array[i] < pos){
start = i;
}
else if (array[i] > pos){
one_past_last = i;
}
else{
break;
}
if (start + 1 >= one_past_last){
i = start;
break;
}
}
}
return(i);
}
Line_Move*
push_line_move(Arena *arena, Line_Move *moves, i64 new_line_first,
i64 old_line_first, i64 old_line_opl, i64 text_shift){
Line_Move *move = push_array(arena, Line_Move, 1);
move->next = moves;
move->kind = LineMove_ShiftOldValues;
move->new_line_first = new_line_first;
move->old_line_first = old_line_first;
move->old_line_opl = old_line_opl;
move->text_shift = text_shift;
return(move);
}
Line_Move*
push_line_move(Arena *arena, Line_Move *moves, i64 new_line_first,
String_Const_u8 string, i64 text_base){
Line_Move *move = push_array(arena, Line_Move, 1);
move->next = moves;
move->kind = LineMove_MeasureString;
move->new_line_first = new_line_first;
move->string = string;
move->text_base = text_base;
return(move);
}
function i64
count_lines(String_Const_u8 string){
i64 result = 0;
for (u64 i = 0; i < string.size; i += 1){
if (string.str[i] == '\n'){
result += 1;
}
}
return(result);
}
function void
fill_line_starts(i64 *lines_starts, String_Const_u8 string, i64 text_base){
i64 *ptr = lines_starts;
for (u64 i = 0; i < string.size; i += 1){
if (string.str[i] == '\n'){
*ptr = text_base + i + 1;
ptr += 1;
}
}
}
function void
buffer_remeasure_starts(Thread_Context *tctx, Gap_Buffer *buffer, Batch_Edit *batch){
Scratch_Block scratch(tctx);
i64 line_start_count = buffer_line_count(buffer) + 1;
Line_Move *moves = 0;
i64 current_line = 0;
i64 text_shift = 0;
i64 line_shift = 0;
for (Batch_Edit *node = batch;
node != 0;
node = node->next){
i64 first_line = buffer_get_line_index(buffer, node->edit.range.first);
i64 opl_line = buffer_get_line_index(buffer, node->edit.range.one_past_last);
i64 new_line_count = count_lines(node->edit.text);
i64 deleted_line_count = opl_line - first_line;
Assert(first_line <= opl_line);
Assert(opl_line <= line_start_count);
if (current_line <= first_line &&
(text_shift != 0 || line_shift != 0)){
moves = push_line_move(scratch, moves, current_line + line_shift,
current_line, first_line + 1, text_shift);
}
if (new_line_count != 0){
moves = push_line_move(scratch, moves, first_line + 1 + line_shift,
node->edit.text, node->edit.range.first + text_shift);
}
text_shift += node->edit.text.size - range_size(node->edit.range);
line_shift += new_line_count - deleted_line_count;
current_line = opl_line + 1;
}
moves = push_line_move(scratch, moves, current_line + line_shift,
current_line, line_start_count, text_shift);
line_start_count = line_start_count + line_shift;
buffer_starts__ensure_max_size(buffer, line_start_count + 1);
buffer->line_start_count = line_start_count;
i64 *array = buffer->line_starts;
for (Line_Move *node = moves;
node != 0;
node = node->next){
if (node->kind == LineMove_ShiftOldValues){
i64 line_index_shift = node->new_line_first - node->old_line_first;
i64 move_text_shift = node->text_shift;
if (line_index_shift > 0){
for (i64 i = node->old_line_opl - 1;
i >= node->old_line_first;
i -= 1){
array[i + line_index_shift] = array[i] + move_text_shift;
}
}
else{
for (i64 i = node->old_line_first;
i < node->old_line_opl;
i += 1){
array[i + line_index_shift] = array[i] + move_text_shift;
}
}
}
}
for (Line_Move *node = moves;
node != 0;
node = node->next){
if (node->kind == LineMove_MeasureString){
fill_line_starts(array + node->new_line_first, node->string, node->text_base);
}
}
}
internal Range_i64
buffer_get_pos_range_from_line_number(Gap_Buffer *buffer, i64 line_number){
Range_i64 result = {};
if (1 <= line_number && line_number < buffer->line_start_count){
result.first = buffer->line_starts[line_number - 1];
result.one_past_last = buffer->line_starts[line_number];
}
return(result);
}
internal i64
buffer_get_first_pos_from_line_number(Gap_Buffer *buffer, i64 line_number){
i64 result = 0;
if (line_number < 1){
result = 0;
}
else if (line_number >= buffer->line_start_count){
result = buffer_size(buffer);
}
else{
result = buffer->line_starts[line_number - 1];
}
return(result);
}
internal i64
buffer_get_last_pos_from_line_number(Gap_Buffer *buffer, i64 line_number){
i64 result = 0;
if (line_number < 1){
result = 0;
}
else if (line_number >= buffer->line_start_count - 1){
result = buffer_size(buffer);
}
else{
result = buffer->line_starts[line_number] - 1;
}
return(result);
}
internal Buffer_Cursor
buffer_cursor_from_pos(Gap_Buffer *buffer, i64 pos){
i64 size = buffer_size(buffer);
pos = clamp(0, pos, size);
i64 line_index = buffer_get_line_index(buffer, pos);
Buffer_Cursor result = {};
result.pos = pos;
result.line = line_index + 1;
result.col = pos - buffer->line_starts[line_index] + 1;
return(result);
}
internal Buffer_Cursor
buffer_cursor_from_line_col(Gap_Buffer *buffer, i64 line, i64 col){
i64 size = buffer_size(buffer);
i64 line_index = line - 1;
i64 line_count = buffer_line_count(buffer);
line_index = clamp(0, line_index, line_count - 1);
i64 this_start = buffer->line_starts[line_index];
i64 max_col = (buffer->line_starts[line_index + 1] - this_start);
if (line_index + 1 == line_count){
max_col += 1;
}
max_col = clamp_bot(1, max_col);
if (col < 0){
if (-col > max_col){
col = 1;
}
else{
col = max_col + col + 1;
}
}
else if (col == 0){
col = 1;
}
else{
col = clamp_top(col, max_col);
}
Assert(col > 0);
i64 adjusted_pos = col - 1;
i64 pos = this_start + adjusted_pos;
Buffer_Cursor result = {};
result.pos = pos;
result.line = line_index + 1;
result.col = col;
return(result);
}
internal String_Const_u8
buffer_invert_edit_shift(Arena *arena, Gap_Buffer *buffer, Edit edit, Edit *inv, i64 shift_amount){
String_Const_u8 string = buffer_stringify(arena, buffer, edit.range);
inv->text = string;
inv->range = Ii64(edit.range.start + shift_amount, edit.range.start + edit.text.size + shift_amount);
return(string);
}
internal b32
buffer_invert_batch(Arena *arena, Gap_Buffer *buffer, Edit *edits, Edit *inverse, i64 count){
b32 result = false;
i64 pos = 0;
i64 shift_amount = 0;
Edit *edit = edits;
Edit *inv_edit = inverse;
for (i64 i = 0; i < count; i += 1, edit += 1, inv_edit += 1){
String_Const_u8 inv_str = buffer_invert_edit_shift(arena, buffer, *edit, inv_edit, shift_amount);
shift_amount += replace_range_shift(edit->range, edit->text.size);
pos += inv_str.size;
}
return(result);
}
internal Buffer_Chunk_Position
buffer_get_chunk_position(String_Const_u8_Array chunks, i64 buffer_size, i64 real_pos){
Buffer_Chunk_Position pos = {};
pos.real_pos = real_pos;
pos.chunk_pos = real_pos;
if (pos.real_pos != buffer_size){
for (;(i64)(chunks.vals[pos.chunk_index].size) <= pos.chunk_pos;){
Assert(pos.chunk_index < chunks.count);
pos.chunk_pos -= (i32)chunks.vals[pos.chunk_index].size;
pos.chunk_index += 1;
}
}
else{
pos.chunk_index = chunks.count - 1;
pos.chunk_pos = (i32)chunks.vals[pos.chunk_index].size;
}
return(pos);
}
internal i32
buffer_chunk_position_iterate(String_Const_u8_Array chunks, Buffer_Chunk_Position *pos, Scan_Direction direction){
i32 past_end = 0;
pos->real_pos += direction;
pos->chunk_pos += direction;
if (pos->chunk_pos < 0){
if (pos->chunk_index == 0){
past_end = -1;
}
else{
pos->chunk_index -= 1;
pos->chunk_pos = (i32)chunks.vals[pos->chunk_index].size - 1;
}
}
else if (pos->chunk_pos >= (i64)(chunks.vals[pos->chunk_index].size)){
pos->chunk_index += 1;
if (pos->chunk_index == chunks.count){
past_end = 1;
}
else{
pos->chunk_pos = 0;
}
}
return(past_end);
}
// BOTTOM

68
code/4ed_buffer.h Normal file
View File

@ -0,0 +1,68 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 24.01.2018
*
* Buffer types
*
*/
// TOP
#if !defined(FRED_BUFFER_H)
#define FRED_BUFFER_H
struct Cursor_With_Index{
i64 pos;
i32 index;
};
struct Gap_Buffer{
Base_Allocator *allocator;
u8 *data;
i64 size1;
i64 gap_size;
i64 size2;
i64 max;
// NOTE(allen): If there are N lines I store N + 1 slots in this array with
// line_starts[N] = size of the buffer.
// The variable line_start_count stores N + 1; call buffer_line_count(buffer)
// to get "N" the actual number of lines.
i64 *line_starts;
i64 line_start_count;
i64 line_start_max;
};
struct Buffer_Chunk_Position{
i64 real_pos;
i64 chunk_pos;
i64 chunk_index;
};
typedef i32 Line_Move_Kind;
enum{
LineMove_ShiftOldValues,
LineMove_MeasureString,
};
struct Line_Move{
Line_Move *next;
Line_Move_Kind kind;
i64 new_line_first;
union{
struct{
i64 old_line_first;
i64 old_line_opl;
i64 text_shift;
};
struct{
String_Const_u8 string;
i64 text_base;
};
};
};
#endif
// BOTTOM

37
code/4ed_buffer_model.h Normal file
View File

@ -0,0 +1,37 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 18.03.2017
*
* Abstract model for the describing the characters of a buffer.
*
*/
// TOP
#if !defined(FRED_BUFFER_MODEL_H)
#define FRED_BUFFER_MODEL_H
struct Buffer_Model_Step{
u32 type;
u32 value;
i32 i;
u32 byte_length;
};
struct Buffer_Model_Behavior{
b32 do_newline;
b32 do_codepoint_advance;
b32 do_number_advance;
};
enum{
BufferModelUnit_None,
BufferModelUnit_Codepoint,
BufferModelUnit_Numbers,
};
#endif
// BOTTOM

158
code/4ed_cli.cpp Normal file
View File

@ -0,0 +1,158 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 17.07.2017
*
* CLI handling code.
*
*/
// TOP
internal void
child_process_container_init(Base_Allocator *allocator, Child_Process_Container *container){
container->arena = make_arena(allocator);
dll_init_sentinel(&container->child_process_active_list);
dll_init_sentinel(&container->child_process_free_list);
container->active_child_process_count = 0;
container->child_process_id_counter = 0;
container->id_to_ptr_table = make_table_u64_u64(allocator, 10);
container->id_to_return_code_table = make_table_u64_u64(allocator, 10);
}
internal void
child_process_container_release(Child_Process_Container *container, Models *models){
linalloc_clear(&container->arena);
table_free(&container->id_to_ptr_table);
block_zero_struct(container);
}
internal Child_Process_And_ID
child_process_alloc_new(Models *models, Child_Process_Container *container){
Child_Process_And_ID result = {};
Child_Process *new_process = 0;
if (container->child_process_free_list.next != &container->child_process_free_list){
Node *new_node = container->child_process_free_list.next;
dll_remove(new_node);
new_process = CastFromMember(Child_Process, node, new_node);
}
else{
new_process = push_array(&container->arena, Child_Process, 1);
}
u32 new_id = ++container->child_process_id_counter;
block_zero_struct(new_process);
dll_insert_back(&container->child_process_active_list, &new_process->node);
new_process->id = new_id;
table_insert(&container->id_to_ptr_table, new_id, (u64)PtrAsInt(new_process));
container->active_child_process_count += 1;
result.process = new_process;
result.id = new_id;
return(result);
}
internal Child_Process*
child_process_from_id(Child_Process_Container *container, Child_Process_ID id){
Table_Lookup lookup = table_lookup(&container->id_to_ptr_table, id);
Child_Process *process = 0;
if (lookup.found_match){
u64 val = 0;
table_read(&container->id_to_ptr_table, lookup, &val);
process = (Child_Process*)IntAsPtr(val);
}
return(process);
}
internal b32
child_process_free(Child_Process_Container *container, Child_Process_ID id){
b32 result = false;
Child_Process *process = child_process_from_id(container, id);
if (process != 0){
table_erase(&container->id_to_ptr_table, id);
dll_remove(&process->node);
dll_insert(&container->child_process_free_list, &process->node);
container->active_child_process_count -= 1;
result = true;
}
return(result);
}
internal b32
child_process_set_return_code(Models *models, Child_Process_Container *container, Child_Process_ID id, i64 val){
table_insert(&container->id_to_return_code_table, id, val);
return(true);
}
internal b32
child_process_lookup_return_code(Child_Process_Container *container, Child_Process_ID id, i64 *out){
b32 result = false;
Table_Lookup lookup = table_lookup(&container->id_to_return_code_table, id);
if (lookup.found_match){
table_read(&container->id_to_return_code_table, lookup, (u64*)out);
result = true;
}
return(result);
}
////////////////////////////////
internal b32
child_process_call(Thread_Context *tctx, Models *models, String_Const_u8 path, String_Const_u8 command, Child_Process_ID *id_out){
b32 result = false;
Scratch_Block scratch(tctx);
String_Const_u8 path_n = push_string_copy(scratch, path);
String_Const_u8 command_n = push_string_copy(scratch, command);
CLI_Handles cli_handles = {};
if (system_cli_call(scratch, (char*)path_n.str, (char*)command_n.str, &cli_handles)){
Child_Process_And_ID new_process = child_process_alloc_new(models, &models->child_processes);
*id_out = new_process.id;
new_process.process->cli = cli_handles;
result = true;
}
return(result);
}
internal b32
child_process_set_target_buffer(Models *models, Child_Process *child_process, Editing_File *file, Child_Process_Set_Target_Flags flags){
b32 result = false;
b32 fail_if_process_has_buffer = HasFlag(flags, ChildProcessSet_FailIfProcessAlreadyAttachedToABuffer);
b32 fail_if_buffer_has_process = HasFlag(flags, ChildProcessSet_FailIfBufferAlreadyAttachedToAProcess);
b32 process_has_buffer = (child_process->out_file != 0);
b32 buffer_has_process = (file->state.attached_child_process != 0);
b32 fail = ((process_has_buffer && fail_if_process_has_buffer) ||
(buffer_has_process && fail_if_buffer_has_process));
if (!fail){
if (process_has_buffer){
child_process->out_file->state.attached_child_process = 0;
}
if (buffer_has_process){
Child_Process *attached_child_process = child_process_from_id(&models->child_processes, file->state.attached_child_process);
if (attached_child_process != 0){
attached_child_process->out_file = 0;
}
}
child_process->out_file = file;
child_process->cursor_at_end = HasFlag(flags, ChildProcessSet_CursorAtEnd);
file->state.attached_child_process = child_process->id;
result = true;
}
return(result);
}
internal Process_State
child_process_get_state(Child_Process_Container *child_processes, Child_Process_ID child_process_id){
Child_Process *child_process = child_process_from_id(child_processes, child_process_id);
Process_State result = {};
if (child_processes != 0){
result.valid = true;
result.is_updating = true;
}
else if (child_process_lookup_return_code(child_processes, child_process_id, &result.return_code)){
result.valid = true;
}
return(result);
}
// BOTTOM

41
code/4ed_cli.h Normal file
View File

@ -0,0 +1,41 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 24.03.2018
*
* CLI handling code.
*
*/
// TOP
#if !defined(FRED_CLI_H)
#define FRED_CLI_H
struct Child_Process{
Node node;
Child_Process_ID id;
CLI_Handles cli;
Editing_File *out_file;
b32 cursor_at_end;
};
struct Child_Process_Container{
Arena arena;
Node child_process_active_list;
Node child_process_free_list;
i32 active_child_process_count;
u32 child_process_id_counter;
Table_u64_u64 id_to_ptr_table;
Table_u64_u64 id_to_return_code_table;
};
struct Child_Process_And_ID{
Child_Process *process;
Child_Process_ID id;
};
#endif
// BOTTOM

164
code/4ed_coroutine.cpp Normal file
View File

@ -0,0 +1,164 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 19.07.2017
*
* Coroutine implementation from thread+mutex+cv
*
*/
// TOP
internal void
coroutine__pass_control(Coroutine *me, Coroutine *other,
Coroutine_State my_new_state, Coroutine_Pass_Control control){
Assert(me->state == CoroutineState_Active);
Assert(me->sys == other->sys);
me->state = my_new_state;
other->state = CoroutineState_Active;
me->sys->active = other;
system_condition_variable_signal(other->cv);
if (control == CoroutinePassControl_BlockMe){
for (;me->state != CoroutineState_Active;){
system_condition_variable_wait(me->cv, me->sys->lock);
}
}
}
internal void
coroutine_main(void *ptr){
Coroutine *me = (Coroutine*)ptr;
Thread_Context_Extra_Info tctx_info = {};
tctx_info.coroutine = me;
Thread_Context tctx_ = {};
thread_ctx_init(&tctx_, ThreadKind_MainCoroutine,
get_base_allocator_system(), get_base_allocator_system());
tctx_.user_data = &tctx_info;
me->tctx = &tctx_;
// NOTE(allen): Init handshake
Assert(me->state == CoroutineState_Dead);
system_mutex_acquire(me->sys->lock);
me->sys->did_init = true;
system_condition_variable_signal(me->sys->init_cv);
for (;;){
// NOTE(allen): Wait until someone wakes us up, then go into our procedure.
for (;me->state != CoroutineState_Active;){
system_condition_variable_wait(me->cv, me->sys->lock);
}
Assert(me->type != CoroutineType_Root);
Assert(me->yield_ctx != 0);
Assert(me->func != 0);
me->func(me);
// NOTE(allen): Wake up the caller and set this coroutine back to being dead.
Coroutine *other = me->yield_ctx;
Assert(other != 0);
Assert(other->state == CoroutineState_Waiting);
coroutine__pass_control(me, other, CoroutineState_Dead, CoroutinePassControl_ExitMe);
me->func = 0;
}
}
internal void
coroutine_sub_init(Coroutine *co, Coroutine_Group *sys){
block_zero_struct(co);
co->sys = sys;
co->state = CoroutineState_Dead;
co->type = CoroutineType_Sub;
co->cv = system_condition_variable_make();
sys->did_init = false;
co->thread = system_thread_launch(coroutine_main, co);
for (;!sys->did_init;){
system_condition_variable_wait(sys->init_cv, sys->lock);
}
}
internal void
coroutine_system_init(Coroutine_Group *sys){
sys->arena = make_arena_system();
Coroutine *root = &sys->root;
sys->lock = system_mutex_make();
sys->init_cv = system_condition_variable_make();
sys->active = root;
block_zero_struct(root);
root->sys = sys;
root->state = CoroutineState_Active;
root->type = CoroutineType_Root;
root->cv = system_condition_variable_make();
sys->unused = 0;
system_mutex_acquire(sys->lock);
}
internal Coroutine*
coroutine_system_alloc(Coroutine_Group *sys){
Coroutine *result = sys->unused;
if (result != 0){
sll_stack_pop(sys->unused);
}
else{
result = push_array(&sys->arena, Coroutine, 1);
coroutine_sub_init(result, sys);
}
result->next = 0;
return(result);
}
internal void
coroutine_system_free(Coroutine_Group *sys, Coroutine *coroutine){
sll_stack_push(sys->unused, coroutine);
}
////////////////////////////////
internal Coroutine*
coroutine_create(Coroutine_Group *coroutines, Coroutine_Function *func){
Coroutine *result = coroutine_system_alloc(coroutines);
Assert(result->state == CoroutineState_Dead);
result->func = func;
return(result);
}
internal Coroutine*
coroutine_run(Coroutine_Group *sys, Coroutine *other, void *in, void *out){
other->in = in;
other->out = out;
Coroutine *me = other->sys->active;
Assert(me != 0);
Assert(me->sys == other->sys);
Assert(other->state == CoroutineState_Dead || other->state == CoroutineState_Inactive);
other->yield_ctx = me;
coroutine__pass_control(me, other, CoroutineState_Waiting, CoroutinePassControl_BlockMe);
Assert(me == other->sys->active);
Coroutine *result = other;
if (other->state == CoroutineState_Dead){
coroutine_system_free(sys, other);
result = 0;
}
return(result);
}
internal void
coroutine_yield(Coroutine *me){
Coroutine *other = me->yield_ctx;
Assert(other != 0);
Assert(me->sys == other->sys);
Assert(other->state == CoroutineState_Waiting);
coroutine__pass_control(me, other, CoroutineState_Inactive, CoroutinePassControl_BlockMe);
}
// BOTTOM

67
code/4ed_coroutine.h Normal file
View File

@ -0,0 +1,67 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 03.08.2019
*
* Coroutine implementation from thread+mutex+cv
*
*/
// TOP
#if !defined(FRED_COROUTINE_H)
#define FRED_COROUTINE_H
typedef void Coroutine_Function(struct Coroutine *head);
typedef u32 Coroutine_State;
enum{
CoroutineState_Dead,
CoroutineState_Active,
CoroutineState_Inactive,
CoroutineState_Waiting,
};
typedef u32 Coroutine_Type;
enum{
CoroutineType_Uninitialized,
CoroutineType_Root,
CoroutineType_Sub,
};
struct Coroutine{
Coroutine *next;
Thread_Context *tctx;
void *in;
void *out;
System_Thread thread;
System_Condition_Variable cv;
struct Coroutine_Group *sys;
Coroutine_Function *func;
Coroutine *yield_ctx;
Coroutine_State state;
Coroutine_Type type;
void *user_data;
};
struct Coroutine_Group{
Arena arena;
System_Mutex lock;
System_Condition_Variable init_cv;
b32 did_init;
Coroutine *active;
Coroutine *unused;
Coroutine root;
};
////////////////////////////////
typedef i32 Coroutine_Pass_Control;
enum{
CoroutinePassControl_ExitMe,
CoroutinePassControl_BlockMe,
};
#endif
// BOTTOM

29
code/4ed_cursor_codes.h Normal file
View File

@ -0,0 +1,29 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 07.11.2017
*
* Application Cursor Codes
*
*/
// TOP
#if !defined(FRED_CURSOR_CODES_H)
#define FRED_CURSOR_CODES_H
typedef i32 Application_Mouse_Cursor;
enum{
APP_MOUSE_CURSOR_DEFAULT,
APP_MOUSE_CURSOR_ARROW,
APP_MOUSE_CURSOR_IBEAM,
APP_MOUSE_CURSOR_LEFTRIGHT,
APP_MOUSE_CURSOR_UPDOWN,
// never below this
APP_MOUSE_CURSOR_COUNT
};
#endif
// BOTTOM

View File

@ -0,0 +1,531 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 22.06.2018
*
* Dynamic variable system
*
*/
// TOP
internal void
managed_ids_init(Base_Allocator *allocator, Managed_ID_Set *set){
set->arena = make_arena(allocator, KB(4), 8);
set->name_to_group_table = make_table_Data_u64(allocator, 20);
}
internal Managed_ID
managed_ids_group_highest_id(Managed_ID_Set *set, String_Const_u8 group_name){
Managed_ID result = 0;
String_Const_u8 data = make_data(group_name.str, group_name.size);
Table_Lookup lookup = table_lookup(&set->name_to_group_table, data);
if (lookup.found_match){
u64 val = 0;
table_read(&set->name_to_group_table, lookup, &val);
Managed_ID_Group *group = (Managed_ID_Group*)IntAsPtr(val);
result = group->id_counter - 1;
}
return(result);
}
internal Managed_ID
managed_ids_declare(Managed_ID_Set *set, String_Const_u8 group_name, String_Const_u8 name){
Managed_ID_Group *group = 0;
{
String_Const_u8 data = make_data(group_name.str, group_name.size);
Table_Lookup lookup = table_lookup(&set->name_to_group_table, data);
if (lookup.found_match){
u64 val = 0;
table_read(&set->name_to_group_table, lookup, &val);
group = (Managed_ID_Group*)IntAsPtr(val);
}
else{
group = push_array(&set->arena, Managed_ID_Group, 1);
group->id_counter = 1;
group->name_to_id_table = make_table_Data_u64(set->arena.base_allocator, 50);
data = push_data_copy(&set->arena, data);
table_insert(&set->name_to_group_table, data, PtrAsInt(group));
}
}
Managed_ID result = 0;
{
String_Const_u8 data = make_data(name.str, name.size);
Table_Lookup lookup = table_lookup(&group->name_to_id_table, data);
if (lookup.found_match){
table_read(&group->name_to_id_table, lookup, &result);
}
else{
result = group->id_counter;
group->id_counter += 1;
data = push_data_copy(&set->arena, data);
table_insert(&group->name_to_id_table, data, result);
}
}
return(result);
}
function Managed_ID
managed_ids_get(Managed_ID_Set *set, String_Const_u8 group_name, String_Const_u8 name){
Managed_ID_Group *group = 0;
{
String_Const_u8 data = make_data(group_name.str, group_name.size);
Table_Lookup lookup = table_lookup(&set->name_to_group_table, data);
if (lookup.found_match){
u64 val = 0;
table_read(&set->name_to_group_table, lookup, &val);
group = (Managed_ID_Group*)IntAsPtr(val);
}
}
Managed_ID result = 0;
if (group != 0){
String_Const_u8 data = make_data(name.str, name.size);
Table_Lookup lookup = table_lookup(&group->name_to_id_table, data);
if (lookup.found_match){
table_read(&group->name_to_id_table, lookup, &result);
}
}
return(result);
}
////////////////////////////////
internal void
dynamic_variable_block_init(Base_Allocator *allocator, Dynamic_Variable_Block *block){
block->arena = make_arena(allocator, KB(4), 8);
block->id_to_data_table = make_table_u64_Data(allocator, 20);
}
internal String_Const_u8
dynamic_variable_get(Dynamic_Variable_Block *block, Managed_ID id, u64 size){
String_Const_u8 result = {};
Table_Lookup lookup = table_lookup(&block->id_to_data_table, id);
if (lookup.found_match){
table_read(&block->id_to_data_table, lookup, &result);
}
else{
result = push_data(&block->arena, size);
block_zero(result);
table_insert(&block->id_to_data_table, id, result);
}
return(result);
}
internal void
dynamic_variable_erase(Dynamic_Variable_Block *block, Managed_ID id){
table_erase(&block->id_to_data_table, id);
}
////////////////////////////////
internal void
lifetime_allocator_init(Base_Allocator *base_allocator, Lifetime_Allocator *lifetime_allocator){
block_zero_struct(lifetime_allocator);
lifetime_allocator->allocator = base_allocator;
lifetime_allocator->node_arena = make_arena(base_allocator, KB(4));
lifetime_allocator->key_table = make_table_Data_u64(base_allocator, 100);
lifetime_allocator->key_check_table = make_table_u64_u64(base_allocator, 100);
lifetime_allocator->scope_id_to_scope_ptr_table = make_table_u64_u64(base_allocator, 100);
}
////////////////////////////////
internal void
dynamic_workspace_init(Lifetime_Allocator *lifetime_allocator, i32 user_type, void *user_back_ptr, Dynamic_Workspace *workspace){
block_zero_struct(workspace);
heap_init(&workspace->heap, lifetime_allocator->allocator);
workspace->heap_wrapper = base_allocator_on_heap(&workspace->heap);
workspace->object_id_to_object_ptr = make_table_u64_u64(&workspace->heap_wrapper, 10);
dynamic_variable_block_init(&workspace->heap_wrapper, &workspace->var_block);
if (lifetime_allocator->scope_id_counter == 0){
lifetime_allocator->scope_id_counter = 1;
}
workspace->scope_id = lifetime_allocator->scope_id_counter++;
table_insert(&lifetime_allocator->scope_id_to_scope_ptr_table,
workspace->scope_id, (u64)PtrAsInt(workspace));
workspace->user_type = user_type;
workspace->user_back_ptr = user_back_ptr;
}
internal void
dynamic_workspace_free(Lifetime_Allocator *lifetime_allocator, Dynamic_Workspace *workspace){
table_erase(&lifetime_allocator->scope_id_to_scope_ptr_table, workspace->scope_id);
heap_free_all(&workspace->heap);
}
internal void
dynamic_workspace_clear_contents(Dynamic_Workspace *workspace){
Base_Allocator *base_allocator = heap_get_base_allocator(&workspace->heap);
heap_free_all(&workspace->heap);
heap_init(&workspace->heap, base_allocator);
workspace->heap_wrapper = base_allocator_on_heap(&workspace->heap);
workspace->object_id_to_object_ptr = make_table_u64_u64(&workspace->heap_wrapper, 10);
dynamic_variable_block_init(&workspace->heap_wrapper, &workspace->var_block);
block_zero_struct(&workspace->buffer_markers_list);
workspace->total_marker_count = 0;
}
internal u32
dynamic_workspace_store_pointer(Dynamic_Workspace *workspace, void *ptr){
if (workspace->object_id_counter == 0){
workspace->object_id_counter = 1;
}
u32 id = workspace->object_id_counter++;
table_insert(&workspace->object_id_to_object_ptr, id, (u64)PtrAsInt(ptr));
return(id);
}
internal void
dynamic_workspace_erase_pointer(Dynamic_Workspace *workspace, u32 id){
table_erase(&workspace->object_id_to_object_ptr, id);
}
internal void*
dynamic_workspace_get_pointer(Dynamic_Workspace *workspace, u32 id){
void *result = 0;
Table_Lookup lookup = table_lookup(&workspace->object_id_to_object_ptr, id);
if (lookup.found_match){
u64 val = 0;
table_read(&workspace->object_id_to_object_ptr, lookup, &val);
result = IntAsPtr(val);
}
return(result);
}
////////////////////////////////
internal String_Const_u8
lifetime__key_as_data(Lifetime_Object **members, i32 count){
return(make_data(members, sizeof(*members)*count));
}
internal String_Const_u8
lifetime__key_as_data(Lifetime_Key *key){
return(lifetime__key_as_data(key->members, key->count));
}
internal void
lifetime__free_key(Lifetime_Allocator *lifetime_allocator, Lifetime_Key *key, Lifetime_Object *skip_object){
// Deinit
dynamic_workspace_free(lifetime_allocator, &key->dynamic_workspace);
// Remove From Objects
i32 count = key->count;
Lifetime_Object **object_ptr = key->members;
for (i32 i = 0; i < count; i += 1, object_ptr += 1){
if (*object_ptr == skip_object) continue;
Lifetime_Key_Ref_Node *delete_point_node = 0;
i32 delete_point_i = 0;
i32 key_i = 0;
Lifetime_Object *object = *object_ptr;
for (Lifetime_Key_Ref_Node *node = object->key_node_first;
node != 0;
node = node->next){
i32 one_past_last = clamp_top(ArrayCount(node->keys), object->key_count - key_i);
for (i32 j = 0; j < one_past_last; j += 1){
if (node->keys[j] == key){
delete_point_node = node;
delete_point_i = j;
goto double_break;
}
}
key_i += one_past_last;
}
double_break:;
Assert(delete_point_node != 0);
Lifetime_Key_Ref_Node *last_node = object->key_node_last;
Lifetime_Key *last_key = last_node->keys[(object->key_count - 1) % ArrayCount(last_node->keys)];
Assert(last_key != 0);
delete_point_node->keys[delete_point_i] = last_key;
object->key_count -= 1;
if ((object->key_count % lifetime_key_reference_per_node) == 0){
zdll_remove(object->key_node_first, object->key_node_last, last_node);
sll_stack_push(lifetime_allocator->free_key_references, last_node);
}
}
// Free
String_Const_u8 key_data = lifetime__key_as_data(key);
table_erase(&lifetime_allocator->key_table, key_data);
table_erase(&lifetime_allocator->key_check_table, (u64)PtrAsInt(key));
base_free(lifetime_allocator->allocator, key->members);
sll_stack_push(lifetime_allocator->free_keys, key);
}
internal Lifetime_Key_Ref_Node*
lifetime__alloc_key_reference_node(Lifetime_Allocator *lifetime_allocator){
Assert(lifetime_allocator != 0);
Lifetime_Key_Ref_Node *result = lifetime_allocator->free_key_references;
if (result == 0){
result = push_array(&lifetime_allocator->node_arena, Lifetime_Key_Ref_Node, 1);
}
else{
sll_stack_pop(lifetime_allocator->free_key_references);
}
return(result);
}
internal void
lifetime__object_add_key(Lifetime_Allocator *lifetime_allocator, Lifetime_Object *object, Lifetime_Key *key){
Lifetime_Key_Ref_Node *last_node = object->key_node_last;
b32 insert_on_new_node = false;
if (last_node == 0){
insert_on_new_node = true;
}
else{
i32 next_insert_slot = object->key_count%ArrayCount(last_node->keys);
if (next_insert_slot != 0){
last_node->keys[next_insert_slot] = key;
object->key_count += 1;
}
else{
insert_on_new_node = true;
}
}
if (insert_on_new_node){
Lifetime_Key_Ref_Node *new_node = lifetime__alloc_key_reference_node(lifetime_allocator);
zdll_push_back(object->key_node_first, object->key_node_last, new_node);
block_zero_struct(new_node->keys);
new_node->keys[0] = key;
object->key_count += 1;
}
}
internal Lifetime_Object*
lifetime_alloc_object(Lifetime_Allocator *lifetime_allocator, i32 user_type, void *user_back_ptr){
Lifetime_Object *object = lifetime_allocator->free_objects;
if (object == 0){
object = push_array(&lifetime_allocator->node_arena, Lifetime_Object, 1);
}
else{
sll_stack_pop(lifetime_allocator->free_objects);
}
block_zero_struct(object);
dynamic_workspace_init(lifetime_allocator, user_type, user_back_ptr, &object->workspace);
return(object);
}
internal void
lifetime__object_free_all_keys(Lifetime_Allocator *lifetime_allocator, Lifetime_Object *lifetime_object){
i32 key_i = 0;
for (Lifetime_Key_Ref_Node *node = lifetime_object->key_node_first;
node != 0;
node = node->next){
i32 one_past_last = clamp_top(ArrayCount(node->keys), lifetime_object->key_count - key_i);
for (i32 i = 0; i < one_past_last; i += 1){
lifetime__free_key(lifetime_allocator, node->keys[i], lifetime_object);
}
key_i += one_past_last;
}
if (lifetime_object->key_count > 0){
lifetime_object->key_node_last->next = lifetime_allocator->free_key_references;
lifetime_allocator->free_key_references = lifetime_object->key_node_first;
}
}
internal void
lifetime__object_clear_all_keys(Lifetime_Allocator *lifetime_allocator, Lifetime_Object *lifetime_object){
i32 key_i = 0;
for (Lifetime_Key_Ref_Node *node = lifetime_object->key_node_first;
node != 0;
node = node->next){
i32 one_past_last = clamp_top(ArrayCount(node->keys), lifetime_object->key_count - key_i);
Lifetime_Key **key_ptr = node->keys;
for (i32 i = 0; i < one_past_last; i += 1, key_ptr += 1){
dynamic_workspace_clear_contents(&(*key_ptr)->dynamic_workspace);
}
key_i += one_past_last;
}
}
internal void
lifetime_free_object(Lifetime_Allocator *lifetime_allocator, Lifetime_Object *lifetime_object){
lifetime__object_free_all_keys(lifetime_allocator, lifetime_object);
dynamic_workspace_free(lifetime_allocator, &lifetime_object->workspace);
sll_stack_push(lifetime_allocator->free_objects, lifetime_object);
}
internal void
lifetime_object_reset(Lifetime_Allocator *lifetime_allocator, Lifetime_Object *lifetime_object){
lifetime__object_clear_all_keys(lifetime_allocator, lifetime_object);
dynamic_workspace_clear_contents(&lifetime_object->workspace);
}
internal i32
lifetime_sort_object_set__part(Lifetime_Object **ptr_array, i32 first, i32 one_past_last){
i32 pivot_index = one_past_last - 1;
Lifetime_Object *pivot = ptr_array[pivot_index];
i32 j = first;
for (i32 i = first; i < pivot_index; i += 1){
Lifetime_Object *object = ptr_array[i];
if (object < pivot){
Swap(Lifetime_Object*, ptr_array[i], ptr_array[j]);
j += 1;
}
}
Swap(Lifetime_Object*, ptr_array[j], ptr_array[pivot_index]);
return(j);
}
internal void
lifetime_sort_object_set__quick(Lifetime_Object **ptr_array, i32 first, i32 one_past_last){
if (first + 1 < one_past_last){
i32 pivot = lifetime_sort_object_set__part(ptr_array, first, one_past_last);
lifetime_sort_object_set__quick(ptr_array, first, pivot);
lifetime_sort_object_set__quick(ptr_array, pivot + 1, one_past_last);
}
}
internal i32
lifetime_sort_and_dedup_object_set(Lifetime_Object **ptr_array, i32 count){
lifetime_sort_object_set__quick(ptr_array, 0, count);
Lifetime_Object **ptr_write = ptr_array + 1;
Lifetime_Object **ptr_read = ptr_array + 1;
for (i32 i = 1; i < count; i += 1, ptr_read += 1){
if (ptr_read[-1] < ptr_read[0]){
*ptr_write = *ptr_read;
ptr_write += 1;
}
}
return((i32)(ptr_write - ptr_array));
}
internal Lifetime_Key*
lifetime_get_or_create_intersection_key(Lifetime_Allocator *lifetime_allocator, Lifetime_Object **object_ptr_array, i32 count){
{
String_Const_u8 key_data = lifetime__key_as_data(object_ptr_array, count);
Table_Lookup lookup = table_lookup(&lifetime_allocator->key_table, key_data);
if (lookup.found_match){
u64 val = 0;
table_read(&lifetime_allocator->key_table, lookup, &val);
return((Lifetime_Key*)IntAsPtr(val));
}
}
// Allocate
Lifetime_Key *new_key = lifetime_allocator->free_keys;
if (new_key == 0){
new_key = push_array(&lifetime_allocator->node_arena, Lifetime_Key, 1);
}
else{
sll_stack_pop(lifetime_allocator->free_keys);
}
block_zero_struct(new_key);
// Add to Objects
Lifetime_Object **object_ptr = object_ptr_array;
for (i32 i = 0; i < count; i += 1, object_ptr += 1){
Lifetime_Object *object = *object_ptr;
lifetime__object_add_key(lifetime_allocator, object, new_key);
}
// Initialize
u64 new_memory_size = sizeof(Lifetime_Object*)*count;
String_Const_u8 new_memory = base_allocate(lifetime_allocator->allocator, new_memory_size);
new_key->members = (Lifetime_Object**)new_memory.str;
block_copy_dynamic_array(new_key->members, object_ptr_array, count);
new_key->count = count;
dynamic_workspace_init(lifetime_allocator,
DynamicWorkspace_Intersected, new_key,
&new_key->dynamic_workspace);
{
String_Const_u8 key_data = lifetime__key_as_data(new_key);
u64 new_key_val = (u64)PtrAsInt(new_key);
table_insert(&lifetime_allocator->key_table, key_data, new_key_val);
table_insert(&lifetime_allocator->key_check_table, new_key_val, new_key_val);
}
return(new_key);
}
internal b32
lifetime_key_check(Lifetime_Allocator *lifetime_allocator, Lifetime_Key *key){
Table_Lookup lookup = table_lookup(&lifetime_allocator->key_check_table, (u64)PtrAsInt(key));
return(lookup.found_match);
}
////////////////////////////////
// TODO(allen): move this shit somewhere real, clean up all object creation functions to be more cleanly layered.
internal u8*
get_dynamic_object_memory_ptr(Managed_Object_Standard_Header *header){
u8 *ptr = 0;
if (header != 0){
switch (header->type){
case ManagedObjectType_Memory:
case ManagedObjectType_Markers:
{
ptr = ((u8*)header) + managed_header_type_sizes[header->type];
}break;
}
}
return(ptr);
}
internal Managed_Object
managed_object_alloc_managed_memory(Dynamic_Workspace *workspace, i32 item_size, i32 count, void **ptr_out){
i32 size = item_size*count;
String_Const_u8 new_memory = base_allocate(&workspace->heap_wrapper, sizeof(Managed_Memory_Header) + size);
void *ptr = new_memory.str;
Managed_Memory_Header *header = (Managed_Memory_Header*)ptr;
header->std_header.type = ManagedObjectType_Memory;
header->std_header.item_size = item_size;
header->std_header.count = count;
if (ptr_out != 0){
*ptr_out = get_dynamic_object_memory_ptr(&header->std_header);
}
u32 id = dynamic_workspace_store_pointer(workspace, ptr);
return(((u64)workspace->scope_id << 32) | (u64)id);
}
internal Managed_Object
managed_object_alloc_buffer_markers(Dynamic_Workspace *workspace, Buffer_ID buffer_id, i32 count, Marker **markers_out){
i32 size = count*sizeof(Marker);
String_Const_u8 new_memory = base_allocate(&workspace->heap_wrapper, size + sizeof(Managed_Buffer_Markers_Header));
void *ptr = new_memory.str;
Managed_Buffer_Markers_Header *header = (Managed_Buffer_Markers_Header*)ptr;
header->std_header.type = ManagedObjectType_Markers;
header->std_header.item_size = sizeof(Marker);
header->std_header.count = count;
zdll_push_back(workspace->buffer_markers_list.first, workspace->buffer_markers_list.last, header);
workspace->buffer_markers_list.count += 1;
workspace->total_marker_count += count;
header->buffer_id = buffer_id;
if (markers_out != 0){
*markers_out = (Marker*)get_dynamic_object_memory_ptr(&header->std_header);
}
u32 id = dynamic_workspace_store_pointer(workspace, ptr);
return(((u64)workspace->scope_id << 32) | (u64)id);
}
internal b32
managed_object_free(Dynamic_Workspace *workspace, Managed_Object object){
b32 result = false;
u32 lo_id = object&max_u32;
u8 *object_ptr = (u8*)dynamic_workspace_get_pointer(workspace, lo_id);
if (object_ptr != 0){
Managed_Object_Type *type = (Managed_Object_Type*)object_ptr;
switch (*type){
case ManagedObjectType_Markers:
{
Managed_Buffer_Markers_Header *header = (Managed_Buffer_Markers_Header*)object_ptr;
workspace->total_marker_count -= header->std_header.count;
zdll_remove(workspace->buffer_markers_list.first, workspace->buffer_markers_list.last, header);
workspace->buffer_markers_list.count -= 1;
}break;
}
dynamic_workspace_erase_pointer(workspace, lo_id);
base_free(&workspace->heap_wrapper, object_ptr);
result = true;
}
return(result);
}
// BOTTOM

View File

@ -0,0 +1,159 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 22.06.2018
*
* Dynamic variable system
*
*/
// TOP
#if !defined(FRED_DYNAMIC_VARIABLES_H)
#define FRED_DYNAMIC_VARIABLES_H
union Managed_Object_Standard_Header{
u64 eight_byte_alignment__;
struct{
Managed_Object_Type type;
u32 item_size;
u32 count;
};
};
struct Managed_Memory_Header{
Managed_Object_Standard_Header std_header;
};
struct Managed_Buffer_Markers_Header{
Managed_Object_Standard_Header std_header;
Managed_Buffer_Markers_Header *next;
Managed_Buffer_Markers_Header *prev;
Buffer_ID buffer_id;
};
struct Managed_Arena_Header{
Managed_Object_Standard_Header std_header;
Managed_Arena_Header *next;
Managed_Arena_Header *prev;
Arena arena;
};
global_const i32 managed_header_type_sizes[ManagedObjectType_COUNT] = {
0,
sizeof(Managed_Memory_Header),
sizeof(Managed_Buffer_Markers_Header),
sizeof(Managed_Arena_Header),
};
struct Managed_Buffer_Markers_Header_List{
Managed_Buffer_Markers_Header *first;
Managed_Buffer_Markers_Header *last;
i32 count;
};
struct Managed_Arena_Header_List{
Managed_Arena_Header *first;
Managed_Arena_Header *last;
i32 count;
};
////////////////////////////////
struct Managed_ID_Group{
Table_Data_u64 name_to_id_table;
Managed_ID id_counter;
};
struct Managed_ID_Set{
Arena arena;
Table_Data_u64 name_to_group_table;
};
struct Dynamic_Variable_Block{
Arena arena;
Table_u64_Data id_to_data_table;
};
////////////////////////////////
struct Dynamic_Workspace{
Dynamic_Variable_Block var_block;
Heap heap;
Base_Allocator heap_wrapper;
Table_u64_u64 object_id_to_object_ptr;
u32 object_id_counter;
u32 visual_id_counter;
u32 scope_id;
i32 user_type;
void *user_back_ptr;
Managed_Buffer_Markers_Header_List buffer_markers_list;
Managed_Arena_Header_List arena_list;
i32 total_marker_count;
};
////////////////////////////////
global_const i32 lifetime_key_reference_per_node = 32;
struct Lifetime_Key_Ref_Node{
Lifetime_Key_Ref_Node *next;
Lifetime_Key_Ref_Node *prev;
struct Lifetime_Key *keys[lifetime_key_reference_per_node];
};
union Lifetime_Object{
Lifetime_Object *next;
struct{
Lifetime_Key_Ref_Node *key_node_first;
Lifetime_Key_Ref_Node *key_node_last;
i32 key_count;
Dynamic_Workspace workspace;
};
};
struct Lifetime_Key{
union{
struct{
Lifetime_Key *next;
Lifetime_Key *prev;
};
struct{
Lifetime_Object **members;
i32 count;
Dynamic_Workspace dynamic_workspace;
};
};
};
global_const u64 LifetimeKeyHash_Empty = 0&(~bit_63);
global_const u64 LifetimeKeyHash_Deleted = max_u64&(~bit_63);
struct Lifetime_Allocator{
Base_Allocator *allocator;
Arena node_arena;
Lifetime_Key_Ref_Node *free_key_references;
Lifetime_Key* free_keys;
Lifetime_Object *free_objects;
Table_Data_u64 key_table;
Table_u64_u64 key_check_table;
Table_u64_u64 scope_id_to_scope_ptr_table;
u32 scope_id_counter;
};
struct Lifetime_Key_With_Opaque_ID{
Lifetime_Key *key;
u64 opaque_id;
};
////////////////////////////////
struct Managed_Object_Ptr_And_Workspace{
Dynamic_Workspace *workspace;
Managed_Object_Standard_Header *header;
};
#endif
// BOTTOM

657
code/4ed_edit.cpp Normal file
View File

@ -0,0 +1,657 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 25.03.2018
*
* High level edit procedures
*
*/
// TOP
function void
pre_edit_state_change(Models *models, Editing_File *file){
file_add_dirty_flag(file, DirtyState_UnsavedChanges);
}
function void
pre_edit_history_prep(Editing_File *file, Edit_Behaviors behaviors){
if (!behaviors.do_not_post_to_history){
history_dump_records_after_index(&file->state.history,
file->state.current_record_index);
}
}
function void
post_edit_call_hook(Thread_Context *tctx, Models *models, Editing_File *file,
Range_i64 new_range, Range_Cursor old_cursor_range){
// NOTE(allen): edit range hook
if (models->buffer_edit_range != 0){
Application_Links app = {};
app.tctx = tctx;
app.cmd_context = models;
models->buffer_edit_range(&app, file->id, new_range, old_cursor_range);
}
}
function void
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){
for (Managed_Buffer_Markers_Header *node = workspace->buffer_markers_list.first;
node != 0;
node = node->next){
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);
}
}
}
}
function void
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){
for (Managed_Buffer_Markers_Header *node = workspace->buffer_markers_list.first;
node != 0;
node = node->next){
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;
}
}
}
}
function f32
edit_fix_markers__compute_scroll_y(i32 line_height, f32 old_y_val, f32 new_y_val_aligned){
f32 y_offset = mod_f32(old_y_val, line_height);
f32 y_position = new_y_val_aligned + y_offset;
return(y_position);
}
function 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));
}
function void
edit_fix_markers(Thread_Context *tctx, Models *models, Editing_File *file, Batch_Edit *batch){
Layout *layout = &models->layout;
Lifetime_Object *file_lifetime_object = file->lifetime_object;
Buffer_ID file_id = file->id;
Assert(file_lifetime_object != 0);
i32 cursor_max = layout_get_open_panel_count(layout)*4;
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;
Scratch_Block scratch(tctx);
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);
i32 cursor_count = 0;
i32 r_cursor_count = 0;
Assert(cursors != 0);
Assert(r_cursors != 0);
for (Panel *panel = layout_get_first_open_panel(layout);
panel != 0;
panel = layout_get_next_open_panel(layout, panel)){
View *view = panel->view;
if (view->file == file){
File_Edit_Positions edit_pos = view_get_edit_pos(view);
write_cursor_with_index(cursors, &cursor_count, (i32)edit_pos.cursor_pos);
write_cursor_with_index(cursors, &cursor_count, (i32)view->mark);
Buffer_Cursor pos_cursor = file_compute_cursor(file, seek_line_col(edit_pos.scroll.position.line_number, 1));
Buffer_Cursor targ_cursor = file_compute_cursor(file, seek_line_col(edit_pos.scroll.target.line_number, 1));
write_cursor_with_index(cursors, &cursor_count, pos_cursor.pos);
write_cursor_with_index(cursors, &cursor_count, targ_cursor.pos);
}
}
edit_fix_markers__write_workspace_markers(&file_lifetime_object->workspace, file_id, cursors, r_cursors, &cursor_count, &r_cursor_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];
edit_fix_markers__write_workspace_markers(&key->dynamic_workspace, file_id, cursors, r_cursors, &cursor_count, &r_cursor_count);
}
key_index += count;
}
}
buffer_remeasure_starts(tctx, &file->state.buffer, batch);
if (cursor_count > 0 || r_cursor_count > 0){
buffer_sort_cursors( cursors, cursor_count);
buffer_sort_cursors(r_cursors, r_cursor_count);
buffer_update_cursors_lean_l( cursors, cursor_count, batch);
buffer_update_cursors_lean_r(r_cursors, r_cursor_count, batch);
buffer_unsort_cursors( cursors, cursor_count);
buffer_unsort_cursors(r_cursors, r_cursor_count);
Face *face = file_get_face(models, file);
cursor_count = 0;
r_cursor_count = 0;
for (Panel *panel = layout_get_first_open_panel(layout);
panel != 0;
panel = layout_get_next_open_panel(layout, panel)){
View *view = panel->view;
if (view->file == file){
i64 cursor_pos = cursors[cursor_count++].pos;
view->mark = cursors[cursor_count++].pos;
File_Edit_Positions edit_pos = view_get_edit_pos(view);
i64 scroll_pos = cursors[cursor_count++].pos;
i64 scroll_targ = cursors[cursor_count++].pos;
Buffer_Cursor pos_cursor = file_compute_cursor(file, seek_pos(scroll_pos));
Buffer_Cursor targ_cursor = file_compute_cursor(file, seek_pos(scroll_targ));
edit_pos.scroll.position.line_number = pos_cursor.line;
edit_pos.scroll.target.line_number = targ_cursor.line;
view_set_cursor_and_scroll(tctx, models, view, cursor_pos, edit_pos.scroll);
}
}
edit_fix_markers__read_workspace_markers(&file_lifetime_object->workspace, file_id, cursors, r_cursors, &cursor_count, &r_cursor_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];
edit_fix_markers__read_workspace_markers(&key->dynamic_workspace, file_id, cursors, r_cursors, &cursor_count, &r_cursor_count);
}
key_index += count;
}
}
}
function void
file_end_file(Thread_Context *tctx, Models *models, Editing_File *file){
if (models->end_buffer != 0){
Application_Links app = {};
app.tctx = tctx;
app.cmd_context = models;
models->end_buffer(&app, file->id);
}
Lifetime_Allocator *lifetime_allocator = &models->lifetime_allocator;
lifetime_free_object(lifetime_allocator, file->lifetime_object);
file->lifetime_object = lifetime_alloc_object(lifetime_allocator, DynamicWorkspace_Buffer, file);
}
function void
edit__apply(Thread_Context *tctx, Models *models, Editing_File *file, Range_i64 range, String_Const_u8 string, Edit_Behaviors behaviors){
Edit edit = {};
edit.text = string;
edit.range = range;
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));
// NOTE(allen): history update
if (!behaviors.do_not_post_to_history){
ProfileTLBlock(tctx, &models->profile_list, "edit apply history");
history_record_edit(&models->global_history, &file->state.history, buffer,
behaviors.pos_before_edit, edit);
file->state.current_record_index =
history_get_record_count(&file->state.history);
}
{
ProfileTLBlock(tctx, &models->profile_list, "edit apply replace range");
i64 shift_amount = replace_range_shift(edit.range, (i64)edit.text.size);
buffer_replace_range(buffer, edit.range, edit.text, shift_amount);
}
}
function void
edit_single(Thread_Context *tctx, Models *models, Editing_File *file,
Range_i64 range, String_Const_u8 string, Edit_Behaviors behaviors){
Range_Cursor cursor_range = {};
cursor_range.min = file_compute_cursor(file, seek_pos(range.min));
cursor_range.max = file_compute_cursor(file, seek_pos(range.max));
pre_edit_state_change(models, file);
pre_edit_history_prep(file, behaviors);
edit__apply(tctx, models, file, range, string, behaviors);
file_clear_layout_cache(file);
Batch_Edit batch = {};
batch.edit.text = string;
batch.edit.range = range;
edit_fix_markers(tctx, models, file, &batch);
post_edit_call_hook(tctx, models, file, Ii64_size(range.first, string.size), cursor_range);
}
function void
edit__apply_record_forward(Thread_Context *tctx, 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!
switch (record->kind){
case RecordKind_Single:
{
String_Const_u8 str = record->single.forward_text;
Range_i64 range = Ii64(record->single.first, record->single.first + record->single.backward_text.size);
edit_single(tctx, models, file, range, str, behaviors_prototype);
}break;
case RecordKind_Group:
{
Node *sentinel = &record->group.children;
for (Node *node = sentinel->next;
node != sentinel;
node = node->next){
Record *sub_record = CastFromMember(Record, node, node);
edit__apply_record_forward(tctx, models, file, sub_record, behaviors_prototype);
}
}break;
default:
{
InvalidPath;
}break;
}
}
function void
edit__apply_record_backward(Thread_Context *tctx, 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:
{
String_Const_u8 str = record->single.backward_text;
Range_i64 range = Ii64(record->single.first, record->single.first + record->single.forward_text.size);
edit_single(tctx, models, file, range, str, behaviors_prototype);
}break;
case RecordKind_Group:
{
Node *sentinel = &record->group.children;
for (Node *node = sentinel->prev;
node != sentinel;
node = node->prev){
Record *sub_record = CastFromMember(Record, node, node);
edit__apply_record_backward(tctx, models, file, sub_record, behaviors_prototype);
}
}break;
default:
{
InvalidPath;
}break;
}
}
function void
edit_change_current_history_state(Thread_Context *tctx, 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;
behaviors_prototype.pos_before_edit = -1;
if (current < target_index){
do{
current += 1;
record = CastFromMember(Record, node, record->node.next);
Assert(record != dummy_record);
edit__apply_record_forward(tctx, models, file, record, behaviors_prototype);
} while (current != target_index);
}
else{
do{
Assert(record != dummy_record);
edit__apply_record_backward(tctx, models, file, record, behaviors_prototype);
current -= 1;
record = CastFromMember(Record, node, record->node.prev);
} while (current != target_index);
}
file->state.current_record_index = current;
}
}
function b32
edit_merge_history_range(Thread_Context *tctx, 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){
u32 in_range_handler = (flags & bitmask_2);
switch (in_range_handler){
case RecordMergeFlag_StateInRange_MoveStateForward:
{
edit_change_current_history_state(tctx, models, file, last_index);
current_index = last_index;
}break;
case RecordMergeFlag_StateInRange_MoveStateBackward:
{
edit_change_current_history_state(tctx, models, file, first_index);
current_index = first_index;
}break;
case RecordMergeFlag_StateInRange_ErrorOut:
{
goto done;
}break;
}
}
Scratch_Block scratch(tctx);
history_merge_records(scratch, 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);
}
function b32
edit_batch_check(Thread_Context *tctx, Profile_Global_List *list, Batch_Edit *batch){
ProfileTLScope(tctx, list, "batch check");
b32 result = true;
Range_i64 prev_range = Ii64(-1, 0);
for (;batch != 0;
batch = batch->next){
if (batch->edit.range.first <= prev_range.first ||
batch->edit.range.first < prev_range.one_past_last){
result = false;
break;
}
}
return(result);
}
function b32
edit_batch(Thread_Context *tctx, Models *models, Editing_File *file,
Batch_Edit *batch, Edit_Behaviors behaviors){
b32 result = true;
if (batch != 0){
if (!edit_batch_check(tctx, &models->profile_list, batch)){
result = false;
}
else{
ProfileTLScope(tctx, &models->profile_list, "batch apply");
pre_edit_state_change(models, file);
pre_edit_history_prep(file, behaviors);
History_Record_Index start_index = 0;
if (history_is_activated(&file->state.history)){
start_index = file->state.current_record_index;
}
ProfileTLBlockNamed(tctx, &models->profile_list, "batch text edits", profile_edits);
Range_i64 old_range = {};
old_range.min = batch->edit.range.min;
for (Batch_Edit *edit = batch;
edit != 0;
edit = edit->next){
if (edit->next == 0){
old_range.max = edit->edit.range.max;
}
}
Range_Cursor cursor_range = {};
cursor_range.min = file_compute_cursor(file, seek_pos(old_range.min));
cursor_range.max = file_compute_cursor(file, seek_pos(old_range.max));
Range_i64 new_range = Ii64_neg_inf;
Gap_Buffer *buffer = &file->state.buffer;
i32 batch_count = 0;
i64 shift = 0;
for (Batch_Edit *edit = batch;
edit != 0;
edit = edit->next){
String_Const_u8 insert_string = edit->edit.text;
Range_i64 edit_range = edit->edit.range;
edit_range.first += shift;
edit_range.one_past_last += shift;
new_range.min = Min(new_range.min, edit_range.min);
i64 new_max = (i64)(edit_range.min + insert_string.size);
new_range.max = Max(new_range.max, new_max);
i64 size = buffer_size(buffer);
if (0 <= edit_range.first &&
edit_range.first <= edit_range.one_past_last &&
edit_range.one_past_last <= size){
edit__apply(tctx, models, file, edit_range, insert_string,
behaviors);
shift += replace_range_shift(edit_range, insert_string.size);
batch_count += 1;
}
else{
result = false;
break;
}
}
ProfileCloseNow(profile_edits);
if (history_is_activated(&file->state.history)){
History_Record_Index last_index = file->state.current_record_index;
if (start_index + 1 < last_index){
edit_merge_history_range(tctx, models, file,
start_index + 1, last_index,
RecordMergeFlag_StateInRange_ErrorOut);
}
}
file_clear_layout_cache(file);
edit_fix_markers(tctx, models, file, batch);
post_edit_call_hook(tctx, models, file, new_range, cursor_range);
}
}
return(result);
}
////////////////////////////////
function Editing_File*
create_file(Thread_Context *tctx, Models *models, String_Const_u8 file_name, Buffer_Create_Flag flags){
Editing_File *result = 0;
if (file_name.size > 0){
Working_Set *working_set = &models->working_set;
Heap *heap = &models->heap;
Scratch_Block scratch(tctx);
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.
if (HasFlag(flags, BufferCreate_NeverAttachToFile) == 0){
if (get_canon_name(scratch, file_name, &canon)){
has_canon_name = true;
file = working_set_contains_canon(working_set, string_from_file_name(&canon));
}
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{
if (!system_load_handle(scratch, (char*)canon.name_space, &handle)){
do_empty_buffer = true;
}
}
}
if (do_empty_buffer){
if (has_canon_name){
buffer_is_for_new_file = true;
}
if (!HasFlag(flags, BufferCreate_NeverNew)){
file = working_set_allocate_file(working_set, &models->lifetime_allocator);
if (file != 0){
if (has_canon_name){
file_bind_file_name(working_set, file, string_from_file_name(&canon));
}
String_Const_u8 front = string_front_of_path(file_name);
buffer_bind_name(tctx, models, scratch, working_set, file, front);
File_Attributes attributes = {};
file_create_from_string(tctx, models, file, SCu8(""), attributes);
result = file;
}
}
}
else{
File_Attributes attributes = system_load_attributes(handle);
b32 in_heap_mem = false;
char *buffer = push_array(scratch, char, (i32)attributes.size);
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);
file = working_set_allocate_file(working_set, &models->lifetime_allocator);
if (file != 0){
file_bind_file_name(working_set, file, string_from_file_name(&canon));
String_Const_u8 front = string_front_of_path(file_name);
buffer_bind_name(tctx, models, scratch, working_set, file, front);
file_create_from_string(tctx, models, file, SCu8(buffer, (i32)attributes.size), attributes);
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.save_state = FileSaveState_SavedWaitingForNotification;
}
if (file != 0 && HasFlag(flags, BufferCreate_AlwaysNew)){
i64 size = buffer_size(&file->state.buffer);
if (size > 0){
Edit_Behaviors behaviors = {};
edit_single(tctx, models, file, Ii64(0, size), string_u8_litexpr(""), behaviors);
if (has_canon_name){
buffer_is_for_new_file = true;
}
}
}
if (file != 0 && buffer_is_for_new_file &&
!HasFlag(flags, BufferCreate_SuppressNewFileHook) &&
models->new_file != 0){
Application_Links app = {};
app.tctx = tctx;
app.cmd_context = models;
models->new_file(&app, file->id);
}
}
return(result);
}
// BOTTOM

23
code/4ed_edit.h Normal file
View File

@ -0,0 +1,23 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 07.02.2019
*
* Types used for edit operations
*
*/
// TOP
#if !defined(FRED_EDIT_H)
#define FRED_EDIT_H
struct Edit_Behaviors{
b32 do_not_post_to_history;
i64 pos_before_edit;
};
#endif
// BOTTOM

568
code/4ed_file.cpp Normal file
View File

@ -0,0 +1,568 @@
/*
* 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, i64 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, Buffer_Scroll scroll){
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 Face*
file_get_face(Models *models, Editing_File *file){
return(font_set_face_from_id(&models->font_set, file->settings.face_id));
}
internal Access_Flag
file_get_access_flags(Editing_File *file){
Access_Flag flags = Access_Read|Access_Visible;
if (!file->settings.read_only){
flags |= Access_Write;
}
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 void
file_set_unimportant(Editing_File *file, b32 val){
if (val){
file->state.dirty = DirtyState_UpToDate;
}
file->settings.unimportant = (b8)(val);
}
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){
u64 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(Thread_Context *tctx, 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){
if (models->save_file != 0){
Application_Links app = {};
app.tctx = tctx;
app.cmd_context = models;
models->save_file(&app, file->id);
}
Gap_Buffer *buffer = &file->state.buffer;
b32 dos_write_mode = file->settings.dos_write_mode;
Scratch_Block scratch(tctx);
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;
}
}
String_Const_u8 saveable_string = buffer_stringify(scratch, buffer, Ii64(0, buffer_size(buffer)));
File_Attributes new_attributes = system_save_file(scratch, (char*)file_name, saveable_string);
if (new_attributes.last_write_time > 0 &&
using_actual_file_name){
file->state.save_state = FileSaveState_SavedWaitingForNotification;
file_clear_dirty_flags(file);
}
LogEventF(log_string(M), scratch, file->id, 0, system_thread_get_id(),
"save file [last_write_time=0x%llx]", new_attributes.last_write_time);
}
return(result);
}
////////////////////////////////
internal Buffer_Cursor
file_compute_cursor(Editing_File *file, Buffer_Seek seek){
Buffer_Cursor result = {};
switch (seek.type){
case buffer_seek_pos:
{
result = buffer_cursor_from_pos(&file->state.buffer, seek.pos);
}break;
case buffer_seek_line_col:
{
result = buffer_cursor_from_line_col(&file->state.buffer, seek.line, seek.col);
}break;
}
return(result);
}
////////////////////////////////
function Layout_Function*
file_get_layout_func(Editing_File *file){
return(file->settings.layout_func);
}
internal void
file_create_from_string(Thread_Context *tctx, Models *models, Editing_File *file, String_Const_u8 val, File_Attributes attributes){
Scratch_Block scratch(tctx);
Base_Allocator *allocator = tctx->allocator;
block_zero_struct(&file->state);
buffer_init(&file->state.buffer, val.str, val.size, allocator);
if (buffer_size(&file->state.buffer) < (i64)val.size){
file->settings.dos_write_mode = true;
}
file_clear_dirty_flags(file);
file->attributes = attributes;
file->settings.layout_func = models->layout_func;
file->settings.face_id = models->global_face_id;
buffer_measure_starts(scratch, &file->state.buffer);
file->lifetime_object = lifetime_alloc_object(&models->lifetime_allocator, DynamicWorkspace_Buffer, file);
history_init(tctx, models, &file->state.history);
file->state.cached_layouts_arena = make_arena(allocator);
file->state.line_layout_table = make_table_Data_u64(allocator, 500);
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);
}
////////////////////////////////
if (models->begin_buffer != 0){
Application_Links app = {};
app.tctx = tctx;
app.cmd_context = models;
models->begin_buffer(&app, file->id);
}
}
internal void
file_free(Thread_Context *tctx, Models *models, Editing_File *file){
Lifetime_Allocator *lifetime_allocator = &models->lifetime_allocator;
Working_Set *working_set = &models->working_set;
lifetime_free_object(lifetime_allocator, file->lifetime_object);
Gap_Buffer *buffer = &file->state.buffer;
if (buffer->data){
base_free(buffer->allocator, buffer->data);
base_free(buffer->allocator, buffer->line_starts);
}
history_free(tctx, &file->state.history);
linalloc_clear(&file->state.cached_layouts_arena);
table_free(&file->state.line_layout_table);
}
////////////////////////////////
internal i32
file_get_current_record_index(Editing_File *file){
return(file->state.current_record_index);
}
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);
}
////////////////////////////////
internal Layout_Item_List
file_get_line_layout(Thread_Context *tctx, Models *models, Editing_File *file,
Layout_Function *layout_func, f32 width, Face *face, i64 line_number){
Layout_Item_List result = {};
i64 line_count = buffer_line_count(&file->state.buffer);
if (1 <= line_number && line_number <= line_count){
Line_Layout_Key key = {};
key.face_id = face->id;
key.face_version_number = face->version_number;
key.width = width;
key.line_number = line_number;
String_Const_u8 key_data = make_data_struct(&key);
Layout_Item_List *list = 0;
Table_Lookup lookup = table_lookup(&file->state.line_layout_table, key_data);
if (lookup.found_match){
u64 val = 0;
table_read(&file->state.line_layout_table, lookup, &val);
list = (Layout_Item_List*)IntAsPtr(val);
}
else{
list = push_array(&file->state.cached_layouts_arena, Layout_Item_List, 1);
Range_i64 line_range = buffer_get_pos_range_from_line_number(&file->state.buffer, line_number);
Application_Links app = {};
app.tctx = tctx;
app.cmd_context = models;
*list = layout_func(&app, &file->state.cached_layouts_arena,
file->id, line_range, face->id, width);
key_data = push_data_copy(&file->state.cached_layouts_arena, key_data);
table_insert(&file->state.line_layout_table, key_data, (u64)PtrAsInt(list));
}
block_copy_struct(&result, list);
}
return(result);
}
internal void
file_clear_layout_cache(Editing_File *file){
linalloc_clear(&file->state.cached_layouts_arena);
table_clear(&file->state.line_layout_table);
}
internal Line_Shift_Vertical
file_line_shift_y(Thread_Context *tctx, Models *models, Editing_File *file,
Layout_Function *layout_func, f32 width, Face *face,
i64 line_number, f32 y_delta){
Line_Shift_Vertical result = {};
f32 line_y = 0.f;
if (y_delta < 0.f){
// NOTE(allen): Iterating upward
b32 has_result = false;
for (;;){
if (line_y <= y_delta){
has_result = true;
result.line = line_number;
result.y_delta = line_y;
break;
}
line_number -= 1;
if (line_number <= 0){
line_number = 1;
break;
}
Layout_Item_List line = file_get_line_layout(tctx, models, file, layout_func,
width, face, line_number);
line_y -= line.height;
}
if (!has_result){
result.line = line_number;
result.y_delta = line_y;
}
}
else{
// NOTE(allen): Iterating downward
b32 has_result = false;
i64 line_count = buffer_line_count(&file->state.buffer);
for (;;line_number += 1){
Layout_Item_List line = file_get_line_layout(tctx, models, file, layout_func,
width, face, line_number);
f32 next_y = line_y + line.height;
if (y_delta < next_y){
has_result = true;
result.line = line_number;
result.y_delta = line_y;
break;
}
if (line_number >= line_count){
break;
}
line_y = next_y;
}
if (!has_result){
result.line = line_number;
result.y_delta = line_y;
}
}
return(result);
}
internal f32
file_line_y_difference(Thread_Context *tctx, Models *models, Editing_File *file,
Layout_Function *layout_func, f32 width, Face *face,
i64 line_a, i64 line_b){
f32 result = 0.f;
if (line_a != line_b){
Range_i64 line_range = Ii64(line_a, line_b);
for (i64 i = line_range.min; i < line_range.max; i += 1){
Layout_Item_List line = file_get_line_layout(tctx, models, file, layout_func, width, face, i);
result += line.height;
}
if (line_a < line_b){
result *= -1.f;
}
}
return(result);
}
internal i64
file_pos_at_relative_xy(Thread_Context *tctx, Models *models, Editing_File *file,
Layout_Function *layout_func, f32 width, Face *face,
i64 base_line, Vec2_f32 relative_xy){
Line_Shift_Vertical shift = file_line_shift_y(tctx, models, file, layout_func, width, face, base_line, relative_xy.y);
relative_xy.y -= shift.y_delta;
Layout_Item_List line = file_get_line_layout(tctx, models, file, layout_func, width, face, shift.line);
return(layout_nearest_pos_to_xy(line, relative_xy));
}
internal Rect_f32
file_relative_box_of_pos(Thread_Context *tctx, Models *models, Editing_File *file,
Layout_Function *layout_func, f32 width, Face *face,
i64 base_line, i64 pos){
i64 line_number = buffer_get_line_index(&file->state.buffer, pos) + 1;
Layout_Item_List line = file_get_line_layout(tctx, models, file, layout_func, width, face, line_number);
Rect_f32 result = layout_box_of_pos(line, pos);
f32 y_difference = file_line_y_difference(tctx, models, file, layout_func, width, face, line_number, base_line);
result.y0 += y_difference;
result.y1 += y_difference;
return(result);
}
function Vec2_f32
file_relative_xy_of_pos(Thread_Context *tctx, Models *models, Editing_File *file,
Layout_Function *layout_func, f32 width, Face *face,
i64 base_line, i64 pos){
Rect_f32 rect = file_relative_box_of_pos(tctx, models, file, layout_func, width, face, base_line, pos);
return(rect_center(rect));
}
function Rect_f32
file_padded_box_of_pos(Thread_Context *tctx, Models *models, Editing_File *file,
Layout_Function *layout_func, f32 width, Face *face,
i64 base_line, i64 pos){
i64 line_number = buffer_get_line_index(&file->state.buffer, pos) + 1;
Layout_Item_List line = file_get_line_layout(tctx, models, file, layout_func, width, face, line_number);
Rect_f32 result = layout_padded_box_of_pos(line, pos);
f32 y_difference = file_line_y_difference(tctx, models, file, layout_func, width, face, line_number, base_line);
result.y0 += y_difference;
result.y1 += y_difference;
return(result);
}
internal Buffer_Point
file_normalize_buffer_point(Thread_Context *tctx, Models *models, Editing_File *file,
Layout_Function *layout_func, f32 width, Face *face,
Buffer_Point point){
Line_Shift_Vertical shift = file_line_shift_y(tctx, models, file, layout_func, width, face, point.line_number, point.pixel_shift.y);
point.line_number = shift.line;
point.pixel_shift.y -= shift.y_delta;
point.pixel_shift.x = clamp_bot(0.f, point.pixel_shift.x);
point.pixel_shift.y = clamp_bot(0.f, point.pixel_shift.y);
return(point);
}
internal Vec2_f32
file_buffer_point_difference(Thread_Context *tctx, Models *models, Editing_File *file,
Layout_Function *layout_func, f32 width, Face *face,
Buffer_Point a, Buffer_Point b){
f32 y_difference = file_line_y_difference(tctx, models, file, layout_func, width, face, a.line_number, b.line_number);
Vec2_f32 result = a.pixel_shift - b.pixel_shift;
result.y += y_difference;
return(result);
}
internal Line_Shift_Character
file_line_shift_characters(Thread_Context *tctx, Models *models, Editing_File *file, Layout_Function *layout_func, f32 width, Face *face, i64 line_number, i64 character_delta){
Line_Shift_Character result = {};
i64 line_character = 0;
if (character_delta < 0){
// NOTE(allen): Iterating upward
b32 has_result = false;
for (;;){
if (line_character <= character_delta){
has_result = true;
result.line = line_number;
result.character_delta = line_character;
break;
}
line_number -= 1;
if (line_number <= 0){
line_number = 1;
break;
}
Layout_Item_List line = file_get_line_layout(tctx, models, file, layout_func, width, face, line_number);
line_character -= line.character_count;
}
if (!has_result){
result.line = line_number;
result.character_delta = line_character;
}
}
else{
// NOTE(allen): Iterating downward
b32 has_result = false;
i64 line_count = buffer_line_count(&file->state.buffer);
for (;;line_number += 1){
Layout_Item_List line = file_get_line_layout(tctx, models, file, layout_func, width, face, line_number);
i64 next_character = line_character + line.character_count;
if (character_delta < next_character){
has_result = true;
result.line = line_number;
result.character_delta = line_character;
break;
}
if (line_number >= line_count){
break;
}
line_character = next_character;
}
if (!has_result){
result.line = line_number;
result.character_delta = line_character;
}
}
return(result);
}
internal i64
file_line_character_difference(Thread_Context *tctx, Models *models, Editing_File *file, Layout_Function *layout_func, f32 width, Face *face, i64 line_a, i64 line_b){
i64 result = 0;
if (line_a != line_b){
Range_i64 line_range = Ii64(line_a, line_b);
for (i64 i = line_range.min; i < line_range.max; i += 1){
Layout_Item_List line = file_get_line_layout(tctx, models, file, layout_func, width, face, i);
result += line.character_count;
}
if (line_a < line_b){
result *= -1;
}
}
return(result);
}
internal i64
file_pos_from_relative_character(Thread_Context *tctx, Models *models, Editing_File *file,
Layout_Function *layout_func, f32 width, Face *face,
i64 base_line, i64 relative_character){
Line_Shift_Character shift = file_line_shift_characters(tctx, models, file, layout_func, width, face, base_line, relative_character);
relative_character -= shift.character_delta;
Layout_Item_List line = file_get_line_layout(tctx, models, file, layout_func, width, face, shift.line);
return(layout_get_pos_at_character(line, relative_character));
}
internal i64
file_relative_character_from_pos(Thread_Context *tctx, Models *models, Editing_File *file, Layout_Function *layout_func, f32 width, Face *face,
i64 base_line, i64 pos){
i64 line_number = buffer_get_line_index(&file->state.buffer, pos) + 1;
Layout_Item_List line = file_get_line_layout(tctx, models, file, layout_func, width, face, line_number);
i64 result = layout_character_from_pos(line, pos);
result += file_line_character_difference(tctx, models, file, layout_func, width, face, line_number, base_line);
return(result);
}
// BOTTOM

100
code/4ed_file.h Normal file
View File

@ -0,0 +1,100 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 24.01.2018
*
* Buffer types
*
*/
// TOP
#if !defined(FRED_FILE_H)
#define FRED_FILE_H
typedef i32 Edit_Pos_Set_Type;
enum{
EditPos_None,
EditPos_CursorSet,
EditPos_ScrollSet
};
struct File_Edit_Positions{
Edit_Pos_Set_Type last_set_type;
Buffer_Scroll scroll;
i64 cursor_pos;
};
struct Editing_File_Settings{
Layout_Function *layout_func;
Face_ID face_id;
b8 dos_write_mode;
b8 is_initialized;
b8 unimportant;
b8 read_only;
b8 unkillable;
b8 never_kill;
};
struct Line_Layout_Key{
Face_ID face_id;
i32 face_version_number;
f32 width;
i64 line_number;
};
typedef i32 File_Save_State;
enum{
FileSaveState_Normal,
FileSaveState_SavedWaitingForNotification,
};
struct Editing_File_State{
Gap_Buffer buffer;
History history;
i32 current_record_index;
Dirty_State dirty;
File_Save_State save_state;
File_Edit_Positions edit_pos_most_recent;
File_Edit_Positions edit_pos_stack[16];
i32 edit_pos_stack_top;
Child_Process_ID attached_child_process;
Arena cached_layouts_arena;
Table_Data_u64 line_layout_table;
};
struct Editing_File_Name{
u8 name_space[256];
u64 name_size;
};
struct Editing_File{
union{
Editing_File *next;
Node main_chain_node;
};
Node touch_node;
Node external_mod_node;
Buffer_ID id;
Editing_File_Settings settings;
Editing_File_State state;
File_Attributes attributes;
Lifetime_Object *lifetime_object;
Editing_File_Name base_name;
Editing_File_Name unique_name;
Editing_File_Name canon;
};
struct Buffer_Point_Delta{
Buffer_Point new_point;
f32 y_shift;
};
#endif
// BOTTOM

34
code/4ed_font_api.cpp Normal file
View File

@ -0,0 +1,34 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 02.10.2019
*
* Font API definition program.
*
*/
// TOP
#include "4ed_api_definition_main.cpp"
function API_Definition*
define_api(Arena *arena){
API_Definition *api = begin_api(arena, "font");
{
API_Call *call = api_call(arena, api, "make_face", "Face*");
api_param(arena, call, "Arena*", "arena");
api_param(arena, call, "Face_Description*", "description");
api_param(arena, call, "f32", "scale_factor");
}
return(api);
}
function Generated_Group
get_api_group(void){
return(GeneratedGroup_Core);
}
// BOTTOM

60
code/4ed_font_interface.h Normal file
View File

@ -0,0 +1,60 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 11.03.2017
*
* Font system interface.
*
*/
// TOP
#if !defined(FCODER_FONT_INTERFACE_H)
#define FCODER_FONT_INTERFACE_H
typedef i32 Texture_Kind;
enum{
TextureKind_Error,
TextureKind_Mono,
};
typedef u32 Graphics_Get_Texture_Function(Vec3_i32 dim, Texture_Kind texture_kind);
typedef b32 Graphics_Fill_Texture_Function(Texture_Kind texture_kind, u32 texture,
Vec3_i32 p, Vec3_i32 dim, void *data);
////////////////////////////////
struct Glyph_Bounds{
Rect_f32 uv;
f32 w;
Rect_f32 xy_off;
};
struct Face{
Face_Description description;
Face_ID id;
i32 version_number;
// NOTE(allen): Metrics
Face_Metrics metrics;
// NOTE(allen): Glyph data
Face_Advance_Map advance_map;
Glyph_Bounds *bounds;
Glyph_Bounds white;
Texture_Kind texture_kind;
u32 texture;
Vec3_f32 texture_dim;
};
////////////////////////////////
// NOTE(allen): Platform layer calls - implemented in a "font provider"
typedef Face *Font_Make_Face_Function(Arena *arena, Face_Description *description, f32 scale_factor);
#endif
// BOTTOM

View File

@ -0,0 +1,371 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 18.07.2017
*
* Freetype implementation of the font provider interface.
*
*/
// TOP
// NOTE(allen): Thanks to insofaras. This is copy-pasted from some work he originally did to get free type working on Linux.
#undef internal
#include <ft2build.h>
#include FT_FREETYPE_H
#define internal static
internal u32
ft__load_flags(b32 use_hinting){
u32 ft_flags = FT_LOAD_RENDER;
if (use_hinting){
// NOTE(inso): FT_LOAD_TARGET_LIGHT does hinting only vertically, which looks nicer imo
// maybe it could be exposed as an option for hinting, instead of just on/off.
ft_flags |= (FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_LIGHT);
}
else{
ft_flags |= (FT_LOAD_NO_AUTOHINT | FT_LOAD_NO_HINTING);
}
return(ft_flags);
}
internal FT_Codepoint_Index_Pair_Array
ft__get_codepoint_index_pairs(Arena *arena, FT_Face face, u16 *maximum_index_out){
FT_Long glyph_count = face->num_glyphs;
FT_Codepoint_Index_Pair_Array array = {};
array.count = glyph_count;
array.vals = push_array(arena, FT_Codepoint_Index_Pair, glyph_count);
u16 maximum_index = 0;
i32 counter = 0;
FT_UInt index = 0;
FT_ULong codepoint = FT_Get_First_Char(face, &index);
array.vals[counter].codepoint = codepoint;
array.vals[counter].index = (u16)index;
maximum_index = Max(maximum_index, (u16)index);
counter += 1;
for (;;){
codepoint = FT_Get_Next_Char(face, codepoint, &index);
array.vals[counter].codepoint = codepoint;
array.vals[counter].index = (u16)index;
maximum_index = Max(maximum_index, (u16)index);
counter += 1;
if (counter == glyph_count){
break;
}
}
*maximum_index_out = maximum_index;
return(array);
}
internal Codepoint_Index_Map
ft__get_codepoint_index_map(Base_Allocator *base_allocator, FT_Face face){
FT_Long glyph_count = face->num_glyphs;
Codepoint_Index_Map map = {};
map.zero_index = max_u16;
map.table = make_table_u32_u16(base_allocator, glyph_count*4);
u16 maximum_index = 0;
i32 counter = 0;
FT_UInt index = 0;
FT_ULong codepoint = FT_Get_First_Char(face, &index);
table_insert(&map.table, (u32)codepoint, (u16)index);
maximum_index = Max(maximum_index, (u16)index);
counter += 1;
for (;;){
codepoint = FT_Get_Next_Char(face, codepoint, &index);
if (codepoint == 0){
map.has_zero_index = true;
map.zero_index = (u16)(index);
}
else{
table_insert(&map.table, (u32)codepoint, (u16)index);
}
maximum_index = Max(maximum_index, (u16)index);
counter += 1;
if (counter == glyph_count){
break;
}
}
map.max_index = maximum_index;
return(map);
}
struct Bad_Rect_Pack{
Vec2_i32 max_dim;
Vec3_i32 dim;
Vec3_i32 p;
i32 current_line_h;
};
internal void
ft__bad_rect_pack_init(Bad_Rect_Pack *pack, Vec2_i32 max_dim){
pack->max_dim = max_dim;
pack->dim = V3i32(0, 0, 1);
pack->p = V3i32(0, 0, 0);
pack->current_line_h = 0;
}
internal void
ft__bad_rect_pack_end_line(Bad_Rect_Pack *pack){
pack->p.y += pack->current_line_h;
pack->dim.y = Max(pack->dim.y, pack->p.y);
pack->current_line_h = 0;
pack->p.x = 0;
}
internal Vec3_i32
ft__bad_rect_pack_next(Bad_Rect_Pack *pack, Vec2_i32 dim){
Vec3_i32 result = {};
if (dim.x <= pack->max_dim.x && dim.y <= pack->max_dim.y){
if (pack->current_line_h < dim.y){
pack->current_line_h = dim.y;
}
if (pack->current_line_h > pack->max_dim.y){
ft__bad_rect_pack_end_line(pack);
pack->p.y = 0;
pack->dim.z += 1;
pack->p.z += 1;
}
else{
if (pack->p.x + dim.x > pack->max_dim.x){
ft__bad_rect_pack_end_line(pack);
}
result = pack->p;
pack->p.x += dim.x;
pack->current_line_h = Max(pack->current_line_h, dim.y);
pack->dim.x = clamp_bot(pack->dim.x, pack->p.x);
}
}
return(result);
}
internal void
ft__bad_rect_store_finish(Bad_Rect_Pack *pack){
ft__bad_rect_pack_end_line(pack);
}
internal void
ft__glyph_bounds_store_uv_raw(Vec3_i32 p, Vec2_i32 dim, Glyph_Bounds *bounds){
bounds->uv = Rf32((f32)p.x, (f32)p.y, (f32)dim.x, (f32)dim.y);
bounds->w = (f32)p.z;
}
internal Face*
ft__font_make_face(Arena *arena, Face_Description *description, f32 scale_factor){
String_Const_u8 file_name = push_string_copy(arena, description->font.file_name);
FT_Library ft;
FT_Init_FreeType(&ft);
FT_Face ft_face;
FT_Error error = FT_New_Face(ft, (char*)file_name.str, 0, &ft_face);
Face *face = 0;
if (error == 0){
face = push_array_zero(arena, Face, 1);
u32 pt_size_unscaled = Max(description->parameters.pt_size, 8);
u32 pt_size = (u32)(pt_size_unscaled*scale_factor);
b32 hinting = description->parameters.hinting;
FT_Size_RequestRec_ size = {};
size.type = FT_SIZE_REQUEST_TYPE_NOMINAL;
size.height = (pt_size << 6);
FT_Request_Size(ft_face, &size);
face->description.font.file_name = file_name;
face->description.parameters = description->parameters;
Face_Metrics *met = &face->metrics;
met->max_advance = f32_ceil32(ft_face->size->metrics.max_advance/64.f);
met->ascent = f32_ceil32(ft_face->size->metrics.ascender/64.f);
met->descent = f32_floor32(ft_face->size->metrics.descender/64.f);
met->text_height = f32_ceil32(ft_face->size->metrics.height/64.f);
met->line_skip = met->text_height - (met->ascent - met->descent);
met->line_skip = clamp_bot(1.f, met->line_skip);
met->line_height = met->text_height + met->line_skip;
{
f32 real_over_notional = met->line_height/(f32)ft_face->height;
f32 relative_center = -1.f*real_over_notional*ft_face->underline_position;
f32 relative_thickness = real_over_notional*ft_face->underline_thickness;
f32 center = f32_floor32(met->ascent + relative_center);
f32 thickness = clamp_bot(1.f, relative_thickness);
met->underline_yoff1 = center - thickness*0.5f;
met->underline_yoff2 = center + thickness*0.5f;
}
face->advance_map.codepoint_to_index =
ft__get_codepoint_index_map(arena->base_allocator, ft_face);
u16 index_count =
codepoint_index_map_count(&face->advance_map.codepoint_to_index);
face->advance_map.index_count = index_count;
face->advance_map.advance = push_array_zero(arena, f32, index_count);
face->bounds = push_array(arena, Glyph_Bounds, index_count);
Temp_Memory_Block temp_memory(arena);
struct Bitmap{
Vec2_i32 dim;
u8 *data;
};
Bitmap *glyph_bitmaps = push_array(arena, Bitmap, index_count);
u32 load_flags = ft__load_flags(hinting);
for (u16 i = 0; i < index_count; i += 1){
Bitmap *bitmap = &glyph_bitmaps[i];
error = FT_Load_Glyph(ft_face, i, load_flags);
if (error == 0){
FT_GlyphSlot ft_glyph = ft_face->glyph;
Vec2_i32 dim = V2i32(ft_glyph->bitmap.width, ft_glyph->bitmap.rows);
bitmap->dim = dim;
bitmap->data = push_array(arena, u8, dim.x*dim.y);
face->bounds[i].xy_off.x0 = (f32)(ft_face->glyph->bitmap_left);
face->bounds[i].xy_off.y0 = (f32)(met->ascent - ft_face->glyph->bitmap_top);
face->bounds[i].xy_off.x1 = (f32)(face->bounds[i].xy_off.x0 + dim.x);
face->bounds[i].xy_off.y1 = (f32)(face->bounds[i].xy_off.y0 + dim.y);
switch (ft_glyph->bitmap.pixel_mode){
case FT_PIXEL_MODE_MONO:
{
NotImplemented;
}break;
case FT_PIXEL_MODE_GRAY:
{
b32 aa_1bit_mono = (description->parameters.aa_mode == FaceAntialiasingMode_1BitMono);
u8 *src_line = ft_glyph->bitmap.buffer;
if (ft_glyph->bitmap.pitch < 0){
src_line = ft_glyph->bitmap.buffer + (-ft_glyph->bitmap.pitch)*(dim.y - 1);
}
u8 *dst = bitmap->data;
for (i32 y = 0; y < dim.y; y += 1){
u8 *src_pixel = src_line;
for (i32 x = 0; x < dim.x; x += 1){
if (aa_1bit_mono){
u8 s = *src_pixel;
if (s > 0){
s = 255;
}
*dst = s;
}
else{
*dst = *src_pixel;
}
dst += 1;
src_pixel += 1;
}
src_line += ft_glyph->bitmap.pitch;
}
}break;
default:
{
NotImplemented;
}break;
}
face->advance_map.advance[i] = f32_ceil32(ft_glyph->advance.x/64.0f);
}
}
u8 white_data[16] = {
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
};
Bitmap white = {};
white.dim = V2i32(4, 4);
white.data = white_data;
Bad_Rect_Pack pack = {};
ft__bad_rect_pack_init(&pack, V2i32(1024, 1024));
ft__glyph_bounds_store_uv_raw(ft__bad_rect_pack_next(&pack, white.dim), white.dim, &face->white);
for (u16 i = 0; i < index_count; i += 1){
Vec2_i32 dim = glyph_bitmaps[i].dim;
ft__glyph_bounds_store_uv_raw(ft__bad_rect_pack_next(&pack, dim), dim, &face->bounds[i]);
}
ft__bad_rect_store_finish(&pack);
Texture_Kind texture_kind = TextureKind_Mono;
u32 texture = graphics_get_texture(pack.dim, texture_kind);
face->texture_kind = texture_kind;
face->texture = texture;
Vec3_f32 texture_dim = V3f32(pack.dim);
face->texture_dim = texture_dim;
{
Vec3_i32 p = V3i32((i32)face->white.uv.x0, (i32)face->white.uv.y0, (i32)face->white.w);
Vec3_i32 dim = V3i32(white.dim.x, white.dim.y, 1);
graphics_fill_texture(texture_kind, texture, p, dim, white.data);
face->white.uv.x1 = (face->white.uv.x0 + face->white.uv.x1)/texture_dim.x;
face->white.uv.y1 = (face->white.uv.y0 + face->white.uv.y1)/texture_dim.y;
face->white.uv.x0 = face->white.uv.x0/texture_dim.x;
face->white.uv.y0 = face->white.uv.y0/texture_dim.y;
face->white.w /= texture_dim.z;
}
for (u16 i = 0; i < index_count; i += 1){
Vec3_i32 p = V3i32((i32)face->bounds[i].uv.x0, (i32)face->bounds[i].uv.y0, (i32)face->bounds[i].w);
Vec3_i32 dim = V3i32(glyph_bitmaps[i].dim.x, glyph_bitmaps[i].dim.y, 1);
graphics_fill_texture(texture_kind, texture, p, dim, glyph_bitmaps[i].data);
face->bounds[i].uv.x1 = (face->bounds[i].uv.x0 + face->bounds[i].uv.x1)/texture_dim.x;
face->bounds[i].uv.y1 = (face->bounds[i].uv.y0 + face->bounds[i].uv.y1)/texture_dim.y;
face->bounds[i].uv.x0 = face->bounds[i].uv.x0/texture_dim.x;
face->bounds[i].uv.y0 = face->bounds[i].uv.y0/texture_dim.y;
face->bounds[i].w /= texture_dim.z;
}
{
Face_Advance_Map *advance_map = &face->advance_map;
met->space_advance = font_get_glyph_advance(advance_map, met, ' ', 0);
met->decimal_digit_advance =
font_get_max_glyph_advance_range(advance_map, met, '0', '9', 0);
met->hex_digit_advance =
font_get_max_glyph_advance_range(advance_map, met, 'A', 'F', 0);
met->hex_digit_advance =
Max(met->hex_digit_advance, met->decimal_digit_advance);
met->byte_sub_advances[0] =
font_get_glyph_advance(advance_map, met, '\\', 0);
met->byte_sub_advances[1] = met->hex_digit_advance;
met->byte_sub_advances[2] = met->hex_digit_advance;
met->byte_advance =
met->byte_sub_advances[0] +
met->byte_sub_advances[1] +
met->byte_sub_advances[2];
met->normal_lowercase_advance =
font_get_average_glyph_advance_range(advance_map, met, 'a', 'z', 0);
met->normal_uppercase_advance =
font_get_average_glyph_advance_range(advance_map, met, 'A', 'Z', 0);
met->normal_advance = (26*met->normal_lowercase_advance +
26*met->normal_uppercase_advance +
10*met->decimal_digit_advance)/62.f;
}
}
FT_Done_FreeType(ft);
return(face);
}
// BOTTOM

View File

@ -0,0 +1,28 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 16.11.2017
*
* Data types for the freetype font provider.
*
*/
// TOP
#if !defined(FCODER_FONT_PROVIDER_FREETYPE_H)
#define FCODER_FONT_PROVIDER_FREETYPE_H
struct FT_Codepoint_Index_Pair{
u32 codepoint;
u16 index;
};
struct FT_Codepoint_Index_Pair_Array{
FT_Codepoint_Index_Pair *vals;
i32 count;
};
#endif
// BOTTOM

171
code/4ed_font_set.cpp Normal file
View File

@ -0,0 +1,171 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 23.07.2019
*
* Type for organizating the set of all loaded font faces.
*
*/
// TOP
internal Face_ID
font_set__alloc_face_id(Font_Set *set){
Face_ID result = 0;
if (set->free_ids != 0){
Font_Face_ID_Node *node = set->free_ids;
result = node->id;
sll_stack_pop(set->free_ids);
sll_stack_push(set->free_id_nodes, node);
}
else{
result = set->next_id_counter;
set->next_id_counter += 1;
}
return(result);
}
internal void
font_set__free_face_id(Font_Set *set, Face_ID id){
if (id + 1 == set->next_id_counter){
set->next_id_counter -= 1;
}
else{
Font_Face_ID_Node *node = 0;
if (set->free_id_nodes == 0){
node = push_array(&set->arena, Font_Face_ID_Node, 1);
}
else{
node = set->free_id_nodes;
sll_stack_pop(set->free_id_nodes);
}
sll_stack_push(set->free_ids, node);
node->id = id;
}
}
internal Font_Face_Slot*
font_set__alloc_face_slot(Font_Set *set){
Font_Face_Slot *result = 0;
if (set->free_face_slots == 0){
result = push_array(&set->arena, Font_Face_Slot, 1);
}
else{
result = set->free_face_slots;
sll_stack_pop(set->free_face_slots);
}
return(result);
}
internal void
font_set__free_face_slot(Font_Set *set, Font_Face_Slot *slot){
if (slot->arena.base_allocator != 0){
table_free(&slot->face->advance_map.codepoint_to_index.table);
linalloc_clear(&slot->arena);
}
block_zero_struct(slot);
sll_stack_push(set->free_face_slots, slot);
}
internal void
font_set_init(Font_Set *set){
block_zero_struct(set);
set->arena = make_arena_system();
set->next_id_counter = 1;
set->id_to_slot_table = make_table_u64_u64(set->arena.base_allocator, 40);
set->scale_factor = system_get_screen_scale_factor();
}
internal Face*
font_set_new_face(Font_Set *set, Face_Description *description){
Arena arena = make_arena_system();
Face *face = font_make_face(&arena, description, set->scale_factor);
if (face != 0){
Font_Face_Slot *slot = font_set__alloc_face_slot(set);
slot->arena = arena;
slot->face = face;
Face_ID new_id = font_set__alloc_face_id(set);
face->id = new_id;
table_insert(&set->id_to_slot_table, new_id, (u64)slot);
}
else{
linalloc_clear(&arena);
}
return(face);
}
internal Font_Face_Slot*
font_set__get_face_slot(Font_Set *set, Face_ID id){
Font_Face_Slot *result = 0;
u64 slot_ptr_u64 = 0;
if (table_read(&set->id_to_slot_table, id, &slot_ptr_u64)){
result = (Font_Face_Slot*)slot_ptr_u64;
}
return(result);
}
internal b32
font_set_release_face(Font_Set *set, Face_ID id){
b32 result = false;
Font_Face_Slot *slot = font_set__get_face_slot(set, id);
if (slot != 0){
table_erase(&set->id_to_slot_table, id);
font_set__free_face_slot(set, slot);
font_set__free_face_id(set, id);
result = true;
}
return(result);
}
internal Face*
font_set_face_from_id(Font_Set *set, Face_ID id){
Face *result = 0;
Font_Face_Slot *slot = font_set__get_face_slot(set, id);
if (slot != 0){
result = slot->face;
}
return(result);
}
internal Face_ID
font_set_get_fallback_face(Font_Set *set){
Face_ID result = 0;
for (Face_ID i = 1; i < set->next_id_counter; i += 1){
if (font_set__get_face_slot(set, i) != 0){
result = i;
break;
}
}
return(result);
}
internal Face_ID
font_set_get_largest_id(Font_Set *set){
return(set->next_id_counter - 1);
}
internal b32
font_set_modify_face(Font_Set *set, Face_ID id, Face_Description *description){
b32 result = false;
Font_Face_Slot *slot = font_set__get_face_slot(set, id);
if (slot != 0){
i32 version_number = slot->face->version_number;
Arena arena = make_arena_system();
Face *face = font_make_face(&arena, description, set->scale_factor);
if (face != 0){
linalloc_clear(&slot->arena);
slot->arena = arena;
slot->face = face;
face->version_number = version_number + 1;
face->id = id;
result = true;
}
else{
linalloc_clear(&arena);
}
}
return(result);
}
// BOTTOM

43
code/4ed_font_set.h Normal file
View File

@ -0,0 +1,43 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 23.07.2019
*
* Type for organizating the set of all loaded font faces.
*
*/
// TOP
#if !defined(FRED_FONT_SET_H)
#define FRED_FONT_SET_H
struct Font_Face_ID_Node{
Font_Face_ID_Node *next;
Face_ID id;
};
union Font_Face_Slot{
struct{
Font_Face_Slot *next;
};
struct{
Arena arena;
Face *face;
};
};
struct Font_Set{
Arena arena;
Face_ID next_id_counter;
Font_Face_ID_Node *free_ids;
Font_Face_ID_Node *free_id_nodes;
Font_Face_Slot *free_face_slots;
Table_u64_u64 id_to_slot_table;
f32 scale_factor;
};
#endif
// BOTTOM

View File

@ -0,0 +1,199 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 09.10.2019
*
* Primary list for all key codes.
*
*/
// TOP
#include "4coder_base_types.h"
#include "4coder_base_types.cpp"
#include "4coder_stringf.cpp"
#include "4coder_malloc_allocator.cpp"
#include <stdio.h>
////////////////////////////////
struct Event_Code{
Event_Code *next;
String_Const_u8 name;
};
struct Event_Code_List{
Event_Code *first;
Event_Code *last;
i32 count;
String_Const_u8 code_prefix;
String_Const_u8 name_table;
};
////////////////////////////////
function void
generate_codes(Arena *scratch, Event_Code_List *list, FILE *out){
String_Const_u8 code_prefix = list->code_prefix;
String_Const_u8 name_table = list->name_table;
fprintf(out, "enum{\n");
i32 counter = 1;
for (Event_Code *code = list->first;
code != 0;
code = code->next){
fprintf(out, "%.*s_%.*s = %d,\n",
string_expand(code_prefix), string_expand(code->name), counter);
counter += 1;
}
fprintf(out, "%.*s_COUNT = %d,\n", string_expand(code_prefix), counter);
fprintf(out, "};\n");
fprintf(out, "global char* %.*s[%.*s_COUNT] = {\n",
string_expand(name_table), string_expand(code_prefix));
fprintf(out, "\"None\",\n");
for (Event_Code *code = list->first;
code != 0;
code = code->next){
fprintf(out, "\"%.*s\",\n", string_expand(code->name));
counter += 1;
}
fprintf(out, "};\n");
}
////////////////////////////////
function Event_Code*
add_code(Arena *arena, Event_Code_List *list, String_Const_u8 name){
Event_Code *code = push_array(arena, Event_Code, 1);
sll_queue_push(list->first, list->last, code);
list->count;
code->name = push_string_copy(arena, name);
return(code);
}
function Event_Code_List
make_key_list(Arena *arena){
Event_Code_List list = {};
list.code_prefix = string_u8_litexpr("KeyCode");
list.name_table = string_u8_litexpr("key_code_name");
for (u32 i = 'A'; i <= 'Z'; i += 1){
u8 c = (u8)i;
add_code(arena, &list, SCu8(&c, 1));
}
for (u32 i = '0'; i <= '9'; i += 1){
u8 c = (u8)i;
add_code(arena, &list, SCu8(&c, 1));
}
add_code(arena, &list, string_u8_litexpr("Space"));
add_code(arena, &list, string_u8_litexpr("Tick"));
add_code(arena, &list, string_u8_litexpr("Minus"));
add_code(arena, &list, string_u8_litexpr("Equal"));
add_code(arena, &list, string_u8_litexpr("LeftBracket"));
add_code(arena, &list, string_u8_litexpr("RightBracket"));
add_code(arena, &list, string_u8_litexpr("Semicolon"));
add_code(arena, &list, string_u8_litexpr("Quote"));
add_code(arena, &list, string_u8_litexpr("Comma"));
add_code(arena, &list, string_u8_litexpr("Period"));
add_code(arena, &list, string_u8_litexpr("ForwardSlash"));
add_code(arena, &list, string_u8_litexpr("BackwardSlash"));
add_code(arena, &list, string_u8_litexpr("Tab"));
add_code(arena, &list, string_u8_litexpr("Escape"));
add_code(arena, &list, string_u8_litexpr("Pause"));
add_code(arena, &list, string_u8_litexpr("Up"));
add_code(arena, &list, string_u8_litexpr("Down"));
add_code(arena, &list, string_u8_litexpr("Left"));
add_code(arena, &list, string_u8_litexpr("Right"));
add_code(arena, &list, string_u8_litexpr("Backspace"));
add_code(arena, &list, string_u8_litexpr("Return"));
add_code(arena, &list, string_u8_litexpr("Delete"));
add_code(arena, &list, string_u8_litexpr("Insert"));
add_code(arena, &list, string_u8_litexpr("Home"));
add_code(arena, &list, string_u8_litexpr("End"));
add_code(arena, &list, string_u8_litexpr("PageUp"));
add_code(arena, &list, string_u8_litexpr("PageDown"));
add_code(arena, &list, string_u8_litexpr("CapsLock"));
add_code(arena, &list, string_u8_litexpr("NumLock"));
add_code(arena, &list, string_u8_litexpr("ScrollLock"));
add_code(arena, &list, string_u8_litexpr("Menu"));
add_code(arena, &list, string_u8_litexpr("Shift"));
add_code(arena, &list, string_u8_litexpr("Control"));
add_code(arena, &list, string_u8_litexpr("Alt"));
add_code(arena, &list, string_u8_litexpr("Command"));
for (u32 i = 1; i <= 24; i += 1){
add_code(arena, &list, push_u8_stringf(arena, "F%d", i));
}
for (u32 i = '0'; i <= '9'; i += 1){
add_code(arena, &list, push_u8_stringf(arena, "NumPad%c", i));
}
add_code(arena, &list, string_u8_litexpr("NumPadStar"));
add_code(arena, &list, string_u8_litexpr("NumPadPlus"));
add_code(arena, &list, string_u8_litexpr("NumPadMinus"));
add_code(arena, &list, string_u8_litexpr("NumPadDot"));
add_code(arena, &list, string_u8_litexpr("NumPadSlash"));
for (i32 i = 0; i < 30; i += 1){
add_code(arena, &list, push_u8_stringf(arena, "Ex%d", i));
}
return(list);
}
function Event_Code_List
make_mouse_list(Arena *arena){
Event_Code_List list = {};
list.code_prefix = string_u8_litexpr("MouseCode");
list.name_table = string_u8_litexpr("mouse_code_name");
add_code(arena, &list, string_u8_litexpr("Left"));
add_code(arena, &list, string_u8_litexpr("Middle"));
add_code(arena, &list, string_u8_litexpr("Right"));
return(list);
}
function Event_Code_List
make_core_list(Arena *arena){
Event_Code_List list = {};
list.code_prefix = string_u8_litexpr("CoreCode");
list.name_table = string_u8_litexpr("core_code_name");
add_code(arena, &list, string_u8_litexpr("Startup"));
add_code(arena, &list, string_u8_litexpr("Animate"));
add_code(arena, &list, string_u8_litexpr("ClickActivateView"));
add_code(arena, &list, string_u8_litexpr("ClickDeactivateView"));
add_code(arena, &list, string_u8_litexpr("TryExit"));
add_code(arena, &list, string_u8_litexpr("FileExternallyModified"));
add_code(arena, &list, string_u8_litexpr("NewClipboardContents"));
return(list);
}
////////////////////////////////
int
main(void){
Arena arena = make_arena_malloc();
Event_Code_List key_list = make_key_list(&arena);
Event_Code_List mouse_list = make_mouse_list(&arena);
Event_Code_List core_list = make_core_list(&arena);
String_Const_u8 path_to_self = string_u8_litexpr(__FILE__);
path_to_self = string_remove_last_folder(path_to_self);
String_Const_u8 file_name =
push_u8_stringf(&arena, "%.*scustom/generated/4coder_event_codes.h",
string_expand(path_to_self));
FILE *out = fopen((char*)file_name.str, "wb");
if (out == 0){
printf("could not open output file '%s'\n", file_name.str);
exit(1);
}
generate_codes(&arena, &key_list, out);
generate_codes(&arena, &mouse_list, out);
generate_codes(&arena, &core_list, out);
fclose(out);
return(0);
}
// BOTTOM

42
code/4ed_graphics_api.cpp Normal file
View File

@ -0,0 +1,42 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 02.10.2019
*
* Graphics API definition program.
*
*/
// TOP
#include "4ed_api_definition_main.cpp"
function API_Definition*
define_api(Arena *arena){
API_Definition *api = begin_api(arena, "graphics");
{
API_Call *call = api_call(arena, api, "get_texture", "u32");
api_param(arena, call, "Vec3_i32", "dim");
api_param(arena, call, "Texture_Kind", "texture_kind");
}
{
API_Call *call = api_call(arena, api, "fill_texture", "b32");
api_param(arena, call, "Texture_Kind", "texture_kind");
api_param(arena, call, "u32", "texture");
api_param(arena, call, "Vec3_i32", "p");
api_param(arena, call, "Vec3_i32", "dim");
api_param(arena, call, "void*", "data");
}
return(api);
}
function Generated_Group
get_api_group(void){
return(GeneratedGroup_Core);
}
// BOTTOM

447
code/4ed_history.cpp Normal file
View File

@ -0,0 +1,447 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 24.03.2018
*
* History
*/
// TOP
internal Node*
history__to_node(Node *sentinel, i32 index){
Node *result = 0;
i32 counter = 0;
Node *it = sentinel;
do{
if (counter == index){
result = it;
break;
}
counter += 1;
it = it->next;
} while (it != sentinel);
return(result);
}
internal void
history__push_back_record_ptr(Base_Allocator *allocator, Record_Ptr_Lookup_Table *lookup, Record *record){
if (lookup->records == 0 || lookup->count == lookup->max){
i32 new_max = clamp_bot(1024, lookup->max*2);
String_Const_u8 new_memory = base_allocate(allocator, sizeof(Record*)*new_max);
Record **new_records = (Record**)new_memory.str;
block_copy(new_records, lookup->records, sizeof(*new_records)*lookup->count);
if (lookup->records != 0){
base_free(allocator, lookup->records);
}
lookup->records = new_records;
lookup->max = new_max;
}
Assert(lookup->count < lookup->max);
lookup->records[lookup->count] = record;
lookup->count += 1;
}
internal void
history__shrink_array(Record_Ptr_Lookup_Table *lookup, i32 new_count){
Assert(0 <= new_count && new_count <= lookup->count);
lookup->count = new_count;
}
internal void
history__merge_record_ptr_range_to_one_ptr(Record_Ptr_Lookup_Table *lookup, i32 first_one_based, i32 last_one_based, Record *record){
i32 first = first_one_based - 1;
i32 one_past_last = last_one_based;
Assert(0 <= first && first <= one_past_last && one_past_last <= lookup->count);
if (first < one_past_last){
i32 shift = 1 + first - one_past_last;
block_copy(lookup->records + one_past_last + shift, lookup->records + one_past_last, lookup->count - one_past_last);
lookup->count += shift;
}
lookup->records[first] = record;
}
internal Node*
history__to_node(History *history, i32 index){
Node *result = 0;
if (index == 0){
result = &history->records;
}
else if (0 < index && index <= history->record_count){
Record_Ptr_Lookup_Table *lookup = &history->record_lookup;
Assert(lookup->count == history->record_count);
result = &lookup->records[index - 1]->node;
}
return(result);
}
////////////////////////////////
internal Record*
history__allocate_record(History *history){
Node *sentinel = &history->free_records;
Node *new_node = sentinel->next;
if (new_node == sentinel){
i32 new_record_count = 1024;
String_Const_u8 new_memory = base_allocate(&history->heap_wrapper, sizeof(Record)*new_record_count);
void *memory = new_memory.str;
Record *new_record = (Record*)memory;
sentinel->next = &new_record->node;
new_record->node.prev = sentinel;
for (i32 i = 1; i < new_record_count; i += 1, new_record += 1){
new_record[0].node.next = &new_record[1].node;
new_record[1].node.prev = &new_record[0].node;
}
new_record[0].node.next = sentinel;
sentinel->prev = &new_record[0].node;
new_node = &((Record*)memory)->node;
}
dll_remove(new_node);
Record *record = CastFromMember(Record, node, new_node);
block_zero_struct(record);
return(record);
}
internal void
global_history_init(Global_History *global_history){
global_history->edit_number_counter = 0;
global_history->edit_grouping_counter = 0;
}
internal i32
global_history_get_edit_number(Global_History *global_history){
i32 result = global_history->edit_number_counter;
if (global_history->edit_grouping_counter == 0){
global_history->edit_number_counter += 1;
}
return(result);
}
internal void
global_history_adjust_edit_grouping_counter(Global_History *global_history, i32 adjustment){
i32 original = global_history->edit_grouping_counter;
global_history->edit_grouping_counter = clamp_bot(0, global_history->edit_grouping_counter + adjustment);
if (global_history->edit_grouping_counter == 0 && original > 0){
global_history->edit_number_counter += 1;
}
}
internal void
history_init(Thread_Context *tctx, Models *models, History *history){
history->activated = true;
history->arena = make_arena_system();
heap_init(&history->heap, tctx->allocator);
history->heap_wrapper = base_allocator_on_heap(&history->heap);
dll_init_sentinel(&history->free_records);
dll_init_sentinel(&history->records);
history->record_count = 0;
block_zero_struct(&history->record_lookup);
}
internal b32
history_is_activated(History *history){
return(history->activated);
}
internal void
history_free(Thread_Context *tctx, History *history){
if (history->activated){
linalloc_clear(&history->arena);
heap_free_all(&history->heap);
block_zero_struct(history);
}
}
internal i32
history_get_record_count(History *history){
i32 result = 0;
if (history->activated){
result = history->record_count;
}
return(result);
}
internal Record*
history_get_record(History *history, i32 index){
Record *result = 0;
if (history->activated){
Node *node = history__to_node(history, index);
if (node != 0){
result = CastFromMember(Record, node, node);
}
}
return(result);
}
internal Record*
history_get_sub_record(Record *record, i32 sub_index_one_based){
Record *result = 0;
if (record->kind == RecordKind_Group){
if (0 < sub_index_one_based && sub_index_one_based <= record->group.count){
Node *node = history__to_node(&record->group.children, sub_index_one_based);
if (node != 0){
result = CastFromMember(Record, node, node);
}
}
}
return(result);
}
internal Record*
history_get_dummy_record(History *history){
Record *result = 0;
if (history->activated){
result = CastFromMember(Record, node, &history->records);
}
return(result);
}
internal void
history__stash_record(History *history, Record *new_record){
Assert(history->record_lookup.count == history->record_count);
dll_insert_back(&history->records, &new_record->node);
history->record_count += 1;
history__push_back_record_ptr(&history->heap_wrapper, &history->record_lookup, new_record);
Assert(history->record_lookup.count == history->record_count);
}
internal void
history__free_single_node(History *history, Node *node){
dll_remove(node);
dll_insert(&history->free_records, node);
}
internal void
history__free_nodes(History *history, i32 first_index, Node *first_node, Node *last_node){
if (first_node == last_node){
history__free_single_node(history, first_node);
}
else{
{
Node *left = first_node->prev;
Node *right = last_node->next;
left->next = right;
right->prev = left;
}
{
Node *left = &history->free_records;
Node *right = left->next;
left->next = first_node;
first_node->prev = left;
right->prev = last_node;
last_node->next = right;
}
}
Assert(first_index != 0);
history->record_count = first_index - 1;
history__shrink_array(&history->record_lookup, history->record_count);
}
internal void
history_record_edit(Global_History *global_history, History *history, Gap_Buffer *buffer,
i64 pos_before_edit, Edit edit){
if (history->activated){
Assert(history->record_lookup.count == history->record_count);
Record *new_record = history__allocate_record(history);
history__stash_record(history, new_record);
new_record->restore_point = begin_temp(&history->arena);
if (pos_before_edit >= 0){
new_record->pos_before_edit = pos_before_edit;
}
else{
new_record->pos_before_edit = edit.range.min;
}
new_record->edit_number = global_history_get_edit_number(global_history);
new_record->kind = RecordKind_Single;
new_record->single.forward_text = push_string_copy(&history->arena, edit.text);
new_record->single.backward_text = buffer_stringify(&history->arena, buffer, edit.range);
new_record->single.first = edit.range.first;
Assert(history->record_lookup.count == history->record_count);
}
}
internal void
history_dump_records_after_index(History *history, i32 index){
if (history->activated){
Assert(history->record_lookup.count == history->record_count);
Assert(0 <= index && index <= history->record_count);
if (index < history->record_count){
Node *node = history__to_node(history, index);
Node *first_node_to_clear = node->next;
Node *sentinel = &history->records;
Assert(first_node_to_clear != sentinel);
Record *first_record_to_clear = CastFromMember(Record, node, first_node_to_clear);
end_temp(first_record_to_clear->restore_point);
Node *last_node_to_clear = sentinel->prev;
history__free_nodes(history, index + 1, first_node_to_clear, last_node_to_clear);
}
Assert(history->record_lookup.count == history->record_count);
}
}
internal void
history__optimize_group(Arena *scratch, History *history, Record *record){
Assert(record->kind == RecordKind_Group);
for (;;){
Record *right = CastFromMember(Record, node, record->group.children.prev);
if (record->group.count == 1){
Record *child = right;
record->restore_point = child->restore_point;
record->pos_before_edit = child->pos_before_edit;
record->edit_number = child->edit_number;
record->kind = RecordKind_Single;
record->single = child->single;
// NOTE(allen): don't use "free" because the child node is no longer linked
// to a valid sentinel, and removing it first (as free does) will mess with
// the data in record->single
dll_insert(&history->free_records, &child->node);
break;
}
Record *left = CastFromMember(Record, node, right->node.prev);
if (right->kind == RecordKind_Single && left->kind == RecordKind_Single){
b32 do_merge = false;
Temp_Memory temp = begin_temp(scratch);
i64 new_length_forward = left->single.forward_text.size + right->single.forward_text.size ;
i64 new_length_backward = left->single.backward_text.size + right->single.backward_text.size;
String_Const_u8 merged_forward = {};
String_Const_u8 merged_backward = {};
i64 merged_first = 0;
if (left->single.first + (i64)left->single.forward_text.size == right->single.first){
do_merge = true;
merged_forward = push_u8_stringf(scratch, "%.*s%.*s",
string_expand(left->single.forward_text),
string_expand(right->single.forward_text));
merged_backward = push_u8_stringf(scratch, "%.*s%.*s",
string_expand(left->single.backward_text),
string_expand(right->single.backward_text));
merged_first = left->single.first;
}
else if (right->single.first + (i64)right->single.backward_text.size == left->single.first){
do_merge = true;
merged_forward = push_u8_stringf(scratch, "%.*s%.*s",
string_expand(right->single.forward_text),
string_expand(left->single.forward_text));
merged_backward = push_u8_stringf(scratch, "%.*s%.*s",
string_expand(right->single.backward_text),
string_expand(left->single.backward_text));
merged_first = right->single.first;
}
else{
break;
}
if (do_merge){
end_temp(left->restore_point);
left->edit_number = right->edit_number;
left->single.first = merged_first;
left->single.forward_text = push_string_copy(&history->arena, merged_forward);
left->single.backward_text = push_string_copy(&history->arena, merged_backward);
history__free_single_node(history, &right->node);
record->group.count -= 1;
}
end_temp(temp);
}
else{
break;
}
}
}
internal void
history_merge_records(Arena *scratch, History *history, i32 first_index, i32 last_index){
if (history->activated){
Assert(history->record_lookup.count == history->record_count);
Assert(first_index < last_index);
Node *first_node = history__to_node(history, first_index);
Node *last_node = history__to_node(history, last_index );
Assert(first_node != &history->records && first_node != 0);
Assert(last_node != &history->records && last_node != 0);
Record *new_record = history__allocate_record(history);
// NOTE(allen): here we remove (last_index - first_index + 1) nodes, and insert 1 node
// which simplifies to this:
history->record_count -= last_index - first_index;
Node *left = first_node->prev;
dll_remove_multiple(first_node, last_node);
dll_insert(left, &new_record->node);
Record *first_record = CastFromMember(Record, node, first_node);
Record *last_record = CastFromMember(Record, node, last_node);
new_record->restore_point = first_record->restore_point;
new_record->pos_before_edit = first_record->pos_before_edit;
new_record->edit_number = last_record->edit_number;
new_record->kind = RecordKind_Group;
Node *new_sentinel = &new_record->group.children;
dll_init_sentinel(new_sentinel);
i32 count = 0;
for (Node *node = first_node, *next = 0;
node != 0;
node = next){
next = node->next;
Record *record = CastFromMember(Record, node, node);
switch (record->kind){
case RecordKind_Single:
{
dll_insert_back(new_sentinel, &record->node);
count += 1;
}break;
case RecordKind_Group:
{
Node *first = record->group.children.next;
Node *last = record->group.children.prev;
Assert(first != &record->group.children);
Assert(last != &record->group.children);
dll_insert_multiple_back(new_sentinel, first, last);
count += record->group.count;
// TODO(allen): free the record for the old group!?
}break;
default:
{
InvalidPath;
}break;
}
}
new_record->group.count = count;
history__merge_record_ptr_range_to_one_ptr(&history->record_lookup, first_index, last_index, new_record);
Assert(history->record_lookup.count == history->record_count);
if (first_index == history->record_count){
history__optimize_group(scratch, history, new_record);
}
}
}
// BOTTOM

64
code/4ed_history.h Normal file
View File

@ -0,0 +1,64 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 24.03.2018
*
* History
*
*/
// TOP
#if !defined(FRED_HISTORY_H)
#define FRED_HISTORY_H
struct Record_Batch_Slot{
i64 length_forward;
i64 length_backward;
i32 first;
};
struct Record{
Node node;
Temp_Memory restore_point;
i64 pos_before_edit;
i32 edit_number;
Record_Kind kind;
union{
struct{
String_Const_u8 forward_text;
String_Const_u8 backward_text;
i64 first;
} single;
struct{
Node children;
i32 count;
} group;
};
};
struct Record_Ptr_Lookup_Table{
Record **records;
i32 count;
i32 max;
};
struct History{
b32 activated;
Arena arena;
Heap heap;
Base_Allocator heap_wrapper;
Node free_records;
Node records;
i32 record_count;
Record_Ptr_Lookup_Table record_lookup;
};
struct Global_History{
i32 edit_number_counter;
i32 edit_grouping_counter;
};
#endif
// BOTTOM

View File

@ -0,0 +1,84 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 03.01.2017
*
* Hot_Directory data structure for 4coder
*
*/
// TOP
internal void
hot_directory_clean_end(Hot_Directory *hot_directory){
String_Const_u8 str = hot_directory->string;
if (!character_is_slash(string_get_character(str, str.size - 1))){
hot_directory->string = string_remove_last_folder(str);
}
}
internal i32
hot_directory_quick_partition(File_Info **infos, i32 start, i32 pivot){
File_Info **p = infos + pivot;
File_Info **a = infos + start;
for (i32 i = start; i < pivot; ++i, ++a){
b32 p_folder = (HasFlag((**p).attributes.flags, FileAttribute_IsDirectory));
b32 a_folder = (HasFlag((**a).attributes.flags, FileAttribute_IsDirectory));
i32 comp = p_folder - a_folder;
if (comp == 0){
comp = string_compare((**a).file_name, (**p).file_name);
}
if (comp < 0){
Swap(File_Info*, *a, infos[start]);
++start;
}
}
Swap(File_Info*, *p, infos[start]);
return(start);
}
internal void
hot_directory_quick_sort(File_Info **infos, i32 start, i32 pivot){
i32 mid = hot_directory_quick_partition(infos, start, pivot);
if (start < mid-1) hot_directory_quick_sort(infos, start, mid-1);
if (mid+1 < pivot) hot_directory_quick_sort(infos, mid+1, pivot);
}
internal void
hot_directory_fixup(Hot_Directory *hot_directory){
File_List *files = &hot_directory->file_list;
if (files->count >= 2){
hot_directory_quick_sort(files->infos, 0, files->count - 1);
}
}
internal void
hot_directory_set(Hot_Directory *hot_directory, String_Const_u8 str){
linalloc_clear(&hot_directory->arena);
hot_directory->string = push_string_copy(&hot_directory->arena, str);
hot_directory->canonical = system_get_canonical(&hot_directory->arena, str);
hot_directory->file_list = system_get_file_list(&hot_directory->arena, hot_directory->canonical);
}
internal void
hot_directory_reload(Arena *scratch, Hot_Directory *hot_directory){
Temp_Memory temp = begin_temp(scratch);
String_Const_u8 string = push_string_copy(scratch, hot_directory->string);
hot_directory_set(hot_directory, string);
end_temp(temp);
}
internal void
hot_directory_init(Arena *scratch, Hot_Directory *hot_directory, String_Const_u8 directory){
hot_directory->arena = make_arena_system();
Temp_Memory temp = begin_temp(scratch);
String_Const_u8 dir = directory;
if (!character_is_slash(string_get_character(directory, directory.size - 1))){
dir = push_u8_stringf(scratch, "%.*s/", string_expand(directory));
}
hot_directory_set(hot_directory, dir);
end_temp(temp);
}
// BOTTOM

25
code/4ed_hot_directory.h Normal file
View File

@ -0,0 +1,25 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 24.01.2018
*
* Buffer types
*
*/
// TOP
#if !defined(FRED_HOT_DIRECTORY_H)
#define FRED_HOT_DIRECTORY_H
struct Hot_Directory{
Arena arena;
String_Const_u8 string;
String_Const_u8 canonical;
File_List file_list;
};
#endif
// BOTTOM

497
code/4ed_layout.cpp Normal file
View File

@ -0,0 +1,497 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 19.08.2015
*
* Panel layout functions
*
*/
// TOP
internal Panel_Split
make_panel_split(Panel_Split_Kind kind, i32 v){
Panel_Split split = {};
split.kind = kind;
split.v_i32 = v;
return(split);
}
internal Panel_Split
make_panel_split(Panel_Split_Kind kind, f32 v){
Panel_Split split = {};
split.kind = kind;
split.v_f32 = v;
return(split);
}
internal Panel_Split
make_panel_split_50_50(void){
return(make_panel_split(PanelSplitKind_Ratio_Min, 0.5f));
}
internal Panel*
layout__alloc_panel(Layout *layout){
Panel *panel = 0;
Node *node = layout->free_panels.next;
if (node != &layout->free_panels){
dll_remove(node);
panel = CastFromMember(Panel, node, node);
}
return(panel);
}
internal void
layout__free_panel(Layout *layout, Panel *panel){
Assert(panel != layout->active_panel);
Assert(panel != layout->root);
dll_remove(&panel->node);
dll_insert(&layout->free_panels, &panel->node);
panel->kind = PanelKind_Unused;
}
internal void
layout__set_panel_rectangle(Layout *layout, Panel *panel, Rect_i32 rect){
panel->rect_full = rect;
panel->rect_inner = rect_inner(rect, layout->margin);
}
internal i32
layout__evaluate_split(Panel_Split split, i32 v0, i32 v1){
i32 v = 0;
switch (split.kind){
case PanelSplitKind_Ratio_Min:
{
v = i32_round32(lerp((f32)v0, split.v_f32, (f32)v1));
}break;
case PanelSplitKind_Ratio_Max:
{
v = i32_round32(lerp((f32)v1, split.v_f32, (f32)v0));
}break;
case PanelSplitKind_FixedPixels_Min:
{
v = clamp_top(v0 + split.v_i32, v1);
}break;
case PanelSplitKind_FixedPixels_Max:
{
v = clamp_bot(v0, v1 - split.v_i32);
}break;
}
return(v);
}
internal void
layout_propogate_sizes_down_from_node(Layout *layout, Panel *panel){
if (panel->kind == PanelKind_Intermediate){
Panel *tl_panel = panel->tl_panel;
Panel *br_panel = panel->br_panel;
Rect_i32 r1 = panel->rect_full;
Rect_i32 r2 = panel->rect_full;
if (panel->vertical_split){
i32 x_pos = layout__evaluate_split(panel->split, r1.x0, r1.x1);
r1.x1 = x_pos;
r2.x0 = x_pos;
}
else{
i32 y_pos = layout__evaluate_split(panel->split, r1.y0, r1.y1);
r1.y1 = y_pos;
r2.y0 = y_pos;
}
layout__set_panel_rectangle(layout, tl_panel, r1);
layout__set_panel_rectangle(layout, br_panel, r2);
layout_propogate_sizes_down_from_node(layout, tl_panel);
layout_propogate_sizes_down_from_node(layout, br_panel);
}
}
internal i32
layout_get_open_panel_count(Layout *layout){
return(layout->open_panel_count);
}
internal Panel*
layout_get_first_open_panel(Layout *layout){
Panel *panel = CastFromMember(Panel, node, layout->open_panels.next);
if (panel != 0 && &panel->node == &layout->open_panels){
panel = 0;
}
AssertImplies(panel != 0, panel->kind == PanelKind_Final);
return(panel);
}
internal Panel*
layout_get_last_open_panel(Layout *layout){
Panel *panel = CastFromMember(Panel, node, layout->open_panels.prev);
if (panel != 0 && &panel->node == &layout->open_panels){
panel = 0;
}
AssertImplies(panel != 0, panel->kind == PanelKind_Final);
return(panel);
}
internal Panel*
layout_get_next_open_panel(Layout *layout, Panel *panel){
panel = CastFromMember(Panel, node, panel->node.next);
if (&panel->node == &layout->open_panels){
panel = 0;
}
AssertImplies(panel != 0, panel->kind == PanelKind_Final);
return(panel);
}
internal Panel*
layout_get_prev_open_panel(Layout *layout, Panel *panel){
panel = CastFromMember(Panel, node, panel->node.prev);
if (&panel->node == &layout->open_panels){
panel = 0;
}
AssertImplies(panel != 0, panel->kind == PanelKind_Final);
return(panel);
}
internal Panel*
layout_get_active_panel(Layout *layout){
return(layout->active_panel);
}
internal b32
layout_split_panel(Layout *layout, Panel *panel, b32 vertical_split, Panel **new_panel_out){
b32 result = false;
if (layout->open_panel_count < layout->open_panel_max_count){
Panel *min_panel = layout__alloc_panel(layout);
Panel *max_panel = layout__alloc_panel(layout);
if (panel->kind == PanelKind_Final){
dll_remove(&panel->node);
dll_insert(&layout->intermediate_panels, &panel->node);
// init min_panel
dll_insert(&layout->open_panels, &min_panel->node);
panel->view->panel = min_panel;
min_panel->parent = panel;
min_panel->kind = PanelKind_Final;
min_panel->view = panel->view;
}
else{
// init min_panel
dll_insert(&layout->intermediate_panels, &min_panel->node);
panel->tl_panel->parent = min_panel;
panel->br_panel->parent = min_panel;
min_panel->parent = panel;
min_panel->kind = PanelKind_Intermediate;
min_panel->tl_panel = panel->tl_panel;
min_panel->br_panel = panel->br_panel;
min_panel->vertical_split = panel->vertical_split;
min_panel->split = panel->split;
}
// init max_panel
dll_insert(&layout->open_panels, &max_panel->node);
*new_panel_out = max_panel;
max_panel->parent = panel;
max_panel->kind = PanelKind_Final;
max_panel->view = 0;
// modify panel
panel->kind = PanelKind_Intermediate;
panel->tl_panel = min_panel;
panel->br_panel = max_panel;
panel->vertical_split = vertical_split;
panel->split = make_panel_split_50_50();
// propogate rectangle sizes down from the new intermediate to
// resize the panel and the new panel.
layout_propogate_sizes_down_from_node(layout, panel);
// update layout state
layout->open_panel_count += 1;
layout->active_panel = max_panel;
layout->panel_state_dirty = true;
result = true;
}
return(result);
}
internal b32
layout_close_panel(Layout *layout, Panel *panel){
b32 result = false;
if (layout->open_panel_count > 1){
Panel *parent = panel->parent;
Assert(parent != 0);
// find sibling
Panel *sibling = 0;
if (parent->tl_panel == panel){
sibling = parent->br_panel;
}
else{
Assert(parent->br_panel == panel);
sibling = parent->tl_panel;
}
// update layout state
if (layout->active_panel == panel){
Panel *new_active = sibling;
for (;new_active->kind == PanelKind_Intermediate;){
new_active = new_active->br_panel;
}
layout->active_panel = new_active;
}
layout->panel_state_dirty = true;
layout->open_panel_count -= 1;
// link grand parent and sibling
Panel *g_parent = parent->parent;
sibling->parent = g_parent;
if (g_parent != 0){
if (g_parent->tl_panel == parent){
g_parent->tl_panel = sibling;
}
else{
Assert(g_parent->br_panel == parent);
g_parent->br_panel = sibling;
}
}
else{
Assert(parent == layout->root);
layout->root = sibling;
}
// set sibling's size
sibling->screen_region = parent->screen_region;
// set the sizes down stream of sibling
layout_propogate_sizes_down_from_node(layout, sibling);
// free panel and parent
layout__free_panel(layout, panel);
layout__free_panel(layout, parent);
result = true;
}
return(result);
}
internal Panel*
layout_initialize(Arena *arena, Layout *layout){
i32 panel_alloc_count = MAX_VIEWS*2 - 1;
Panel *panels = push_array(arena, Panel, panel_alloc_count);
block_zero(panels, sizeof(*panels)*panel_alloc_count);
layout->panel_first = panels;
layout->panel_one_past_last = panels + panel_alloc_count;
layout->margin = 3;
layout->open_panel_count = 0;
layout->open_panel_max_count = MAX_VIEWS;
dll_init_sentinel(&layout->open_panels);
dll_init_sentinel(&layout->intermediate_panels);
Panel *panel = panels;
layout->free_panels.next = &panel->node;
panel->node.prev = &layout->free_panels;
for (i32 i = 1; i < panel_alloc_count; i += 1, panel += 1){
panel[1].node.prev = &panel[0].node;
panel[0].node.next = &panel[1].node;
}
panel->node.next = &layout->free_panels;
layout->free_panels.prev = &panel->node;
panel = layout__alloc_panel(layout);
panel->parent = 0;
panel->kind = PanelKind_Final;
panel->view = 0;
block_zero_struct(&panel->screen_region);
dll_insert(&layout->open_panels, &panel->node);
layout->open_panel_count += 1;
layout->root = panel;
layout->active_panel = panel;
layout->panel_state_dirty = true;
return(panel);
}
internal void
layout_set_margin(Layout *layout, i32 margin){
if (layout->margin != margin){
layout->margin = margin;
layout__set_panel_rectangle(layout, layout->root, Ri32(0, 0, layout->full_dim.x, layout->full_dim.y));
layout_propogate_sizes_down_from_node(layout, layout->root);
layout->panel_state_dirty = true;
}
}
internal void
layout_set_root_size(Layout *layout, Vec2_i32 dim){
if (layout->full_dim != dim){
layout->full_dim = dim;
layout__set_panel_rectangle(layout, layout->root, Ri32(0, 0, dim.x, dim.y));
layout_propogate_sizes_down_from_node(layout, layout->root);
layout->panel_state_dirty = true;
}
}
internal Vec2_i32
layout_get_root_size(Layout *layout){
return(layout->full_dim);
}
internal i32
layout_get_absolute_position_of_split(Panel *panel){
i32 pos = 0;
if (panel->vertical_split){
pos = layout__evaluate_split(panel->split, panel->rect_full.x0, panel->rect_full.x1);
}
else{
pos = layout__evaluate_split(panel->split, panel->rect_full.y0, panel->rect_full.y1);
}
return(pos);
}
internal Range_i32
layout__get_limiting_range_on_split_children(i32 mid, b32 vertical_split, Panel *panel, Range_i32 range){
if (panel->kind == PanelKind_Intermediate){
if (vertical_split == panel->vertical_split){
i32 pos = layout_get_absolute_position_of_split(panel);
if (mid < pos && pos < range.max){
range.max = pos;
}
else if (range.min < pos && pos < mid){
range.min = pos;
}
}
range = layout__get_limiting_range_on_split_children(mid, vertical_split, panel->tl_panel, range);
range = layout__get_limiting_range_on_split_children(mid, vertical_split, panel->br_panel, range);
}
return(range);
}
internal Range_i32
layout_get_limiting_range_on_split(Layout *layout, Panel *panel){
// root level min max
Range_i32 range = {};
if (panel->vertical_split){
range.max = layout->full_dim.x;
}
else{
range.max = layout->full_dim.y;
}
// get mid
i32 mid = layout_get_absolute_position_of_split(panel);
// parents min max
for (Panel *panel_it = panel;
panel_it != 0;
panel_it = panel_it->parent){
if (panel->vertical_split == panel_it->vertical_split){
i32 pos = layout_get_absolute_position_of_split(panel_it);
if (mid < pos && pos < range.max){
range.max = pos;
}
else if (range.min < pos && pos < mid){
range.min = pos;
}
}
}
// children min max
if (panel->kind == PanelKind_Intermediate){
range = layout__get_limiting_range_on_split_children(mid, panel->vertical_split, panel->tl_panel, range);
range = layout__get_limiting_range_on_split_children(mid, panel->vertical_split, panel->br_panel, range);
}
return(range);
}
internal void
layout__reverse_evaluate_panel_split(Panel *panel, i32 position){
i32 v0 = 0;
i32 v1 = 0;
if (panel->vertical_split){
v0 = panel->rect_full.x0;
v1 = panel->rect_full.x1;
}
else{
v0 = panel->rect_full.y0;
v1 = panel->rect_full.y1;
}
switch (panel->split.kind){
case PanelSplitKind_Ratio_Min:
{
panel->split.v_f32 = unlerp((f32)v0, (f32)position, (f32)v1);
}break;
case PanelSplitKind_Ratio_Max:
{
panel->split.v_f32 = unlerp((f32)v1, (f32)position, (f32)v0);
}break;
case PanelSplitKind_FixedPixels_Min:
{
panel->split.v_i32 = clamp(v0, position, v1) - v0;
}break;
case PanelSplitKind_FixedPixels_Max:
{
panel->split.v_i32 = v1 - clamp(v0, position, v1);
}break;
}
}
internal void
layout__set_split_absolute_position_inner(Panel *panel){
if (panel->kind == PanelKind_Intermediate){
Rect_i32 r = panel->rect_full;
i32 position = 0;
if (panel->vertical_split){
position = layout__evaluate_split(panel->split, r.x0, r.x1);
}
else{
position = layout__evaluate_split(panel->split, r.y0, r.y1);
}
layout__reverse_evaluate_panel_split(panel, position);
}
}
internal void
layout_set_split_absolute_position(Layout *layout, Panel *panel, i32 absolute_position){
if (panel->kind == PanelKind_Intermediate){
layout__reverse_evaluate_panel_split(panel, absolute_position);
layout__set_split_absolute_position_inner(panel->tl_panel);
layout__set_split_absolute_position_inner(panel->br_panel);
layout_propogate_sizes_down_from_node(layout, panel);
layout->panel_state_dirty = true;
}
}
internal Panel_ID
panel_get_id(Layout *layout, Panel *panel){
Panel_ID id = 0;
if (layout->panel_first <= panel && panel < layout->panel_one_past_last){
id = (Panel_ID)(panel - layout->panel_first) + 1;
}
return(id);
}
////////////////////////////////
internal Panel*
imp_get_panel(Models *models, Panel_ID panel_id){
Layout *layout = &models->layout;
Panel *panel = layout->panel_first + panel_id - 1;
if (!(layout->panel_first <= panel && panel < layout->panel_one_past_last)){
panel = 0;
}
return(panel);
}
// BOTTOM

77
code/4ed_layout.h Normal file
View File

@ -0,0 +1,77 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 24.03.2018
*
* Panel layout structures
*
*/
// TOP
#if !defined(FRED_LAYOUT_H)
#define FRED_LAYOUT_H
struct Panel_Split{
Panel_Split_Kind kind;
union{
f32 v_f32;
i32 v_i32;
};
};
typedef i32 Panel_Kind;
enum{
PanelKind_Unused = 0,
PanelKind_Intermediate = 1,
PanelKind_Final = 2,
};
struct Panel{
Node node;
Panel *parent;
Panel_Kind kind;
union{
struct View *view;
struct{
struct Panel *tl_panel;
struct Panel *br_panel;
b32 vertical_split;
Panel_Split split;
};
};
union{
struct{
Rect_i32 rect_full;
Rect_i32 rect_inner;
} screen_region;
struct{
Rect_i32 rect_full;
Rect_i32 rect_inner;
};
};
};
struct Layout{
Node free_panels;
Node open_panels;
Node intermediate_panels;
Panel *root;
Panel *active_panel;
Panel *panel_first;
Panel *panel_one_past_last;
i32 margin;
i32 open_panel_count;
i32 open_panel_max_count;
Vec2_i32 full_dim;
b32 panel_state_dirty;
};
#endif
// BOTTOM

57
code/4ed_log.cpp Normal file
View File

@ -0,0 +1,57 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 14.08.2019
*
* Core logging implementation.
*
*/
// TOP
global Log global_log = {};
internal void
log_init(void){
global_log.mutex = system_mutex_make();
global_log.arena = make_arena_system();
}
internal b32
log_string(String_Const_u8 str){
b32 result = false;
i32 thread_id = system_thread_get_id();
if (global_log.disabled_thread_id != thread_id){
system_mutex_acquire(global_log.mutex);
string_list_push(&global_log.arena, &global_log.list, push_string_copy(&global_log.arena, str));
system_mutex_release(global_log.mutex);
result = true;
}
return(result);
}
internal void
output_file_append(Thread_Context *tctx, Models *models, Editing_File *file, String_Const_u8 value);
internal b32
log_flush(Thread_Context *tctx, Models *models){
b32 result = false;
system_mutex_acquire(global_log.mutex);
global_log.disabled_thread_id = system_thread_get_id();
if (global_log.list.total_size > 0){
String_Const_u8 text = string_list_flatten(&global_log.arena, global_log.list);
output_file_append(tctx, models, models->log_buffer, text);
result = true;
}
linalloc_clear(&global_log.arena);
block_zero_struct(&global_log.list);
global_log.disabled_thread_id = 0;
system_mutex_release(global_log.mutex);
return(result);
}
// BOTTOM

25
code/4ed_log.h Normal file
View File

@ -0,0 +1,25 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 14.08.2019
*
* Core logging structures.
*
*/
// TOP
#if !defined(FRED_LOG_H)
#define FRED_LOG_H
struct Log{
System_Mutex mutex;
Arena arena;
List_String_Const_u8 list;
volatile i32 disabled_thread_id;
b32 stdout_log_enabled;
};
#endif
// BOTTOM

77
code/4ed_mem.cpp Normal file
View File

@ -0,0 +1,77 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 30.08.2016
*
* Replacements for common memory block managing functions.
*/
// TOP
// TODO(allen): Make these as fast as possible
#if 0
internal void
block_zero(void *a, u64 size){
for (u8 *ptr = (u8*)a, *e = ptr + size; ptr < e; ptr += 1){
*ptr = 0;
}
}
internal void
block_fill_ones(void *a, u64 size){
for (u8 *ptr = (u8*)a, *e = ptr + size; ptr < e; ptr += 1){
*ptr = 0xFF;
}
}
internal void
block_copy(void *dst, void *src, u64 size){
for (u8 *d = (u8*)dst, *s = (u8*)src, *e = s + size; s < e; d += 1, s += 1){
*d = *s;
}
}
internal i32
block_compare(void *a, void *b, u64 size){
for (u8 *aptr = (u8*)a, *bptr = (u8*)b, *e = bptr + size; bptr < e; aptr += 1, bptr += 1){
i32 dif = (i32)*aptr - (i32)*bptr;
if (dif != 0){
return(dif > 0?1:-1);
}
}
return(0);
}
internal void
block_fill_u8(void *a, u64 size, u8 val){
for (u8 *ptr = (u8*)a, *e = ptr + size; ptr < e; ptr += 1){
*ptr = val;
}
}
internal void
block_fill_u16(void *a, u64 size, u16 val){
Assert(size%sizeof(u16) == 0);
u64 count = size/sizeof(u16);
for (u16 *ptr = (u16*)a, *e = ptr + count; ptr < e; ptr += 1){
*ptr = val;
}
}
internal void
block_fill_u32(void *a, u64 size, u32 val){
Assert(size%sizeof(u32) == 0);
u64 count = size/sizeof(u32);
for (u32 *ptr = (u32*)a, *e = ptr + count; ptr < e; ptr += 1){
*ptr = val;
}
}
internal void
block_fill_u64(void *a, u64 size, u64 val){
Assert(size%sizeof(u64) == 0);
u64 count = size/sizeof(u64);
for (u64 *ptr = (u64*)a, *e = ptr + count; ptr < e; ptr += 1){
*ptr = val;
}
}
#define block_zero_struct(s) block_zero((s), sizeof(*(s)))
#endif
// BOTTOM

351
code/4ed_render_target.cpp Normal file
View File

@ -0,0 +1,351 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 10.11.2017
*
* Render target function implementations.
*
*/
// TOP
internal void
draw__begin_new_group(Render_Target *target){
Render_Group *group = 0;
if (target->group_last != 0){
if (target->group_last->vertex_list.vertex_count == 0){
group = target->group_last;
}
}
if (group == 0){
group = push_array_zero(&target->arena, Render_Group, 1);
sll_queue_push(target->group_first, target->group_last, group);
}
group->face_id = target->current_face_id;
group->clip_box = target->current_clip_box;
}
internal Render_Vertex_Array_Node*
draw__extend_group_vertex_memory(Arena *arena, Render_Vertex_List *list, i32 size){
Render_Vertex_Array_Node *node = push_array_zero(arena, Render_Vertex_Array_Node, 1);
sll_queue_push(list->first, list->last, node);
node->vertices = push_array(arena, Render_Vertex, size);
node->vertex_max = size;
return(node);
}
internal void
draw__write_vertices_in_current_group(Render_Target *target, Render_Vertex *vertices, i32 count){
if (count > 0){
Render_Group *group = target->group_last;
if (group == 0){
draw__begin_new_group(target);
group = target->group_last;
}
Render_Vertex_List *list = &group->vertex_list;
Render_Vertex_Array_Node *last = list->last;
Render_Vertex *tail_vertex = 0;
i32 tail_count = 0;
if (last != 0){
tail_vertex = last->vertices + last->vertex_count;
tail_count = last->vertex_max - last->vertex_count;
}
i32 base_vertex_max = 64;
i32 transfer_count = clamp_top(count, tail_count);
if (transfer_count > 0){
block_copy_dynamic_array(tail_vertex, vertices, transfer_count);
last->vertex_count += transfer_count;
list->vertex_count += transfer_count;
base_vertex_max = last->vertex_max;
}
i32 count_left_over = count - transfer_count;
if (count_left_over > 0){
Render_Vertex *vertices_left_over = vertices + transfer_count;
i32 next_node_size = (base_vertex_max + count_left_over)*2;
Render_Vertex_Array_Node *memory = draw__extend_group_vertex_memory(&target->arena, list, next_node_size);
block_copy_dynamic_array(memory->vertices, vertices_left_over, count_left_over);
memory->vertex_count += count_left_over;
list->vertex_count += count_left_over;
}
}
}
internal void
draw__set_face_id(Render_Target *target, Face_ID face_id){
if (target->current_face_id != face_id){
if (target->current_face_id != 0){
target->current_face_id = face_id;
draw__begin_new_group(target);
}
else{
target->current_face_id = face_id;
for (Render_Group *group = target->group_first;
group != 0;
group = group->next){
group->face_id = face_id;
}
}
}
}
////////////////////////////////
internal Rect_f32
draw_set_clip(Render_Target *target, Rect_f32 clip_box){
Rect_f32 result = target->current_clip_box;
if (target->current_clip_box != clip_box){
target->current_clip_box = clip_box;
draw__begin_new_group(target);
}
return(result);
}
internal void
begin_frame(Render_Target *target, void *font_set){
linalloc_clear(&target->arena);
target->group_first = 0;
target->group_last = 0;
target->current_face_id = 0;
target->current_clip_box = Rf32(0, 0, (f32)target->width, (f32)target->height);
target->font_set = font_set;
}
internal void
begin_render_section(Render_Target *target, i32 frame_index, f32 literal_dt, f32 animation_dt){
target->frame_index = frame_index;
target->literal_dt = literal_dt;
target->animation_dt = animation_dt;
}
internal void
end_render_section(Render_Target *target){
}
////////////////////////////////
internal void
draw_rectangle_outline(Render_Target *target, Rect_f32 rect, f32 roundness, f32 thickness, u32 color){
if (roundness < epsilon_f32){
roundness = 0.f;
}
thickness = clamp_bot(1.f, thickness);
f32 half_thickness = thickness*0.5f;
Render_Vertex vertices[6] = {};
vertices[0].xy = V2f32(rect.x0, rect.y0);
vertices[1].xy = V2f32(rect.x1, rect.y0);
vertices[2].xy = V2f32(rect.x0, rect.y1);
vertices[3].xy = V2f32(rect.x1, rect.y0);
vertices[4].xy = V2f32(rect.x0, rect.y1);
vertices[5].xy = V2f32(rect.x1, rect.y1);
Vec2_f32 center = rect_center(rect);
for (i32 i = 0; i < ArrayCount(vertices); i += 1){
vertices[i].uvw = V3f32(center.x, center.y, roundness);
vertices[i].color = color;
vertices[i].half_thickness = half_thickness;
}
draw__write_vertices_in_current_group(target, vertices, ArrayCount(vertices));
}
internal void
draw_rectangle(Render_Target *target, Rect_f32 rect, f32 roundness, u32 color){
Vec2_f32 dim = rect_dim(rect);
draw_rectangle_outline(target, rect, roundness, Max(dim.x, dim.y), color);
}
internal void
draw_font_glyph(Render_Target *target, Face *face, u32 codepoint, Vec2_f32 p,
ARGB_Color color, Glyph_Flag flags, Vec2_f32 x_axis){
draw__set_face_id(target, face->id);
u16 glyph_index = 0;
if (!codepoint_index_map_read(&face->advance_map.codepoint_to_index,
codepoint, &glyph_index)){
glyph_index = 0;
}
Glyph_Bounds bounds = face->bounds[glyph_index];
Vec3_f32 texture_dim = face->texture_dim;
Render_Vertex vertices[6] = {};
Rect_f32 uv = bounds.uv;
vertices[0].uvw = V3f32(uv.x0, uv.y0, bounds.w);
vertices[1].uvw = V3f32(uv.x1, uv.y0, bounds.w);
vertices[2].uvw = V3f32(uv.x0, uv.y1, bounds.w);
vertices[5].uvw = V3f32(uv.x1, uv.y1, bounds.w);
Vec2_f32 y_axis = V2f32(-x_axis.y, x_axis.x);
Vec2_f32 x_min = bounds.xy_off.x0*x_axis;
Vec2_f32 x_max = bounds.xy_off.x1*x_axis;
Vec2_f32 y_min = bounds.xy_off.y0*y_axis;
Vec2_f32 y_max = bounds.xy_off.y1*y_axis;
Vec2_f32 p_x_min = p + x_min;
Vec2_f32 p_x_max = p + x_max;
vertices[0].xy = p_x_min + y_min;
vertices[1].xy = p_x_max + y_min;
vertices[2].xy = p_x_min + y_max;
vertices[5].xy = p_x_max + y_max;
#if 0
Vec2_f32 xy_min = p + bounds.xy_off.x0*x_axis + bounds.xy_off.y0*y_axis;
Vec2_f32 xy_max = p + bounds.xy_off.x1*x_axis + bounds.xy_off.y1*y_axis;
vertices[0].xy = V2f32(xy_min.x, xy_min.y);
vertices[1].xy = V2f32(xy_max.x, xy_min.y);
vertices[2].xy = V2f32(xy_min.x, xy_max.y);
vertices[5].xy = V2f32(xy_max.x, xy_max.y);
#endif
#if 0
if (!HasFlag(flags, GlyphFlag_Rotate90)){
Rect_f32 xy = Rf32(p + bounds.xy_off.p0, p + bounds.xy_off.p1);
vertices[0].xy = V2f32(xy.x0, xy.y1);
vertices[0].uvw = V3f32(uv.x0, uv.y1, bounds.w);
vertices[1].xy = V2f32(xy.x1, xy.y1);
vertices[1].uvw = V3f32(uv.x1, uv.y1, bounds.w);
vertices[2].xy = V2f32(xy.x0, xy.y0);
vertices[2].uvw = V3f32(uv.x0, uv.y0, bounds.w);
vertices[5].xy = V2f32(xy.x1, xy.y0);
vertices[5].uvw = V3f32(uv.x1, uv.y0, bounds.w);
}
else{
Rect_f32 xy = Rf32(p.x - bounds.xy_off.y1, p.y + bounds.xy_off.x0,
p.x - bounds.xy_off.y0, p.y + bounds.xy_off.x1);
vertices[0].xy = V2f32(xy.x0, xy.y1);
vertices[0].uvw = V3f32(uv.x1, uv.y1, bounds.w);
vertices[1].xy = V2f32(xy.x1, xy.y1);
vertices[1].uvw = V3f32(uv.x1, uv.y0, bounds.w);
vertices[2].xy = V2f32(xy.x0, xy.y0);
vertices[2].uvw = V3f32(uv.x0, uv.y1, bounds.w);
vertices[5].xy = V2f32(xy.x1, xy.y0);
vertices[5].uvw = V3f32(uv.x0, uv.y0, bounds.w);
}
#endif
vertices[3] = vertices[1];
vertices[4] = vertices[2];
for (i32 i = 0; i < ArrayCount(vertices); i += 1){
vertices[i].color = color;
vertices[i].half_thickness = 0.f;
}
draw__write_vertices_in_current_group(target, vertices, ArrayCount(vertices));
}
////////////////////////////////
internal Vec2_f32
floor32(Vec2_f32 point){
point.x = f32_floor32(point.x);
point.y = f32_floor32(point.y);
return(point);
}
internal f32
draw_string(Render_Target *target, Face *face, String_Const_u8 string, Vec2_f32 point,
ARGB_Color color, u32 flags, Vec2_f32 delta){
f32 total_delta = 0.f;
if (face != 0){
point = floor32(point);
f32 byte_advance = face->metrics.byte_advance;
f32 *byte_sub_advances = face->metrics.byte_sub_advances;
u8 *str = (u8*)string.str;
u8 *str_end = str + string.size;
Translation_State tran = {};
Translation_Emits emits = {};
for (u32 i = 0; str < str_end; ++str, ++i){
translating_fully_process_byte(&tran, *str, i, (i32)string.size, &emits);
for (TRANSLATION_DECL_EMIT_LOOP(J, emits)){
TRANSLATION_DECL_GET_STEP(step, behavior, J, emits);
if (behavior.do_codepoint_advance){
u32 codepoint = step.value;
if (color != 0){
u32 draw_codepoint = step.value;
if (draw_codepoint == '\t'){
draw_codepoint = ' ';
}
draw_font_glyph(target, face, draw_codepoint, point, color, flags, delta);
}
local_const f32 internal_tab_width = 4.f;
f32 d = font_get_glyph_advance(&face->advance_map, &face->metrics, codepoint, internal_tab_width);
point += d*delta;
total_delta += d;
}
else if (behavior.do_number_advance){
u8 n = (u8)(step.value);
if (color != 0){
u8 cs[3];
cs[0] = '\\';
u8 nh = (n >> 4);
u8 nl = (n & 0xF);
u8 ch = '0' + nh;
u8 cl = '0' + nl;
if (nh > 0x9){
ch = ('A' - 0xA) + nh;
}
if (nl > 0x9){
cl = ('A' - 0xA) + nl;
}
cs[1] = ch;
cs[2] = cl;
Vec2_f32 pp = point;
for (u32 j = 0; j < 3; ++j){
draw_font_glyph(target, face, cs[j], pp, color, flags, delta);
pp += delta*byte_sub_advances[j];
}
}
point += byte_advance*delta;
total_delta += byte_advance;
}
}
}
}
return(total_delta);
}
internal f32
draw_string(Render_Target *target, Face *face, String_Const_u8 string, Vec2_f32 point, u32 color){
return(draw_string(target, face, string, point, color, 0, V2f32(1.f, 0.f)));
}
internal f32
draw_string(Render_Target *target, Face *face, u8 *str, Vec2_f32 point, u32 color, u32 flags, Vec2_f32 delta){
return(draw_string(target, face, SCu8(str), point, color, flags, delta));
}
internal f32
draw_string(Render_Target *target, Face *face, u8 *str, Vec2_f32 point, u32 color){
return(draw_string(target, face, SCu8(str), point, color, 0, V2f32(1.f, 0.f)));
}
internal f32
font_string_width(Render_Target *target, Face *face, String_Const_u8 str){
return(draw_string(target, face, str, V2f32(0, 0), 0, 0, V2f32(0, 0)));
}
internal f32
font_string_width(Render_Target *target, Face *face, u8 *str){
return(draw_string(target, face, SCu8(str), V2f32(0, 0), 0, 0, V2f32(0, 0)));
}
// BOTTOM

76
code/4ed_render_target.h Normal file
View File

@ -0,0 +1,76 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 10.11.2017
*
* Render target type definition
*
*/
// TOP
#if !defined(FRED_RENDER_TARGET_H)
#define FRED_RENDER_TARGET_H
struct Render_Free_Texture{
Render_Free_Texture *next;
u32 tex_id;
};
struct Render_Vertex{
Vec2_f32 xy;
Vec3_f32 uvw;
u32 color;
f32 half_thickness;
};
struct Render_Vertex_Array_Node{
Render_Vertex_Array_Node *next;
Render_Vertex *vertices;
i32 vertex_count;
i32 vertex_max;
};
struct Render_Vertex_List{
Render_Vertex_Array_Node *first;
Render_Vertex_Array_Node *last;
i32 vertex_count;
};
struct Render_Group{
Render_Group *next;
Render_Vertex_List vertex_list;
// parameters
Face_ID face_id;
Rect_f32 clip_box;
};
struct Render_Target{
b8 clip_all;
i32 width;
i32 height;
i32 bound_texture;
u32 color;
i32 frame_index;
f32 literal_dt;
f32 animation_dt;
Render_Free_Texture *free_texture_first;
Render_Free_Texture *free_texture_last;
Arena arena;
Render_Group *group_first;
Render_Group *group_last;
i32 group_count;
Face_ID current_face_id;
Rect_f32 current_clip_box;
void *font_set;
u32 fallback_texture_id;
};
#endif
// BOTTOM

View File

@ -0,0 +1,333 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 16.06.2019
*
* Routines for string matching within chunked streams.
*
*/
// TOP
internal u64_Array
string_compute_prefix_table(Arena *arena, String_Const_u8 string, Scan_Direction direction){
u64_Array array = {};
array.count = (i32)(string.size);
array.vals = push_array(arena, u64, array.count);
u8 *str = string.str;
if (direction == Scan_Backward){
str = string.str + string.size - 1;
}
array.vals[0] = 0;
for (u64 i = 1; i < string.size; i += 1){
u64 previous_longest_prefix = array.vals[i - 1];
for (;;){
u8 *a = str + previous_longest_prefix;
u8 *b = str + i;
if (direction == Scan_Backward){
a = str - previous_longest_prefix;
b = str - i;
}
if (character_to_upper(*a) == character_to_upper(*b)){
array.vals[i] = previous_longest_prefix + 1;
break;
}
if (previous_longest_prefix == 0){
array.vals[i] = 0;
break;
}
previous_longest_prefix = array.vals[previous_longest_prefix - 1];
}
}
return(array);
}
internal u64_Array
string_compute_needle_jump_table(Arena *arena, u64_Array longest_prefixes){
u64_Array array = {};
array.count = longest_prefixes.count + 1;
array.vals = push_array(arena, u64, array.count);
array.vals[0] = 0;
for (u64 i = 1; i < array.count; i += 1){
array.vals[i] = i - longest_prefixes.vals[i - 1];
}
return(array);
}
internal u64_Array
string_compute_needle_jump_table(Arena *arena, String_Const_u8 needle, Scan_Direction direction){
u64_Array prefix_table = string_compute_prefix_table(arena, needle, direction);
return(string_compute_needle_jump_table(arena, prefix_table));
}
#define character_predicate_check_character(p, c) (((p).b[(c)/8] & (1 << ((c)%8))) != 0)
internal String_Match_List
find_all_matches_forward(Arena *arena, i32 maximum_output_count,
List_String_Const_u8 chunks, String_Const_u8 needle,
u64_Array jump_table, Character_Predicate *predicate,
u64 base_index, Buffer_ID buffer, i32 string_id){
String_Match_List list = {};
if (chunks.node_count > 0){
u64 i = 0;
u64 j = 0;
b8 current_l = false;
i64 last_insensitive = -1;
i64 last_boundary = -1;
Node_String_Const_u8 *node = chunks.first;
i64 chunk_pos = 0;
i32 jump_back_code = 0;
u8 c = 0;
u64 n = 0;
u8 needle_c = 0;
u64 jump = 0;
if (false){
iterate_forward:
i += 1;
chunk_pos += 1;
if (chunk_pos >= (i64)node->string.size){
last_boundary = i;
chunk_pos = 0;
node = node->next;
}
switch (jump_back_code){
case 0:
{
goto jump_back_0;
}break;
case 1:
{
goto jump_back_1;
}break;
}
}
for (;node != 0;){
c = node->string.str[chunk_pos];
n = i - j;
needle_c = needle.str[n];
if (character_to_upper(c) == character_to_upper(needle_c)){
if (c != needle_c){
last_insensitive = i;
}
jump_back_code = 0;
goto iterate_forward;
jump_back_0:
if (n + 1 == needle.size){
String_Match_Flag flags = 0;
if (!(last_insensitive >= 0 &&
j <= (u64)last_insensitive &&
(u64)last_insensitive < j + needle.size)){
AddFlag(flags, StringMatch_CaseSensitive);
}
if (!(last_boundary >= 0 &&
j <= (u64)last_boundary &&
(u64)last_boundary < j + needle.size)){
AddFlag(flags, StringMatch_Straddled);
}
if (node != 0){
u8 next_c = node->string.str[chunk_pos];
if (character_predicate_check_character(*predicate, next_c)){
AddFlag(flags, StringMatch_RightSideSloppy);
}
}
if (current_l){
AddFlag(flags, StringMatch_LeftSideSloppy);
}
string_match_list_push(arena, &list, buffer, string_id, flags,
base_index + j, needle.size);
if (list.count >= maximum_output_count){
break;
}
jump = jump_table.vals[n + 1];
current_l = character_predicate_check_character(*predicate, needle.str[jump - 1]);
j += jump;
}
}
else{
jump = jump_table.vals[n];
if (jump == 0){
current_l = character_predicate_check_character(*predicate, c);
jump_back_code = 1;
goto iterate_forward;
jump_back_1:
j += 1;
}
else{
u8 prev_c = needle.str[jump - 1];
current_l = character_predicate_check_character(*predicate, prev_c);
j += jump;
}
}
}
}
return(list);
}
internal String_Match_List
find_all_matches_backward(Arena *arena, i32 maximum_output_count,
List_String_Const_u8 chunks, String_Const_u8 needle,
u64_Array jump_table, Character_Predicate *predicate,
u64 base_index, Buffer_ID buffer, i32 string_id){
String_Match_List list = {};
string_list_reverse(&chunks);
if (chunks.node_count > 0){
i64 size = (i64)chunks.total_size;
i64 i = size - 1;
i64 j = size - 1;
b8 current_r = false;
i64 last_insensitive = size;
i64 last_boundary = size;
Node_String_Const_u8 *node = chunks.first;
i64 chunk_pos = node->string.size - 1;
i32 jump_back_code = 0;
u8 c = 0;
u64 n = 0;
u8 needle_c = 0;
u64 jump = 0;
if (false){
iterate_backward:
i -= 1;
chunk_pos -= 1;
if (chunk_pos < 0){
last_boundary = i;
node = node->next;
if (node != 0){
chunk_pos = node->string.size - 1;
}
}
switch (jump_back_code){
case 0:
{
goto jump_back_0;
}break;
case 1:
{
goto jump_back_1;
}break;
}
}
for (;node != 0;){
c = node->string.str[chunk_pos];
n = j - i;
needle_c = needle.str[needle.size - 1 - n];
if (character_to_upper(c) == character_to_upper(needle_c)){
if (c != needle_c){
last_insensitive = i;
}
jump_back_code = 0;
goto iterate_backward;
jump_back_0:
if (n + 1 == needle.size){
String_Match_Flag flags = 0;
if (!(last_insensitive < size &&
j >= last_insensitive &&
last_insensitive > j - (i64)needle.size)){
AddFlag(flags, StringMatch_CaseSensitive);
}
if (!(last_boundary < size &&
j >= last_boundary &&
last_boundary > j - (i64)needle.size)){
AddFlag(flags, StringMatch_Straddled);
}
if (node != 0){
u8 next_c = node->string.str[chunk_pos];
if (character_predicate_check_character(*predicate, next_c)){
AddFlag(flags, StringMatch_LeftSideSloppy);
}
}
if (current_r){
AddFlag(flags, StringMatch_RightSideSloppy);
}
string_match_list_push(arena, &list, buffer, string_id, flags,
base_index + (j - (needle.size - 1)), needle.size);
if (list.count >= maximum_output_count){
break;
}
jump = jump_table.vals[n + 1];
u64 m = needle.size - jump;
u8 needle_m = needle.str[m];
current_r = character_predicate_check_character(*predicate, needle_m);
j -= jump;
}
}
else{
jump = jump_table.vals[n];
if (jump == 0){
current_r = character_predicate_check_character(*predicate, c);
jump_back_code = 1;
goto iterate_backward;
jump_back_1:
j -= 1;
}
else{
u64 m = needle.size - jump;
u8 needle_m = needle.str[m];
current_r = character_predicate_check_character(*predicate, needle_m);
j -= jump;
}
}
}
}
string_list_reverse(&chunks);
return(list);
}
internal String_Match_List
find_all_matches(Arena *arena, i32 maximum_output_count,
List_String_Const_u8 chunks, String_Const_u8 needle,
u64_Array jump_table, Character_Predicate *predicate,
Scan_Direction direction,
u64 base_index, Buffer_ID buffer, i32 string_id){
String_Match_List list = {};
switch (direction){
case Scan_Forward:
{
list = find_all_matches_forward(arena, maximum_output_count,
chunks, needle, jump_table, predicate,
base_index, buffer, string_id);
}break;
case Scan_Backward:
{
list = find_all_matches_backward(arena, maximum_output_count,
chunks, needle, jump_table, predicate,
base_index, buffer, string_id);
}break;
}
return(list);
}
// BOTTOM

331
code/4ed_system_api.cpp Normal file
View File

@ -0,0 +1,331 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 02.10.2019
*
* System API definition program.
*
*/
// TOP
#include "4ed_api_definition_main.cpp"
function API_Definition*
define_api(Arena *arena){
API_Definition *api = begin_api(arena, "system");
{
API_Call *call = api_call(arena, api, "error_box", "void");
api_param(arena, call, "char*", "msg");
}
{
API_Call *call = api_call(arena, api, "get_path", "String_Const_u8");
api_param(arena, call, "Arena*", "arena");
api_param(arena, call, "System_Path_Code", "path_code");
}
{
API_Call *call = api_call(arena, api, "get_canonical", "String_Const_u8");
api_param(arena, call, "Arena*", "arena");
api_param(arena, call, "String_Const_u8", "name");
}
{
API_Call *call = api_call(arena, api, "get_file_list", "File_List");
api_param(arena, call, "Arena*", "arena");
api_param(arena, call, "String_Const_u8", "directory");
}
{
API_Call *call = api_call(arena, api, "quick_file_attributes", "File_Attributes");
api_param(arena, call, "Arena*", "scratch");
api_param(arena, call, "String_Const_u8", "file_name");
}
{
API_Call *call = api_call(arena, api, "load_handle", "b32");
api_param(arena, call, "Arena*", "scratch");
api_param(arena, call, "char*", "file_name");
api_param(arena, call, "Plat_Handle*", "out");
}
{
API_Call *call = api_call(arena, api, "load_attributes", "File_Attributes");
api_param(arena, call, "Plat_Handle", "handle");
}
{
API_Call *call = api_call(arena, api, "load_file", "b32");
api_param(arena, call, "Plat_Handle", "handle");
api_param(arena, call, "char*", "buffer");
api_param(arena, call, "u32", "size");
}
{
API_Call *call = api_call(arena, api, "load_close", "b32");
api_param(arena, call, "Plat_Handle", "handle");
}
{
API_Call *call = api_call(arena, api, "save_file", "File_Attributes");
api_param(arena, call, "Arena*", "scratch");
api_param(arena, call, "char*", "file_name");
api_param(arena, call, "String_Const_u8", "data");
}
{
API_Call *call = api_call(arena, api, "load_library", "b32");
api_param(arena, call, "Arena*", "scratch");
api_param(arena, call, "String_Const_u8", "file_name");
api_param(arena, call, "System_Library*", "out");
}
{
API_Call *call = api_call(arena, api, "release_library", "b32");
api_param(arena, call, "System_Library", "handle");
}
{
API_Call *call = api_call(arena, api, "get_proc", "Void_Func*");
api_param(arena, call, "System_Library", "handle");
api_param(arena, call, "char*", "proc_name");
}
{
api_call(arena, api, "now_time", "u64");
}
{
api_call(arena, api, "now_date_time_universal", "Date_Time");
}
{
API_Call *call = api_call(arena, api, "local_date_time_from_universal", "Date_Time");
api_param(arena, call, "Date_Time*", "date_time");
}
{
API_Call *call = api_call(arena, api, "universal_date_time_from_local", "Date_Time");
api_param(arena, call, "Date_Time*", "date_time");
}
{
api_call(arena, api, "wake_up_timer_create", "Plat_Handle");
}
{
API_Call *call = api_call(arena, api, "wake_up_timer_release", "void");
api_param(arena, call, "Plat_Handle", "handle");
}
{
API_Call *call = api_call(arena, api, "wake_up_timer_set", "void");
api_param(arena, call, "Plat_Handle", "handle");
api_param(arena, call, "u32", "time_milliseconds");
}
{
API_Call *call = api_call(arena, api, "signal_step", "void");
api_param(arena, call, "u32", "code");
}
{
API_Call *call = api_call(arena, api, "sleep", "void");
api_param(arena, call, "u64", "microseconds");
}
{
API_Call *call = api_call(arena, api, "get_clipboard", "String_Const_u8");
api_param(arena, call, "Arena*", "arena");
api_param(arena, call, "i32", "index");
}
{
API_Call *call = api_call(arena, api, "post_clipboard", "void");
api_param(arena, call, "String_Const_u8", "str");
api_param(arena, call, "i32", "index");
}
{
API_Call *call = api_call(arena, api, "set_clipboard_catch_all", "void");
api_param(arena, call, "b32", "enabled");
}
{
api_call(arena, api, "get_clipboard_catch_all", "b32");
}
{
API_Call *call = api_call(arena, api, "cli_call", "b32");
api_param(arena, call, "Arena*", "scratch");
api_param(arena, call, "char*", "path");
api_param(arena, call, "char*", "script");
api_param(arena, call, "CLI_Handles*", "cli_out");
}
{
API_Call *call = api_call(arena, api, "cli_begin_update", "void");
api_param(arena, call, "CLI_Handles*", "cli");
}
{
API_Call *call = api_call(arena, api, "cli_update_step", "b32");
api_param(arena, call, "CLI_Handles*", "cli");
api_param(arena, call, "char*", "dest");
api_param(arena, call, "u32", "max");
api_param(arena, call, "u32*", "amount");
}
{
API_Call *call = api_call(arena, api, "cli_end_update", "b32");
api_param(arena, call, "CLI_Handles*", "cli");
}
{
API_Call *call = api_call(arena, api, "open_color_picker", "void");
api_param(arena, call, "Color_Picker*", "picker");
}
{
api_call(arena, api, "get_screen_scale_factor", "f32");
}
{
API_Call *call = api_call(arena, api, "thread_launch", "System_Thread");
api_param(arena, call, "Thread_Function*", "proc");
api_param(arena, call, "void*", "ptr");
}
{
API_Call *call = api_call(arena, api, "thread_join", "void");
api_param(arena, call, "System_Thread", "thread");
}
{
API_Call *call = api_call(arena, api, "thread_free", "void");
api_param(arena, call, "System_Thread", "thread");
}
{
api_call(arena, api, "thread_get_id", "i32");
}
{
API_Call *call = api_call(arena, api, "acquire_global_frame_mutex", "void");
api_param(arena, call, "Thread_Context*", "tctx");
}
{
API_Call *call = api_call(arena, api, "release_global_frame_mutex", "void");
api_param(arena, call, "Thread_Context*", "tctx");
}
{
api_call(arena, api, "mutex_make", "System_Mutex");
}
{
API_Call *call = api_call(arena, api, "mutex_acquire", "void");
api_param(arena, call, "System_Mutex", "mutex");
}
{
API_Call *call = api_call(arena, api, "mutex_release", "void");
api_param(arena, call, "System_Mutex", "mutex");
}
{
API_Call *call = api_call(arena, api, "mutex_free", "void");
api_param(arena, call, "System_Mutex", "mutex");
}
{
api_call(arena, api, "condition_variable_make",
"System_Condition_Variable");
}
{
API_Call *call = api_call(arena, api, "condition_variable_wait", "void");
api_param(arena, call, "System_Condition_Variable", "cv");
api_param(arena, call, "System_Mutex", "mutex");
}
{
API_Call *call = api_call(arena, api, "condition_variable_signal", "void");
api_param(arena, call, "System_Condition_Variable", "cv");
}
{
API_Call *call = api_call(arena, api, "condition_variable_free", "void");
api_param(arena, call, "System_Condition_Variable", "cv");
}
{
API_Call *call = api_call(arena, api, "memory_allocate", "void*");
api_param(arena, call, "u64", "size");
api_param(arena, call, "String_Const_u8", "location");
}
{
API_Call *call = api_call(arena, api, "memory_set_protection", "b32");
api_param(arena, call, "void*", "ptr");
api_param(arena, call, "u64", "size");
api_param(arena, call, "u32", "flags");
}
{
API_Call *call = api_call(arena, api, "memory_free", "void");
api_param(arena, call, "void*", "ptr");
api_param(arena, call, "u64", "size");
}
{
API_Call *call = api_call(arena, api, "memory_annotation", "Memory_Annotation");
api_param(arena, call, "Arena*", "arena");
}
{
API_Call *call = api_call(arena, api, "show_mouse_cursor", "void");
api_param(arena, call, "i32", "show");
}
{
API_Call *call = api_call(arena, api, "set_fullscreen", "b32");
api_param(arena, call, "b32", "full_screen");
}
{
api_call(arena, api, "is_fullscreen", "b32");
}
{
API_Call *call = api_call(arena, api, "get_keyboard_modifiers", "Input_Modifier_Set");
api_param(arena, call, "Arena*", "arena");
}
{
API_Call *call = api_call(arena, api, "set_key_mode", "void");
api_param(arena, call, "Key_Mode", "mode");
}
{
API_Call *call = api_call(arena, api, "set_source_mixer", "void");
api_param(arena, call, "void*", "ctx");
api_param(arena, call, "Audio_Mix_Sources_Function*", "mix_func");
}
{
API_Call *call = api_call(arena, api, "set_destination_mixer", "void");
api_param(arena, call, "Audio_Mix_Destination_Function*", "mix_func");
}
return(api);
}
function Generated_Group
get_api_group(void){
return(GeneratedGroup_Custom);
}
// BOTTOM

134
code/4ed_text_layout.cpp Normal file
View File

@ -0,0 +1,134 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 31.03.2019
*
* Text layout representation
*
*/
// TOP
internal void
text_layout_init(Thread_Context *tctx, Text_Layout_Container *container){
block_zero_struct(container);
container->node_arena = make_arena_system();
container->table = make_table_u64_u64(tctx->allocator, 20);
}
internal Text_Layout*
text_layout_new__alloc_layout(Text_Layout_Container *container){
Text_Layout *node = container->free_nodes;
if (node == 0){
node = push_array(&container->node_arena, Text_Layout, 1);
}
else{
sll_stack_pop(container->free_nodes);
}
return(node);
}
internal void
text_layout_release(Thread_Context *tctx, Models *models, Text_Layout_Container *container, Text_Layout *layout){
Arena arena = *layout->arena;
linalloc_clear(&arena);
sll_stack_push(container->free_nodes, layout);
}
internal Text_Layout_ID
text_layout_new(Text_Layout_Container *container, Arena *arena,
Buffer_ID buffer_id, Buffer_Point point,
Range_i64 visible_range, Range_i64 visible_line_number_range,
Rect_f32 rect, ARGB_Color *item_colors, Layout_Function *layout_func){
Text_Layout *new_layout_data = text_layout_new__alloc_layout(container);
new_layout_data->arena = arena;
new_layout_data->buffer_id = buffer_id;
new_layout_data->point = point;
new_layout_data->visible_range = visible_range;
new_layout_data->visible_line_number_range = visible_line_number_range;
new_layout_data->rect = rect;
new_layout_data->item_colors = item_colors;
new_layout_data->layout_func = layout_func;
Text_Layout_ID new_id = ++container->id_counter;
table_insert(&container->table, new_id, (u64)PtrAsInt(new_layout_data));
return(new_id);
}
internal Text_Layout*
text_layout_get(Text_Layout_Container *container, Text_Layout_ID id){
Text_Layout *result = 0;
Table_Lookup lookup = table_lookup(&container->table, id);
if (lookup.found_match){
u64 ptr_val = 0;
table_read(&container->table, lookup, &ptr_val);
result = (Text_Layout*)IntAsPtr(ptr_val);
}
return(result);
}
internal b32
text_layout_erase(Thread_Context *tctx, Models *models, Text_Layout_Container *container, Text_Layout_ID id){
b32 result = false;
Table_Lookup lookup = table_lookup(&container->table, id);
if (lookup.found_match){
u64 ptr_val = 0;
table_read(&container->table, lookup, &ptr_val);
Text_Layout *ptr = (Text_Layout*)IntAsPtr(ptr_val);
text_layout_release(tctx, models, container, ptr);
table_erase(&container->table, lookup);
result = true;
}
return(result);
}
////////////////////////////////
internal void
text_layout_render(Thread_Context *tctx, Models *models, Text_Layout *layout,
ARGB_Color special_color, ARGB_Color ghost_color){
Editing_File *file = imp_get_file(models, layout->buffer_id);
if (file != 0){
Render_Target *target = models->target;
Face *face = file_get_face(models, file);
f32 width = rect_width(layout->rect);
Vec2_f32 delta = V2f32(1.f, 0.f);
Vec2_f32 shift_p = layout->rect.p0 - layout->point.pixel_shift;
i64 first_index = layout->visible_range.first;
i64 line_number = layout->visible_line_number_range.min;
i64 line_number_last = layout->visible_line_number_range.max;
Layout_Function *layout_func = layout->layout_func;
for (;line_number <= line_number_last; line_number += 1){
Layout_Item_List line = file_get_line_layout(tctx, models, file,
layout_func, width, face,
line_number);
for (Layout_Item_Block *block = line.first;
block != 0;
block = block->next){
Layout_Item *item = block->items;
i64 count = block->item_count;
ARGB_Color *item_colors = layout->item_colors;
for (i32 i = 0; i < count; i += 1, item += 1){
if (item->codepoint != 0){
ARGB_Color color = 0;
if (HasFlag(item->flags, LayoutItemFlag_Special_Character)){
color = special_color;
}
else if (HasFlag(item->flags, LayoutItemFlag_Ghost_Character)){
color = ghost_color;
}
else{
color = item_colors[item->index - first_index];
}
Vec2_f32 p = item->rect.p0 + shift_p;
draw_font_glyph(target, face, item->codepoint, p, color, GlyphFlag_None, delta);
}
}
}
shift_p.y += line.height;
}
}
}
// BOTTOM

39
code/4ed_text_layout.h Normal file
View File

@ -0,0 +1,39 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 31.03.2019
*
* Text layout representation
*
*/
// TOP
#if !defined(FRED_TEXT_LAYOUT_H)
#define FRED_TEXT_LAYOUT_H
union Text_Layout{
Text_Layout *next;
struct{
Arena *arena;
Buffer_ID buffer_id;
Buffer_Point point;
Range_i64 visible_range;
Range_i64 visible_line_number_range;
Rect_f32 rect;
ARGB_Color *item_colors;
Layout_Function *layout_func;
};
};
struct Text_Layout_Container{
Arena node_arena;
Text_Layout *free_nodes;
Table_u64_u64 table;
Text_Layout_ID id_counter;
};
#endif
// BOTTOM

236
code/4ed_translation.cpp Normal file
View File

@ -0,0 +1,236 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 11.03.2017
*
* Translation system for turning byte streams into a stream of buffer model steps.
*
*/
// TOP
// TODO(allen): I don't like this code _AT ALL_
// unravel the mess!
//
//
// So what happened here was I thought, "Hey I have text in non-contiguous chunks and now I
// need to translates it into unicode codepoints (interpreting it as utf8), so I should make
// a system that translates utf-8 by taking in one byte at a time then emitting one or more
// codepoints whenever there is enough information, all the while ensuring there is no backtracking"
//
// Even though this may make the iteration sound nice, it's a HUGE FREAKING PAIN IN THE ASS.
// You can't optimize it very well, the code is inscrutible both on the implementation side
// and the calling side. Besides the fact that I "got it working" there isn't one good thing
// about this code.
//
// My next idea would be to try to make translation systems that take in the chunks themselves as
// a linked list, and then just does the WHOLE translation, MAYBE with optional "stop conditions".
// This way someone can actually optimize the translation loop by hand in _ONE SPOT_. The downside
// is that the caller will have to put up with maybe more translation work than they needed, but that
// translation work will be so much cheaper, and easier to maintain, that the caller will be happier
// overall.
//
//
// If this comment is still here, then I haven't fixed any of this garbage yet, but it should really
// be fixed!
#define ERROR_BYTE (max_u8-1)
#define CONTINUATION_BYTE max_u8
internal void
translating_consume_byte(Translation_State *tran, u8 ch, u32 i, u32 size, Translation_Byte_Description *desc_out){
desc_out->byte_class = 0;
if (ch < 0x80){
desc_out->byte_class = 1;
}
else if (ch < 0xC0){
desc_out->byte_class = CONTINUATION_BYTE;
}
else if (ch < 0xE0){
desc_out->byte_class = 2;
}
else if (ch < 0xF0){
desc_out->byte_class = 3;
}
else if (ch < 0xF8){
desc_out->byte_class = 4;
}
else{
desc_out->byte_class = ERROR_BYTE;
}
desc_out->prelim_emit_type = BufferModelUnit_None;
desc_out->last_byte_handler = TranLBH_None;
if (tran->fill_expected == 0){
tran->fill_buffer[0] = ch;
tran->fill_start_i = i;
tran->fill_i = 1;
if (desc_out->byte_class == 1){
desc_out->prelim_emit_type = BufferModelUnit_Codepoint;
}
else if (desc_out->byte_class == 0 || desc_out->byte_class == CONTINUATION_BYTE || desc_out->byte_class == ERROR_BYTE){
desc_out->prelim_emit_type = BufferModelUnit_Numbers;
}
else{
tran->fill_expected = desc_out->byte_class;
}
}
else{
if (desc_out->byte_class == CONTINUATION_BYTE){
tran->fill_buffer[tran->fill_i] = ch;
++tran->fill_i;
if (tran->fill_i == tran->fill_expected){
desc_out->prelim_emit_type = BufferModelUnit_Codepoint;
}
}
else{
if (desc_out->byte_class >= 2 && desc_out->byte_class <= 4){
desc_out->last_byte_handler = TranLBH_Rebuffer;
}
else if (desc_out->byte_class == 1){
desc_out->last_byte_handler = TranLBH_EmitAsCP;
}
else{
tran->fill_buffer[tran->fill_i] = ch;
++tran->fill_i;
}
desc_out->prelim_emit_type = BufferModelUnit_Numbers;
}
}
if (desc_out->prelim_emit_type == BufferModelUnit_None && i+1 == size){
desc_out->prelim_emit_type = BufferModelUnit_Numbers;
}
}
internal void
translating_select_emit_rule_UTF8(Translation_State *tran, Translation_Byte_Description desc, Translation_Emit_Rule *type_out){
type_out->byte_class = desc.byte_class;
type_out->last_byte_handler = desc.last_byte_handler;
type_out->emit_type = desc.prelim_emit_type;
type_out->codepoint = 0;
type_out->codepoint_length = 0;
if (desc.prelim_emit_type == BufferModelUnit_Codepoint){
Character_Consume_Result consume = utf8_consume(tran->fill_buffer, ArrayCount(tran->fill_buffer));
u32 cp = consume.codepoint;
type_out->codepoint_length = consume.inc;
if (cp == max_u32){
type_out->codepoint_length = 0;
}
if (type_out->codepoint_length != 0){
if ((cp >= nonchar_min && cp <= nonchar_max) || ((cp & 0xFFFF) >= 0xFFFE)){
type_out->emit_type = BufferModelUnit_Numbers;
}
else{
type_out->codepoint = cp;
if (cp > 0x10FFFF){
type_out->emit_type = BufferModelUnit_Numbers;
}
}
}
else{
type_out->emit_type = BufferModelUnit_Numbers;
}
}
}
internal void
translating_generate_emits(Translation_State *tran, Translation_Emit_Rule emit_rule, u8 ch, u32 i, Translation_Emits *emits_out){
emits_out->step_count = 0;
switch (emit_rule.emit_type){
default: goto skip_all;
case BufferModelUnit_Codepoint:
{
emits_out->steps[0].type = 1;
emits_out->steps[0].value = emit_rule.codepoint;
emits_out->steps[0].i = tran->fill_start_i;
emits_out->steps[0].byte_length = emit_rule.codepoint_length;
emits_out->step_count = 1;
}break;
case BufferModelUnit_Numbers:
{
for (u32 j = 0; j < tran->fill_i; ++j){
emits_out->steps[j].type = 0;
emits_out->steps[j].value = tran->fill_buffer[j];
emits_out->steps[j].i = tran->fill_start_i + j;
emits_out->steps[j].byte_length = 1;
}
emits_out->step_count = tran->fill_i;
}break;
}
tran->fill_start_i = 0;
tran->fill_i = 0;
tran->fill_expected = 0;
switch (emit_rule.last_byte_handler){
case TranLBH_Rebuffer:
{
tran->fill_buffer[0] = ch;
tran->fill_start_i = i;
tran->fill_i = 1;
tran->fill_expected = emit_rule.byte_class;
}break;
case TranLBH_EmitAsCP:
{
emits_out->steps[emits_out->step_count].type = 1;
emits_out->steps[emits_out->step_count].value = ch;
emits_out->steps[emits_out->step_count].i = i;
emits_out->steps[emits_out->step_count].byte_length = 1;
++emits_out->step_count;
}break;
}
skip_all:;
}
internal void
translating_fully_process_byte(Translation_State *tran, u8 ch, u32 i, u32 size, Translation_Emits *emits_out){
Translation_Byte_Description description = {};
translating_consume_byte(tran, ch, i, size, &description);
Translation_Emit_Rule emit_rule = {};
translating_select_emit_rule_UTF8(tran, description, &emit_rule);
translating_generate_emits(tran, emit_rule, ch, i, emits_out);
}
internal void
translation_step_read(Buffer_Model_Step step, Buffer_Model_Behavior *behavior_out){
behavior_out->do_newline = false;
behavior_out->do_codepoint_advance = false;
behavior_out->do_number_advance = false;
if (step.type == 1){
switch (step.value){
case '\n':
{
behavior_out->do_newline = true;
}break;
default:
{
behavior_out->do_codepoint_advance = true;
}break;
}
}
else{
behavior_out->do_number_advance = true;
}
}
#define TRANSLATION_DECL_EMIT_LOOP(_j,_emit) u32 _j = 0; _j < (_emit).step_count; ++_j
#define TRANSLATION_DECL_GET_STEP(_step,_behav,_j,_emit) \
Buffer_Model_Step _step = _emit.steps[_j]; Buffer_Model_Behavior _behav; \
translation_step_read(_step, &_behav)
#define TRANSLATION_EMIT_LOOP(_j,_emit) _j = 0; _j < (_emit).step_count; ++_j
#define TRANSLATION_GET_STEP(_step,_behav,_j,_emit)\
(_step) = _emit.steps[_j]; translation_step_read((_step), &(_behav))
// BOTTOM

50
code/4ed_translation.h Normal file
View File

@ -0,0 +1,50 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 24.01.2018
*
* Buffer types
*
*/
// TOP
#if !defined(FRED_TRANSLATION_H)
#define FRED_TRANSLATION_H
struct Translation_State{
u8 fill_buffer[4];
u32 fill_start_i;
u8 fill_i;
u8 fill_expected;
};
enum{
TranLBH_None,
TranLBH_Rebuffer,
TranLBH_EmitAsCP,
};
struct Translation_Byte_Description{
u8 byte_class;
u8 last_byte_handler;
u8 prelim_emit_type;
};
struct Translation_Emit_Rule{
u8 byte_class;
u8 last_byte_handler;
u8 emit_type;
u32 codepoint;
u32 codepoint_length;
};
struct Translation_Emits{
Buffer_Model_Step steps[5];
u32 step_count;
};
#endif
// BOTTOM

853
code/4ed_view.cpp Normal file
View File

@ -0,0 +1,853 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 19.08.2015
*
* Viewing
*
*/
// TOP
function void
begin_handling_input(Models *models, User_Input *input){
block_copy_struct(&models->current_input, input);
models->current_input_sequence_number += 1;
}
////////////////////////////////
internal void
init_query_set(Query_Set *set){
Query_Slot *slot = set->slots;
set->free_slot = slot;
set->used_slot = 0;
for (i32 i = 0; i+1 < ArrayCount(set->slots); ++i, ++slot){
slot->next = slot + 1;
}
}
internal Query_Slot*
alloc_query_slot(Query_Set *set){
Query_Slot *slot = set->free_slot;
if (slot != 0){
set->free_slot = slot->next;
slot->next = set->used_slot;
set->used_slot = slot;
}
return(slot);
}
internal void
free_query_slot(Query_Set *set, Query_Bar *match_bar){
Query_Slot *slot = 0;
Query_Slot *prev = 0;
for (slot = set->used_slot; slot != 0; slot = slot->next){
if (slot->query_bar == match_bar) break;
prev = slot;
}
if (slot){
if (prev){
prev->next = slot->next;
}
else{
set->used_slot = slot->next;
}
slot->next = set->free_slot;
set->free_slot = slot;
}
}
function void
free_all_queries(Query_Set *set){
for (;set->used_slot != 0;){
Query_Slot *slot = set->used_slot;
set->used_slot = slot->next;
slot->next = set->free_slot;
set->free_slot = slot;
}
}
////////////////////////////////
internal Access_Flag
view_get_access_flags(View *view){
Access_Flag result = file_get_access_flags(view->file);
View_Context_Node *node = view->ctx;
b32 hides_buffer = (node != 0 && node->ctx.hides_buffer);
if (hides_buffer){
RemFlag(result, Access_Visible);
}
return(result);
}
internal i32
view_get_index(Live_Views *live_set, View *view){
return((i32)(view - live_set->views));
}
internal View_ID
view_get_id(Live_Views *live_set, View *view){
return((View_ID)(view - live_set->views) + 1);
}
internal View*
live_set_alloc_view(Lifetime_Allocator *lifetime_allocator, Live_Views *live_set, Panel *panel){
Assert(live_set->count < live_set->max);
++live_set->count;
View *result = live_set->free_sentinel.next;
dll_remove(result);
block_zero_struct(result);
result->in_use = true;
init_query_set(&result->query_set);
result->lifetime_object = lifetime_alloc_object(lifetime_allocator, DynamicWorkspace_View, result);
panel->view = result;
result->panel = panel;
return(result);
}
internal void
live_set_free_view(Lifetime_Allocator *lifetime_allocator, Live_Views *live_set, View *view){
Assert(live_set->count > 0);
--live_set->count;
view->next = live_set->free_sentinel.next;
view->prev = &live_set->free_sentinel;
live_set->free_sentinel.next = view;
view->next->prev = view;
view->in_use = false;
lifetime_free_object(lifetime_allocator, view->lifetime_object);
}
////////////////////////////////
internal File_Edit_Positions
view_get_edit_pos(View *view){
return(view->edit_pos_);
}
internal void
view_set_edit_pos(View *view, File_Edit_Positions edit_pos){
edit_pos.scroll.position.line_number = clamp_bot(1, edit_pos.scroll.position.line_number);
edit_pos.scroll.target.line_number = clamp_bot(1, edit_pos.scroll.target.line_number);
view->edit_pos_ = edit_pos;
view->file->state.edit_pos_most_recent = edit_pos;
}
////////////////////////////////
internal Rect_f32
view_get_buffer_rect(Thread_Context *tctx, Models *models, View *view){
Rect_f32 region = Rf32(view->panel->rect_full);
if (models->buffer_region != 0){
Rect_f32 rect = region;
Rect_f32 sub_region = Rf32(V2f32(0, 0), rect_dim(rect));
Application_Links app = {};
app.tctx = tctx;
app.cmd_context = models;
sub_region = models->buffer_region(&app, view_get_id(&models->view_set, view), sub_region);
region.p0 = rect.p0 + sub_region.p0;
region.p1 = rect.p0 + sub_region.p1;
region.x1 = clamp_top(region.x1, rect.x1);
region.y1 = clamp_top(region.y1, rect.y1);
region.x0 = clamp_top(region.x0, region.x1);
region.y0 = clamp_top(region.y0, region.y1);
}
return(region);
}
internal f32
view_width(Thread_Context *tctx, Models *models, View *view){
return(rect_width(view_get_buffer_rect(tctx, models, view)));
}
internal f32
view_height(Thread_Context *tctx, Models *models, View *view){
return(rect_height(view_get_buffer_rect(tctx, models, view)));
}
////////////////////////////////
internal Layout_Item_List
view_get_line_layout(Thread_Context *tctx, Models *models, View *view, i64 line_number){
Editing_File *file = view->file;
Face *face = file_get_face(models, file);
f32 width = view_width(tctx, models, view);
Layout_Function *layout_func = file_get_layout_func(file);
return(file_get_line_layout(tctx, models, file, layout_func, width, face, line_number));
}
internal Line_Shift_Vertical
view_line_shift_y(Thread_Context *tctx, Models *models, View *view,
i64 line_number, f32 y_delta){
Editing_File *file = view->file;
Face *face = file_get_face(models, file);
f32 width = view_width(tctx, models, view);
Layout_Function *layout_func = file_get_layout_func(file);
return(file_line_shift_y(tctx, models, file, layout_func, width, face,
line_number, y_delta));
}
internal f32
view_line_y_difference(Thread_Context *tctx, Models *models, View *view,
i64 line_a, i64 line_b){
Editing_File *file = view->file;
Face *face = file_get_face(models, file);
f32 width = view_width(tctx, models, view);
Layout_Function *layout_func = file_get_layout_func(file);
return(file_line_y_difference(tctx, models, file,
layout_func, width, face, line_a, line_b));
}
internal i64
view_pos_at_relative_xy(Thread_Context *tctx, Models *models, View *view,
i64 base_line, Vec2_f32 relative_xy){
Editing_File *file = view->file;
Face *face = file_get_face(models, file);
f32 width = view_width(tctx, models, view);
Layout_Function *layout_func = file_get_layout_func(file);
return(file_pos_at_relative_xy(tctx, models, file,
layout_func, width, face, base_line, relative_xy));
}
internal Rect_f32
view_relative_box_of_pos(Thread_Context *tctx, Models *models, View *view,
i64 base_line, i64 pos){
Editing_File *file = view->file;
Face *face = file_get_face(models, file);
f32 width = view_width(tctx, models, view);
Layout_Function *layout_func = file_get_layout_func(file);
return(file_relative_box_of_pos(tctx, models, file,
layout_func, width, face, base_line, pos));
}
internal Vec2_f32
view_relative_xy_of_pos(Thread_Context *tctx, Models *models, View *view,
i64 base_line, i64 pos){
Rect_f32 rect = view_relative_box_of_pos(tctx, models, view, base_line, pos);
return(rect_center(rect));
}
function Rect_f32
view_padded_box_of_pos(Thread_Context *tctx, Models *models, View *view,
i64 base_line, i64 pos){
Editing_File *file = view->file;
Face *face = file_get_face(models, file);
f32 width = view_width(tctx, models, view);
Layout_Function *layout_func = file_get_layout_func(file);
return(file_padded_box_of_pos(tctx, models, file,
layout_func, width, face, base_line, pos));
}
internal Buffer_Point
view_normalize_buffer_point(Thread_Context *tctx, Models *models, View *view,
Buffer_Point point){
Editing_File *file = view->file;
Face *face = file_get_face(models, file);
f32 width = view_width(tctx, models, view);
Layout_Function *layout_func = file_get_layout_func(file);
return(file_normalize_buffer_point(tctx, models, file,
layout_func, width, face, point));
}
internal Vec2_f32
view_buffer_point_difference(Thread_Context *tctx, Models *models, View *view,
Buffer_Point a, Buffer_Point b){
Editing_File *file = view->file;
Face *face = file_get_face(models, file);
f32 width = view_width(tctx, models, view);
Layout_Function *layout_func = file_get_layout_func(file);
return(file_buffer_point_difference(tctx, models, file,
layout_func, width, face, a, b));
}
internal Buffer_Point
view_move_buffer_point(Thread_Context *tctx, Models *models, View *view,
Buffer_Point buffer_point, Vec2_f32 delta){
delta += buffer_point.pixel_shift;
Line_Shift_Vertical shift = view_line_shift_y(tctx, models, view, buffer_point.line_number, delta.y);
buffer_point.line_number = shift.line;
buffer_point.pixel_shift = V2f32(delta.x, delta.y - shift.y_delta);
return(buffer_point);
}
internal Line_Shift_Character
view_line_shift_characters(Thread_Context *tctx, Models *models, View *view,
i64 line_number, i64 character_delta){
Editing_File *file = view->file;
Face *face = file_get_face(models, file);
f32 width = view_width(tctx, models, view);
Layout_Function *layout_func = file_get_layout_func(file);
return(file_line_shift_characters(tctx, models, file,
layout_func, width, face, line_number, character_delta));
}
internal i64
view_line_character_difference(Thread_Context *tctx, Models *models, View *view,
i64 line_a, i64 line_b){
Editing_File *file = view->file;
Face *face = file_get_face(models, file);
f32 width = view_width(tctx, models, view);
Layout_Function *layout_func = file_get_layout_func(file);
return(file_line_character_difference(tctx, models, file, layout_func, width, face,
line_a, line_b));
}
internal i64
view_pos_from_relative_character(Thread_Context *tctx, Models *models, View *view,
i64 base_line, i64 relative_character){
Editing_File *file = view->file;
Face *face = file_get_face(models, file);
f32 width = view_width(tctx, models, view);
Layout_Function *layout_func = file_get_layout_func(file);
return(file_pos_from_relative_character(tctx, models, file, layout_func, width, face,
base_line, relative_character));
}
internal i64
view_relative_character_from_pos(Thread_Context *tctx, Models *models, View *view,
i64 base_line, i64 pos){
Editing_File *file = view->file;
Face *face = file_get_face(models, file);
f32 width = view_width(tctx, models, view);
Layout_Function *layout_func = file_get_layout_func(file);
return(file_relative_character_from_pos(tctx, models, file,
layout_func, width, face, base_line, pos));
}
internal Buffer_Cursor
view_compute_cursor(View *view, Buffer_Seek seek){
Editing_File *file = view->file;
return(file_compute_cursor(file, seek));
}
////////////////////////////////
internal b32
view_move_view_to_cursor(Thread_Context *tctx, Models *models, View *view, Buffer_Scroll *scroll){
Editing_File *file = view->file;
Face *face = file_get_face(models, file);
Rect_f32 rect = view_get_buffer_rect(tctx, models, view);
Vec2_f32 view_dim = rect_dim(rect);
Layout_Function *layout_func = file_get_layout_func(file);
File_Edit_Positions edit_pos = view_get_edit_pos(view);
Vec2_f32 p = file_relative_xy_of_pos(tctx, models, file,
layout_func, view_dim.x, face,
scroll->target.line_number, edit_pos.cursor_pos);
p -= scroll->target.pixel_shift;
f32 line_height = face->metrics.line_height;
f32 normal_advance = face->metrics.normal_advance;
Vec2_f32 margin = view->cursor_margin;
Vec2_f32 push_in = view->cursor_push_in_multiplier;
Vec2_f32 lim_dim = view_dim*0.45f;
margin.x = clamp_top(margin.x, lim_dim.x);
margin.y = clamp_top(margin.y, lim_dim.y);
Vec2_f32 push_in_lim_dim = hadamard(lim_dim, V2f32(1.f/line_height, 1.f/normal_advance)) - margin;
push_in_lim_dim.x = clamp_bot(0.f, push_in_lim_dim.x);
push_in_lim_dim.y = clamp_bot(0.f, push_in_lim_dim.y);
push_in.x = clamp_top(push_in.x, push_in_lim_dim.x);
push_in.y = clamp_top(push_in.y, push_in_lim_dim.y);
Vec2_f32 target_p_relative = {};
if (p.y < margin.y){
target_p_relative.y = p.y - margin.y - line_height*push_in.y;
}
else if (p.y > view_dim.y - margin.y){
target_p_relative.y = (p.y + margin.y + line_height*push_in.y) - view_dim.y;
}
if (p.x < margin.x){
target_p_relative.x = p.x - margin.x - normal_advance*push_in.x;
}
else if (p.x > view_dim.x - margin.x){
target_p_relative.x = (p.x + margin.x + normal_advance*push_in.x) - view_dim.x;
}
scroll->target.pixel_shift += target_p_relative;
scroll->target = view_normalize_buffer_point(tctx, models, view, scroll->target);
scroll->target.pixel_shift.x = f32_round32(scroll->target.pixel_shift.x);
scroll->target.pixel_shift.y = f32_round32(scroll->target.pixel_shift.y);
return(target_p_relative != V2f32(0.f, 0.f));
}
internal b32
view_move_cursor_to_view(Thread_Context *tctx, Models *models, View *view, Buffer_Scroll scroll, i64 *pos_in_out, f32 preferred_x){
Editing_File *file = view->file;
Face *face = file_get_face(models, file);
Rect_f32 rect = view_get_buffer_rect(tctx, models, view);
Vec2_f32 view_dim = rect_dim(rect);
Layout_Function *layout_func = file_get_layout_func(file);
Vec2_f32 p = file_relative_xy_of_pos(tctx, models, file,
layout_func, view_dim.x, face,
scroll.target.line_number, *pos_in_out);
p -= scroll.target.pixel_shift;
f32 line_height = face->metrics.line_height;
b32 adjusted_y = true;
if (p.y < 0.f){
p.y = line_height*1.5f;
}
else if (p.y > view_dim.y){
p.y = view_dim.y - line_height*1.5f;
}
else{
adjusted_y = false;
}
b32 result = false;
if (adjusted_y){
p += scroll.target.pixel_shift;
*pos_in_out = file_pos_at_relative_xy(tctx, models, file,
layout_func, view_dim.x, face,
scroll.target.line_number, p);
result = true;
}
return(result);
}
internal void
view_set_cursor(Thread_Context *tctx, Models *models, View *view, i64 pos){
File_Edit_Positions edit_pos = view_get_edit_pos(view);
file_edit_positions_set_cursor(&edit_pos, pos);
view_set_edit_pos(view, edit_pos);
Buffer_Scroll scroll = edit_pos.scroll;
if (view_move_view_to_cursor(tctx, models, view, &scroll)){
edit_pos.scroll = scroll;
view_set_edit_pos(view, edit_pos);
}
}
internal void
view_set_scroll(Thread_Context *tctx, Models *models, View *view, Buffer_Scroll scroll){
File_Edit_Positions edit_pos = view_get_edit_pos(view);
file_edit_positions_set_scroll(&edit_pos, scroll);
view_set_edit_pos(view, edit_pos);
if (view_move_cursor_to_view(tctx, models, view, edit_pos.scroll, &edit_pos.cursor_pos, view->preferred_x)){
view_set_edit_pos(view, edit_pos);
}
}
internal void
view_set_cursor_and_scroll(Thread_Context *tctx, Models *models, View *view, i64 pos, Buffer_Scroll scroll){
File_Edit_Positions edit_pos = view_get_edit_pos(view);
file_edit_positions_set_cursor(&edit_pos, pos);
Buffer_Cursor cursor = view_compute_cursor(view, seek_pos(pos));
Vec2_f32 p = view_relative_xy_of_pos(tctx, models, view, cursor.line, pos);
view->preferred_x = p.x;
file_edit_positions_set_scroll(&edit_pos, scroll);
edit_pos.last_set_type = EditPos_None;
view_set_edit_pos(view, edit_pos);
}
////////////////////////////////
internal void
view_set_file(Thread_Context *tctx, Models *models, View *view, Editing_File *file){
Assert(file != 0);
Editing_File *old_file = view->file;
if (models->view_change_buffer != 0){
Application_Links app = {};
app.tctx = tctx;
app.cmd_context = models;
models->view_change_buffer(&app, view_get_id(&models->view_set, view),
(old_file != 0)?old_file->id:0, file->id);
}
if (old_file != 0){
file_touch(&models->working_set, old_file);
file_edit_positions_push(old_file, view_get_edit_pos(view));
}
view->file = file;
File_Edit_Positions edit_pos = file_edit_positions_pop(file);
view_set_edit_pos(view, edit_pos);
view->mark = edit_pos.cursor_pos;
Buffer_Cursor cursor = view_compute_cursor(view, seek_pos(edit_pos.cursor_pos));
Vec2_f32 p = view_relative_xy_of_pos(tctx, models, view, cursor.line, edit_pos.cursor_pos);
view->preferred_x = p.x;
models->layout.panel_state_dirty = true;
}
////////////////////////////////
function void
view_push_context(View *view, View_Context *ctx){
Temp_Memory pop_me = begin_temp(&view->node_arena);
View_Context_Node *node = push_array_zero(&view->node_arena, View_Context_Node, 1);
sll_stack_push(view->ctx, node);
node->pop_me = pop_me;
block_copy_struct(&node->ctx, ctx);
node->delta_rule_memory = push_array_zero(&view->node_arena, u8, ctx->delta_rule_memory_size);
}
function void
view_alter_context(View *view, View_Context *ctx){
View_Context_Node *node = view->ctx;
Assert(node != 0);
block_copy_struct(&node->ctx, ctx);
}
function void
view_pop_context(View *view){
View_Context_Node *node = view->ctx;
if (node != 0 && node->next != 0){
sll_stack_pop(view->ctx);
end_temp(node->pop_me);
}
}
function View_Context_Node*
view_current_context_node(View *view){
return(view->ctx);
}
function View_Context
view_current_context(View *view){
View_Context ctx = {};
View_Context_Node *node = view->ctx;
if (node != 0){
block_copy_struct(&ctx, &node->ctx);
}
return(ctx);
}
////////////////////////////////
internal Coroutine*
co_handle_request(Thread_Context *tctx, Models *models, Coroutine *co, Co_Out *out){
Coroutine *result = 0;
switch (out->request){
case CoRequest_NewFontFace:
{
Face_Description *description = out->face_description;
Face *face = font_set_new_face(&models->font_set, description);
Co_In in = {};
in.face_id = (face != 0)?face->id:0;
result = coroutine_run(&models->coroutines, co, &in, out);
}break;
case CoRequest_ModifyFace:
{
Face_Description *description = out->face_description;
Face_ID face_id = out->face_id;
Co_In in = {};
in.success = font_set_modify_face(&models->font_set, face_id, description);
result = coroutine_run(&models->coroutines, co, &in, out);
}break;
case CoRequest_AcquireGlobalFrameMutex:
{
system_acquire_global_frame_mutex(tctx);
result = coroutine_run(&models->coroutines, co, 0, out);
}break;
case CoRequest_ReleaseGlobalFrameMutex:
{
system_release_global_frame_mutex(tctx);
result = coroutine_run(&models->coroutines, co, 0, out);
}break;
}
return(result);
}
internal Coroutine*
co_run(Thread_Context *tctx, Models *models, Coroutine *co, Co_In *in, Co_Out *out){
Coroutine *result = coroutine_run(&models->coroutines, co, in, out);
for (;result != 0 && out->request != CoRequest_None;){
result = co_handle_request(tctx, models, result, out);
}
return(result);
}
internal void
view_event_context_base__inner(Coroutine *coroutine){
Co_In *in = (Co_In*)coroutine->in;
Models *models = in->models;
Custom_Command_Function *event_context_base = in->event_context_base;
Assert(event_context_base != 0);
Application_Links app = {};
app.tctx = coroutine->tctx;
app.cmd_context = models;
event_context_base(&app);
}
function void
view_init(Thread_Context *tctx, Models *models, View *view, Editing_File *initial_buffer,
Custom_Command_Function *event_context_base){
view_set_file(tctx, models, view, initial_buffer);
view->node_arena = make_arena_system();
View_Context first_ctx = {};
first_ctx.render_caller = models->render_caller;
first_ctx.delta_rule = models->delta_rule;
first_ctx.delta_rule_memory_size = models->delta_rule_memory_size;
view_push_context(view, &first_ctx);
view->cursor_margin = V2f32(0.f, 0.f);
view->cursor_push_in_multiplier = V2f32(1.5f, 1.5f);
view->co = coroutine_create(&models->coroutines, view_event_context_base__inner);
view->co->user_data = view;
Co_In in = {};
in.models = models;
in.event_context_base = event_context_base;
view->co = co_run(tctx, models, view->co, &in, &view->co_out);
// TODO(allen): deal with this kind of problem!
Assert(view->co != 0);
}
// TODO(allen): This doesn't make any sense!!!!!! COROUTINE SHUTDOWN? VIEW CLOSING? WADAFUQ?
function b32
view_close(Models *models, View *view){
Layout *layout = &models->layout;
b32 result = false;
if (layout_close_panel(layout, view->panel)){
if (view->co != 0){
models_push_wind_down(models, view->co);
}
live_set_free_view(&models->lifetime_allocator, &models->view_set, view);
result = true;
}
return(result);
}
internal void
view_check_co_exited(Models *models, View *view){
if (view->co == 0){
b32 result = view_close(models, view);
// TODO(allen): Here it looks like the final view has
// exited from it's event handler. We should probably
// have a failsafe restarter for the event handler when
// this happens.
Assert(result);
}
}
// TODO(allen): This is dumb. Let's rethink view cleanup strategy.
internal void
co_single_abort(Thread_Context *tctx, Models *models, View *view){
Coroutine *co = view->co;
Co_In in = {};
in.user_input.abort = true;
view->co = co_run(tctx, models, co, &in, &view->co_out);
view_check_co_exited(models, view);
}
internal void
co_full_abort(Thread_Context *tctx, Models *models, View *view){
Coroutine *co = view->co;
Co_In in = {};
in.user_input.abort = true;
for (u32 j = 0; j < 100 && co != 0; ++j){
co = co_run(tctx, models, co, &in, &view->co_out);
}
if (co != 0){
Application_Links app = {};
app.tctx = tctx;
app.cmd_context = models;
#define M "SERIOUS ERROR: full stack abort did not complete"
print_message(&app, string_u8_litexpr(M));
#undef M
}
view->co = 0;
init_query_set(&view->query_set);
}
function b32
co_send_event(Thread_Context *tctx, Models *models, View *view, Input_Event *event){
b32 event_was_handled = false;
Coroutine *co = view->co;
Co_Out *co_out = &view->co_out;
{
models->current_input_unhandled = false;
Co_In in = {};
in.user_input.event = *event;
in.user_input.abort = false;
begin_handling_input(models, &in.user_input);
view->co = co_run(tctx, models, view->co, &in, &view->co_out);
view_check_co_exited(models, view);
if (!(event->kind == InputEventKind_Core && event->core.code == CoreCode_Animate)){
models->animate_next_frame = true;
}
event_was_handled = !models->current_input_unhandled;
}
return(event_was_handled);
}
function b32
co_send_core_event(Thread_Context *tctx, Models *models, View *view, Core_Code code, String_Const_u8 string){
Input_Event event = {};
event.kind = InputEventKind_Core;
event.core.code = code;
event.core.string = string;
return(co_send_event(tctx, models, view, &event));
}
function b32
co_send_core_event(Thread_Context *tctx, Models *models, View *view, Core_Code code, Buffer_ID id){
Input_Event event = {};
event.kind = InputEventKind_Core;
event.core.code = code;
event.core.id = id;
return(co_send_event(tctx, models, view, &event));
}
function b32
co_send_core_event(Thread_Context *tctx, Models *models, View *view, Core_Code code){
return(co_send_core_event(tctx, models, view, code, SCu8()));
}
function b32
co_send_event(Thread_Context *tctx, Models *models, Input_Event *event){
Panel *active_panel = models->layout.active_panel;
View *view = active_panel->view;
return(co_send_event(tctx, models, view, event));
}
function b32
co_send_core_event(Thread_Context *tctx, Models *models, Core_Code code, String_Const_u8 string){
Panel *active_panel = models->layout.active_panel;
View *view = active_panel->view;
return(co_send_core_event(tctx, models, view, code, string));
}
function b32
co_send_core_event(Thread_Context *tctx, Models *models, Core_Code code, Buffer_ID buffer_id){
Panel *active_panel = models->layout.active_panel;
View *view = active_panel->view;
return(co_send_core_event(tctx, models, view, code, buffer_id));
}
function b32
co_send_core_event(Thread_Context *tctx, Models *models, Core_Code code){
return(co_send_core_event(tctx, models, code, SCu8()));
}
////////////////////////////////
function void
view_quit_ui(Thread_Context *tctx, Models *models, View *view){
for (u32 j = 0;; j += 1){
if (j == 100){
Application_Links app = {};
app.tctx = tctx;
app.cmd_context = models;
#define M "SERIOUS ERROR: view quit ui did not complete"
print_message(&app, string_u8_litexpr(M));
#undef M
break;
}
View_Context_Node *ctx = view->ctx;
if (ctx->next == 0){
break;
}
co_single_abort(tctx, models, view);
}
}
////////////////////////////////
internal b32
file_is_viewed(Layout *layout, Editing_File *file){
b32 is_viewed = false;
for (Panel *panel = layout_get_first_open_panel(layout);
panel != 0;
panel = layout_get_next_open_panel(layout, panel)){
View *view = panel->view;
if (view->file == file){
is_viewed = true;
break;
}
}
return(is_viewed);
}
internal void
adjust_views_looking_at_file_to_new_cursor(Thread_Context *tctx, Models *models, Editing_File *file){
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;
if (view->file == file){
File_Edit_Positions edit_pos = view_get_edit_pos(view);
view_set_cursor(tctx, models, view, edit_pos.cursor_pos);
}
}
}
internal void
global_set_font_and_update_files(Models *models, Face *new_global_face){
for (Node *node = models->working_set.active_file_sentinel.next;
node != &models->working_set.active_file_sentinel;
node = node->next){
Editing_File *file = CastFromMember(Editing_File, main_chain_node, node);
file->settings.face_id = new_global_face->id;
}
models->global_face_id = new_global_face->id;
}
internal b32
release_font_and_update(Models *models, Face *face, Face *replacement_face){
b32 success = false;
Assert(replacement_face != 0 && replacement_face != face);
if (font_set_release_face(&models->font_set, face->id)){
for (Node *node = models->working_set.active_file_sentinel.next;
node != &models->working_set.active_file_sentinel;
node = node->next){
Editing_File *file = CastFromMember(Editing_File, main_chain_node, node);
if (file->settings.face_id == face->id){
file->settings.face_id = replacement_face->id;
}
}
if (models->global_face_id == face->id){
models->global_face_id = replacement_face->id;
}
success = true;
}
return(success);
}
////////////////////////////////
internal View*
imp_get_view(Models *models, View_ID view_id){
Live_Views *view_set = &models->view_set;
View *view = 0;
view_id -= 1;
if (0 <= view_id && view_id < view_set->max){
view = view_set->views + view_id;
if (!view->in_use){
view = 0;
}
}
return(view);
}
// BOTTOM

99
code/4ed_view.h Normal file
View File

@ -0,0 +1,99 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 17.07.2017
*
* File editing view for 4coder.
*
*/
// TOP
#if !defined(FRED_VIEW_H)
#define FRED_VIEW_H
struct Co_In{
union{
struct{
struct Models *models;
Custom_Command_Function *event_context_base;
};
User_Input user_input;
Face_ID face_id;
b32 success;
};
};
typedef i32 Co_Request;
enum{
CoRequest_None = 0,
CoRequest_NewFontFace = 1,
CoRequest_ModifyFace = 2,
CoRequest_AcquireGlobalFrameMutex = 3,
CoRequest_ReleaseGlobalFrameMutex = 4,
};
struct Co_Out{
Co_Request request;
Face_Description *face_description;
Face_ID face_id;
};
struct Query_Slot{
Query_Slot *next;
Query_Bar *query_bar;
};
struct Query_Set{
Query_Slot slots[8];
Query_Slot *free_slot;
Query_Slot *used_slot;
};
struct View_Context_Node{
View_Context_Node *next;
Temp_Memory pop_me;
View_Context ctx;
void *delta_rule_memory;
};
struct View{
View *next;
View *prev;
struct Panel *panel;
b32 in_use;
Editing_File *file;
Lifetime_Object *lifetime_object;
File_Edit_Positions edit_pos_;
i64 mark;
f32 preferred_x;
Vec2_f32 cursor_margin;
Vec2_f32 cursor_push_in_multiplier;
b8 new_scroll_target;
b8 hide_scrollbar;
b8 hide_file_bar;
b8 show_whitespace;
Coroutine *co;
Co_Out co_out;
Arena node_arena;
View_Context_Node *ctx;
Query_Set query_set;
};
struct Live_Views{
View *views;
View free_sentinel;
i32 count;
i32 max;
};
#endif
// BOTTOM

509
code/4ed_working_set.cpp Normal file
View File

@ -0,0 +1,509 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 03.01.2017
*
* Working_Set data structure
*
*/
// TOP
internal void
working_set_file_default_settings(Working_Set *working_set, Editing_File *file){
block_zero_struct(&file->settings);
}
////////////////////////////////
internal void
file_change_notification_check(Arena *scratch, Working_Set *working_set, Editing_File *file){
if (file->canon.name_size > 0 && !file->settings.unimportant){
String_Const_u8 name = SCu8(file->canon.name_space, file->canon.name_size);
File_Attributes attributes = system_quick_file_attributes(scratch, name);
if ((attributes.last_write_time > file->attributes.last_write_time) ||
(attributes.last_write_time == 0 && file->attributes.last_write_time > 0)){
if (file->state.save_state == FileSaveState_SavedWaitingForNotification){
file->state.save_state = FileSaveState_Normal;
file->attributes = attributes;
}
else{
file_add_dirty_flag(file, DirtyState_UnloadedChanges);
if (file->external_mod_node.next == 0){
LogEventF(log_string(M), &working_set->arena, file->id, 0, system_thread_get_id(),
"external modification [lwt=0x%llx]", attributes.last_write_time);
dll_insert_back(&working_set->has_external_mod_sentinel, &file->external_mod_node);
system_signal_step(0);
}
}
}
file->attributes = attributes;
}
}
internal void
file_change_notification_thread_main(void *ptr){
Models *models = (Models*)ptr;
Arena arena = make_arena_system();
Working_Set *working_set = &models->working_set;
for (;;){
system_sleep(Thousand(250));
Mutex_Lock lock(working_set->mutex);
if (working_set->active_file_count > 0){
i32 check_count = working_set->active_file_count/16;
check_count = clamp(1, check_count, 100);
Node *used = &working_set->active_file_sentinel;
Node *node = working_set->sync_check_iterator;
if (node == 0 || node == used){
node = used->next;
}
for (i32 i = 0; i < check_count; i += 1){
Editing_File *file = CastFromMember(Editing_File, main_chain_node, node);
node = node->next;
if (node == used){
node = node->next;
}
file_change_notification_check(&arena, working_set, file);
}
working_set->sync_check_iterator = node;
}
}
}
////////////////////////////////
internal Editing_File*
working_set_allocate_file(Working_Set *working_set, Lifetime_Allocator *lifetime_allocator){
Editing_File *file = working_set->free_files;
if (file == 0){
file = push_array(&working_set->arena, Editing_File, 1);
}
else{
sll_stack_pop(working_set->free_files);
}
block_zero_struct(file);
dll_insert_back(&working_set->active_file_sentinel, &file->main_chain_node);
dll_insert_back(&working_set->touch_order_sentinel, &file->touch_node);
working_set->active_file_count += 1;
file->id = working_set->id_counter;
working_set->id_counter += 1;
working_set_file_default_settings(working_set, file);
table_insert(&working_set->id_to_ptr_table,
(u64)file->id, (u64)(PtrAsInt(file)));
return(file);
}
internal void
working_set_free_file(Heap *heap, Working_Set *working_set, Editing_File *file){
if (working_set->sync_check_iterator == &file->main_chain_node){
working_set->sync_check_iterator = working_set->sync_check_iterator->next;
}
dll_remove(&file->main_chain_node);
dll_remove(&file->touch_node);
working_set->active_file_count -= 1;
table_erase(&working_set->id_to_ptr_table, file->id);
sll_stack_push(working_set->free_files, file);
}
internal Editing_File*
working_set_get_file(Working_Set *working_set, Buffer_ID id){
Editing_File *result = 0;
u64 val = 0;
if (table_read(&working_set->id_to_ptr_table, id, &val)){
result = (Editing_File*)(IntAsPtr(val));
}
return(result);
}
internal void
working_set_init(Models *models, Working_Set *working_set){
block_zero_struct(working_set);
working_set->arena = make_arena_system();
working_set->id_counter = 1;
dll_init_sentinel(&working_set->active_file_sentinel);
dll_init_sentinel(&working_set->touch_order_sentinel);
local_const i32 slot_count = 128;
Base_Allocator *allocator = get_base_allocator_system();
working_set->id_to_ptr_table = make_table_u64_u64(allocator, slot_count);
working_set->canon_table = make_table_Data_u64(allocator, slot_count);
working_set->name_table = make_table_Data_u64(allocator, slot_count);
dll_init_sentinel(&working_set->has_external_mod_sentinel);
working_set->mutex = system_mutex_make();
working_set->file_change_thread = system_thread_launch(file_change_notification_thread_main, models);
}
internal Editing_File*
working_set_contains__generic(Working_Set *working_set, Table_Data_u64 *table, String_Const_u8 name){
Editing_File *result = 0;
u64 val = 0;
if (table_read(table, make_data(name.str, name.size), &val)){
result = working_set_get_file(working_set, (Buffer_ID)val);
}
return(result);
}
internal b32
working_set_add__generic(Table_Data_u64 *table, Buffer_ID id, String_Const_u8 name){
return(table_insert(table, make_data(name.str, name.size), id));
}
internal void
working_set_remove__generic(Table_Data_u64 *table, String_Const_u8 name){
table_erase(table, make_data(name.str, name.size));
}
internal Editing_File*
working_set_contains_canon(Working_Set *working_set, String_Const_u8 name){
return(working_set_contains__generic(working_set, &working_set->canon_table, name));
}
internal b32
working_set_canon_add(Working_Set *working_set, Editing_File *file, String_Const_u8 name){
return(working_set_add__generic(&working_set->canon_table, file->id, name));
}
internal void
working_set_canon_remove(Working_Set *working_set, String_Const_u8 name){
working_set_remove__generic(&working_set->canon_table, name);
}
internal Editing_File*
working_set_contains_name(Working_Set *working_set, String_Const_u8 name){
return(working_set_contains__generic(working_set, &working_set->name_table, name));
}
internal b32
working_set_add_name(Working_Set *working_set, Editing_File *file, String_Const_u8 name){
return(working_set_add__generic(&working_set->name_table, file->id, name));
}
internal void
working_set_remove_name(Working_Set *working_set, String_Const_u8 name){
working_set_remove__generic(&working_set->name_table, name);
}
internal Editing_File*
get_file_from_identifier(Working_Set *working_set, Buffer_Identifier buffer){
Editing_File *file = 0;
if (buffer.id != 0){
file = working_set_get_file(working_set, buffer.id);
}
else if (buffer.name != 0){
String_Const_u8 name = SCu8(buffer.name, buffer.name_len);
file = working_set_contains_name(working_set, name);
}
return(file);
}
////////////////////////////////
#if 0
// TODO(allen): Bring the clipboard fully to the custom side.
internal void
working_set_clipboard_clear(Heap *heap, Working_Set *working){
String_Const_u8 *str = working->clipboards;
for (i32 i = 0; i < working->clipboard_size; i += 1, str += 1){
heap_free(heap, str->str);
block_zero_struct(str);
}
working->clipboard_size = 0;
working->clipboard_current = 0;
}
internal String_Const_u8*
working_set_next_clipboard_string(Heap *heap, Working_Set *working, u64 str_size){
i32 clipboard_current = working->clipboard_current;
if (working->clipboard_size == 0){
clipboard_current = 0;
working->clipboard_size = 1;
}
else{
++clipboard_current;
if (clipboard_current >= working->clipboard_max_size){
clipboard_current = 0;
}
else if (working->clipboard_size <= clipboard_current){
working->clipboard_size = clipboard_current + 1;
}
}
String_Const_u8 *result = &working->clipboards[clipboard_current];
working->clipboard_current = clipboard_current;
if (result->str != 0){
heap_free(heap, result->str);
}
u8 *new_str = (u8*)heap_allocate(heap, (i32)(str_size + 1));
*result = SCu8(new_str, str_size);
return(result);
}
internal String_Const_u8*
working_set_clipboard_index(Working_Set *working, i32 index){
String_Const_u8 *result = 0;
i32 size = working->clipboard_size;
i32 current = working->clipboard_current;
if (index >= 0 && size > 0){
index = index % size;
index = current + size - index;
index = index % size;
result = &working->clipboards[index];
}
return(result);
}
#endif
////////////////////////////////
// TODO(allen): get rid of this???
internal b32
get_canon_name(Arena *scratch, String_Const_u8 file_name, Editing_File_Name *canon_name){
Temp_Memory temp = begin_temp(scratch);
String_Const_u8 canonical = system_get_canonical(scratch, file_name);
u64 size = Min(sizeof(canon_name->name_space), canonical.size);
block_copy(canon_name->name_space, canonical.str, size);
canon_name->name_size = size;
end_temp(temp);
file_name_terminate(canon_name);
return(canon_name->name_size > 0);
}
internal void
file_bind_file_name(Working_Set *working_set, Editing_File *file, String_Const_u8 canon_file_name){
Assert(file->unique_name.name_size == 0);
Assert(file->canon.name_size == 0);
u64 size = canon_file_name.size;
size = clamp_top(size, sizeof(file->canon.name_space) - 1);
file->canon.name_size = size;
block_copy(file->canon.name_space, canon_file_name.str, size);
file_name_terminate(&file->canon);
b32 result = working_set_canon_add(working_set, file, string_from_file_name(&file->canon));
Assert(result);
}
internal void
buffer_unbind_file(Working_Set *working_set, Editing_File *file){
Assert(file->unique_name.name_size == 0);
Assert(file->canon.name_size != 0);
working_set_canon_remove(working_set, string_from_file_name(&file->canon));
file->canon.name_size = 0;
}
internal b32
buffer_name_has_conflict(Working_Set *working_set, String_Const_u8 base_name){
b32 hit_conflict = false;
Node *used_nodes = &working_set->active_file_sentinel;
for (Node *node = used_nodes->next;
node != used_nodes;
node = node->next){
Editing_File *file_ptr = CastFromMember(Editing_File, main_chain_node, node);
if (file_ptr && string_match(base_name, string_from_file_name(&file_ptr->unique_name))){
hit_conflict = true;
break;
}
}
return(hit_conflict);
}
internal void
buffer_resolve_name_low_level(Arena *scratch, Working_Set *working_set, Editing_File_Name *name, String_Const_u8 base_name){
u64 size = base_name.size;
size = clamp_top(size, sizeof(name->name_space));
block_copy(name->name_space, base_name.str, size);
String_u8 string = Su8(name->name_space, size, sizeof(name->name_space));
u64 original_size = string.size;
u64 file_x = 0;
for (b32 hit_conflict = true; hit_conflict;){
hit_conflict = buffer_name_has_conflict(working_set, string.string);
if (hit_conflict){
file_x += 1;
string.size = original_size;
Temp_Memory temp = begin_temp(scratch);
String_Const_u8 int_str = string_from_integer(scratch, file_x, 10);
string_append(&string, string_u8_litexpr(" ("));
string_append(&string, int_str);
string_append(&string, string_u8_litexpr(")"));
end_temp(temp);
}
}
name->name_size = string.size;
}
internal void
buffer_bind_name_low_level(Arena *scratch, Working_Set *working_set, Editing_File *file, String_Const_u8 base_name, String_Const_u8 name){
Assert(file->base_name.name_size == 0);
Assert(file->unique_name.name_size == 0);
Editing_File_Name new_name = {};
buffer_resolve_name_low_level(scratch, working_set, &new_name, name);
{
u64 size = base_name.size;
size = clamp_top(size, sizeof(file->base_name.name_space));
block_copy(file->base_name.name_space, base_name.str, size);
file->base_name.name_size = size;
}
{
u64 size = new_name.name_size;
block_copy(file->unique_name.name_space, new_name.name_space, size);
file->unique_name.name_size = size;
}
b32 result = working_set_add_name(working_set, file, string_from_file_name(&file->unique_name));
Assert(result);
}
internal void
buffer_unbind_name_low_level(Working_Set *working_set, Editing_File *file){
Assert(file->base_name.name_size != 0);
Assert(file->unique_name.name_size != 0);
working_set_remove_name(working_set, string_from_file_name(&file->unique_name));
file->base_name.name_size = 0;
file->unique_name.name_size = 0;
}
internal void
buffer_bind_name(Thread_Context *tctx, Models *models, Arena *scratch, Working_Set *working_set, Editing_File *file, String_Const_u8 base_name){
Temp_Memory temp = begin_temp(scratch);
// List of conflict files.
struct Node_Ptr{
Node_Ptr *next;
Editing_File *file_ptr;
};
Node_Ptr *conflict_first = 0;
Node_Ptr *conflict_last = 0;
i32 conflict_count = 0;
{
Node_Ptr *node = push_array(scratch, Node_Ptr, 1);
sll_queue_push(conflict_first, conflict_last, node);
node->file_ptr = file;
conflict_count += 1;
}
Node *used_nodes = &working_set->active_file_sentinel;
for (Node *node = used_nodes->next;
node != used_nodes;
node = node->next){
Editing_File *file_ptr = CastFromMember(Editing_File, main_chain_node, node);
if (file_ptr != 0 && string_match(base_name, string_from_file_name(&file_ptr->base_name))){
Node_Ptr *new_node = push_array(scratch, Node_Ptr, 1);
sll_queue_push(conflict_first, conflict_last, new_node);
new_node->file_ptr = file_ptr;
conflict_count += 1;
}
}
// Fill conflict array.
Buffer_Name_Conflict_Entry *conflicts = push_array(scratch, Buffer_Name_Conflict_Entry, conflict_count);
{
i32 i = 0;
for (Node_Ptr *node = conflict_first;
node != 0;
node = node->next, i += 1){
Editing_File *file_ptr = node->file_ptr;
Buffer_Name_Conflict_Entry *entry = &conflicts[i];
entry->buffer_id = file_ptr->id;
entry->file_name = push_string_copy(scratch, string_from_file_name(&file_ptr->canon));
entry->base_name = push_string_copy(scratch, base_name);
String_Const_u8 b = base_name;
if (i > 0){
b = string_from_file_name(&file_ptr->unique_name);
}
u64 unique_name_capacity = 256;
u8 *unique_name_buffer = push_array(scratch, u8, unique_name_capacity);
Assert(b.size <= unique_name_capacity);
block_copy(unique_name_buffer, b.str, b.size);
entry->unique_name_in_out = unique_name_buffer;
entry->unique_name_len_in_out = b.size;
entry->unique_name_capacity = unique_name_capacity;
}
}
// Get user's resolution data.
if (models->buffer_name_resolver != 0){
Application_Links app = {};
app.tctx = tctx;
app.cmd_context = models;
models->buffer_name_resolver(&app, conflicts, conflict_count);
}
// Re-bind all of the files
{
i32 i = 0;
for (Node_Ptr *node = conflict_first;
node != 0;
node = node->next, i += 1){
Editing_File *file_ptr = node->file_ptr;
if (file_ptr->unique_name.name_size > 0){
buffer_unbind_name_low_level(working_set, file_ptr);
}
}
}
{
i32 i = 0;
for (Node_Ptr *node = conflict_first;
node != 0;
node = node->next, i += 1){
Editing_File *file_ptr = node->file_ptr;
Buffer_Name_Conflict_Entry *entry = &conflicts[i];
String_Const_u8 unique_name = SCu8(entry->unique_name_in_out, entry->unique_name_len_in_out);
buffer_bind_name_low_level(scratch, working_set, file_ptr, base_name, unique_name);
}
}
end_temp(temp);
}
////////////////////////////////
internal void
file_touch(Working_Set *working_set, Editing_File *file){
Assert(file != 0);
dll_remove(&file->touch_node);
dll_insert(&working_set->touch_order_sentinel, &file->touch_node);
}
internal Editing_File*
file_get_next(Working_Set *working_set, Editing_File *file){
if (file != 0){
Node *node = file->touch_node.next;
file = CastFromMember(Editing_File, touch_node, node);
if (node == &working_set->touch_order_sentinel){
file = 0;
}
}
else{
if (working_set->active_file_count > 0){
Node *node = working_set->touch_order_sentinel.next;
file = CastFromMember(Editing_File, touch_node, node);
}
}
return(file);
}
////////////////////////////////
internal Editing_File*
imp_get_file(Models *models, Buffer_ID buffer_id){
Working_Set *working_set = &models->working_set;
return(working_set_get_file(working_set, buffer_id));
}
// BOTTOM

42
code/4ed_working_set.h Normal file
View File

@ -0,0 +1,42 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* 24.03.2018
*
* Working_Set data structure
*
*/
// TOP
#if !defined(FRED_WORKING_SET_H)
#define FRED_WORKING_SET_H
struct Working_Set{
// NOTE(allen): After initialization of file_change_thread
// the members of this struct should only be accessed by a thread
// who owns the mutex member.
Arena arena;
Editing_File *free_files;
Buffer_ID id_counter;
Node active_file_sentinel;
Node touch_order_sentinel;
i32 active_file_count;
Table_u64_u64 id_to_ptr_table;
Table_Data_u64 canon_table;
Table_Data_u64 name_table;
Node *sync_check_iterator;
Node has_external_mod_sentinel;
System_Mutex mutex;
System_Thread file_change_thread;
};
#endif
// BOTTOM

78
code/README.md Normal file
View File

@ -0,0 +1,78 @@
Welcome to the 4coder code base.
This codebase was authored by Allen Webster from 2014-2022, with help from a number of contributors:
+ Casey Muratori
+ "insofaras" Alex Baines
+ Yuval Dolev
+ Ryan Fleury
Also thanks to all those who supported the project, financially and/or through all your detailed feedback.
As of May 31st 2022, I am freezing this codebase and open sourcing it.
I *DO NOT* recommend learning from this codebase, especially not with an uncritical eye. It may be a useful reference for certain algorithms and architectural ideas, and it certainly contains some cautionary tales. But if you are a beginner, I encourage you to aim for more than emulating the style and structure of this codebase.
I will not be taking pull requests, or any other form of contribution in this repository. Since I am no longer maintaining this codebase, it is my intention that users who rely on 4coder will have the option to fork the codebase, fix their issues, or carry what I started in new directions according to their own vision if they would like.
The license I chose for this codebase is the very permissive MIT license. For the sake of clarity in the community of 4coder users, I ask that new forks of this codebase be given unique names.
In this readme you will find:
1. License (MIT)
2. Build Instructions
3. Notes on Major Issues
# License
Copyright (c) 2022 Allen Webster
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# Build Instructions
1. Create an empty folder named "4ed" to contain the codebase.
2. Clone the repository
3. Rename the folder containing the repository to "code"
4. At the same level as the "4ed" folder, clone the "4coder-non-source" repository
5. A. On windows setup the visual studio command line magic sauce so that "cl" works
B. On linux setup g++
C. On mac setup clang
6. Navigate to the "4ed/code" folder.
7. A. On windows run "bin\build.bat"
B. On linux run "bin\build-linux.sh"
C. On linux run "bin\build-mac.sh"
# Notes on Major Issues
1. The build system and organization of files is extremely complicated. There is a 4ed_build.cpp that defines how builds run, and the build scripts have to build and run this C++ file. The file is pretty chaotic since it cannot rely on the codebase's usual helpers. On top of that there is a totally separate build system for the custom layer which is also a big gigantic mess of its own. It involves several stages of compilation, and a number of metaprograms.
2. The documentation system is over complicated & the documentation is incomplete. There is very little documentation for the internals or the complicated layers of helpers.
3. The lexer generator is way too complicated, and the built-in support for language features is not fully developed. The background threaded parsing is not very carefully organized and is not very flexible, so it's hard to add new languages at any level of the system.
4. There are a few layers of overcomplicated configuration parsers.
5. Mac support has not been maintained for several versions.
6. The codebase has a very weak base layer with key features that were added very late, so lots of code was written in the absence of useful features to bind things together. To make matters worse the base layer is split by the distinction of custom layer & core layer, leading to some double definitions and some incosistencies.

721
code/bin/4ed_build.cpp Normal file
View File

@ -0,0 +1,721 @@
/*
* Mr. 4th Dimention - Allen Webster
*
* ??.??.????
*
* 4coder development build rule.
*
*/
// TOP
//#define FM_PRINT_COMMANDS
#include "4coder_base_types.h"
#include "4coder_version.h"
#include "4coder_base_types.cpp"
#include "4coder_malloc_allocator.cpp"
#define FTECH_FILE_MOVING_IMPLEMENTATION
#include "4coder_file_moving.h"
//
// OS and compiler index
//
typedef u32 Tier_Code;
enum{
Tier_Demo,
Tier_Super,
Tier_COUNT,
};
char *tier_names[] = {
"demo",
"super",
};
typedef u32 Platform_Code;
enum{
Platform_Windows,
Platform_Linux,
Platform_Mac,
//
Platform_COUNT,
Platform_None = Platform_COUNT,
};
char *platform_names[] = {
"win",
"linux",
"mac",
};
typedef u32 Compiler_Code;
enum{
Compiler_CL,
Compiler_GCC,
Compiler_Clang,
//
Compiler_COUNT,
Compiler_None = Compiler_COUNT,
};
char *compiler_names[] = {
"cl",
"gcc",
"clang",
};
typedef u32 Arch_Code;
enum{
Arch_X64,
Arch_X86,
//
Arch_COUNT,
Arch_None = Arch_COUNT,
};
char *arch_names[] = {
"x64",
"x86",
};
#if OS_WINDOWS
# define This_OS Platform_Windows
#elif OS_LINUX
# define This_OS Platform_Linux
#elif OS_MAC
# define This_OS Platform_Mac
#else
# error This platform is not enumerated.
#endif
#if COMPILER_CL
# define This_Compiler Compiler_CL
#elif COMPILER_GCC
# define This_Compiler Compiler_GCC
#elif COMPILER_CLANG
# define This_Compiler Compiler_Clang
#else
# error This compilers is not enumerated.
#endif
//
// Universal directories
//
#define BUILD_DIR "../build"
#define PACK_DIR "../distributions"
#define SITE_DIR "../site"
#define FOREIGN "../4coder-non-source/foreign"
#define FOREIGN_WIN "..\\4coder-non-source\\foreign"
char *includes[] = { "custom", FOREIGN "/freetype2", 0, };
//
// Platform layer file tables
//
char *windows_platform_layer[] = { "platform_win32/win32_4ed.cpp", 0 };
char *linux_platform_layer[] = { "platform_linux/linux_4ed.cpp", 0 };
char *mac_platform_layer[] = { "platform_mac/mac_4ed.mm", 0 };
char **platform_layers[Platform_COUNT] = {
windows_platform_layer,
linux_platform_layer ,
mac_platform_layer ,
};
char *windows_cl_platform_inc[] = { "platform_all", 0 };
char *linux_gcc_platform_inc[] = { "platform_all", "platform_unix", 0 };
char *mac_clang_platform_inc[] = { "platform_all", "platform_unix", 0 };
char **platform_includes[Platform_COUNT][Compiler_COUNT] = {
{windows_cl_platform_inc, 0 , 0},
{0 , linux_gcc_platform_inc, 0},
{0 , 0 , mac_clang_platform_inc},
};
char *default_custom_target = "../code/custom/4coder_default_bindings.cpp";
// NOTE(allen): Build flags
enum{
OPTS = 0x1,
LIBS = 0x2,
ICON = 0x4,
SHARED_CODE = 0x8,
DEBUG_INFO = 0x10,
OPTIMIZATION = 0x20,
SUPER = 0x40,
INTERNAL = 0x80,
SHIP = 0x100,
};
internal char**
get_defines_from_flags(Arena *arena, u32 flags){
char **result = 0;
if (HasFlag(flags, SHIP)){
result = fm_list(arena, fm_list_one_item(arena, "SHIP_MODE"), result);
}
if (HasFlag(flags, INTERNAL)){
result = fm_list(arena, fm_list_one_item(arena, "FRED_INTERNAL"), result);
}
if (HasFlag(flags, SUPER)){
result = fm_list(arena, fm_list_one_item(arena, "FRED_SUPER"), result);
}
return(result);
}
//
// build implementation: cl
//
#if COMPILER_CL
#define CL_OPTS \
"-W4 -wd4310 -wd4100 -wd4201 -wd4505 -wd4996 " \
"-wd4127 -wd4510 -wd4512 -wd4610 -wd4390 " \
"-wd4611 -wd4189 -WX -GR- -EHa- -nologo -FC"
#define CL_LIBS_COMMON \
"user32.lib winmm.lib gdi32.lib opengl32.lib comdlg32.lib userenv.lib "
#define CL_LIBS_X64 CL_LIBS_COMMON FOREIGN_WIN "\\x64\\freetype.lib"
#define CL_LIBS_X86 CL_LIBS_COMMON FOREIGN_WIN "\\x86\\freetype.lib"
#define CL_ICON "..\\4coder-non-source\\res\\icon.res"
internal void
build(Arena *arena, u32 flags, u32 arch, char *code_path, char **code_files, char *out_path, char *out_file, char **defines, char **exports, char **inc_folders){
Temp_Dir temp = fm_pushdir(out_path);
Build_Line line;
fm_init_build_line(&line);
if (arch == Arch_X86){
fm_add_to_line(line, "%s\\custom\\bin\\setup_cl_x86.bat &", code_path);
}
fm_add_to_line(line, "cl");
if (flags & OPTS){
fm_add_to_line(line, CL_OPTS);
}
switch (arch){
case Arch_X64: fm_add_to_line(line, "-DFTECH_64_BIT"); break;
case Arch_X86: fm_add_to_line(line, "-DFTECH_32_BIT"); break;
default: InvalidPath;
}
fm_add_to_line(line, "-I%s", code_path);
if (inc_folders != 0){
for (u32 i = 0; inc_folders[i] != 0; ++i){
char *str = fm_str(arena, code_path, "/", inc_folders[i]);
fm_add_to_line(line, "-I%s", str);
}
}
if (flags & LIBS){
switch (arch){
case Arch_X64: fm_add_to_line(line, CL_LIBS_X64); break;
case Arch_X86: fm_add_to_line(line, CL_LIBS_X86); break;
default: InvalidPath;
}
}
if (flags & ICON){
fm_add_to_line(line, CL_ICON);
}
if (flags & DEBUG_INFO){
fm_add_to_line(line, "-Zi");
fm_add_to_line(line, "-DDO_CRAZY_EXPENSIVE_ASSERTS");
}
if (flags & OPTIMIZATION){
fm_add_to_line(line, "-O2");
}
if (flags & SHARED_CODE){
fm_add_to_line(line, "-LD");
}
if (defines != 0){
for (u32 i = 0; defines[i] != 0; ++i){
char *define_flag = fm_str(arena, "-D", defines[i]);
fm_add_to_line(line, "%s", define_flag);
}
}
for (u32 i = 0; code_files[i]; ++i){
fm_add_to_line(line, "\"%s\\%s\"", code_path, code_files[i]);
}
fm_add_to_line(line, "-Fe%s", out_file);
fm_add_to_line(line, "-link -INCREMENTAL:NO -RELEASE -PDBALTPATH:%%_PDB%%");
switch (arch){
case Arch_X64: fm_add_to_line(line, "-MACHINE:X64"); break;
case Arch_X86: fm_add_to_line(line, "-MACHINE:X86"); break;
default: InvalidPath;
}
if (flags & DEBUG_INFO){
fm_add_to_line(line, "-DEBUG");
}
if (flags & SHARED_CODE){
Assert(exports != 0);
fm_add_to_line(line, "-OPT:REF");
for (u32 i = 0; exports[i] != 0; ++i){
char *str = fm_str(arena, "-EXPORT:", exports[i]);
fm_add_to_line(line, "%s", str);
}
}
else{
fm_add_to_line(line, "-NODEFAULTLIB:library");
}
fm_finish_build_line(&line);
//printf("%s\n", line.build_options);
systemf("%s", line.build_options);
fm_popdir(temp);
fflush(stdout);
}
//
// build implementation: gcc
//
#elif COMPILER_GCC
#if OS_LINUX
# define GCC_OPTS \
"-Wno-write-strings " \
"-D_GNU_SOURCE -fPIC " \
"-fno-threadsafe-statics -pthread " \
"-Wno-unused-result " \
"-std=c++11"
# define GCC_LIBS_COMMON \
"-lX11 -lpthread -lm -lrt " \
"-lGL -ldl -lXfixes -lfreetype -lfontconfig"
# define GCC_LIBS_X64 GCC_LIBS_COMMON
# define GCC_LIBS_X86 GCC_LIBS_COMMON
#else
# error gcc options not set for this platform
#endif
internal void
build(Arena *arena, u32 flags, u32 arch, char *code_path, char **code_files, char *out_path, char *out_file, char **defines, char **exports, char **inc_folders){
Build_Line line;
fm_init_build_line(&line);
switch (arch){
case Arch_X64:
fm_add_to_line(line, "-m64");
fm_add_to_line(line, "-DFTECH_64_BIT"); break;
case Arch_X86:
fm_add_to_line(line, "-m32");
fm_add_to_line(line, "-DFTECH_32_BIT"); break;
default: InvalidPath;
}
if (flags & OPTS){
fm_add_to_line(line, GCC_OPTS);
}
fm_add_to_line(line, "-I%s", code_path);
if (inc_folders != 0){
for (u32 i = 0; inc_folders[i] != 0; ++i){
char *str = fm_str(arena, code_path, "/", inc_folders[i]);
fm_add_to_line(line, "-I%s", str);
}
}
if (flags & DEBUG_INFO){
fm_add_to_line(line, "-g -O0");
}
if (flags & OPTIMIZATION){
fm_add_to_line(line, "-O3");
}
if (flags & SHARED_CODE){
fm_add_to_line(line, "-shared");
}
if (defines != 0){
for (u32 i = 0; defines[i]; ++i){
char *define_flag = fm_str(arena, "-D", defines[i]);
fm_add_to_line(line, "%s", define_flag);
}
}
fm_add_to_line(line, "-I\"%s\"", code_path);
for (u32 i = 0; code_files[i] != 0; ++i){
fm_add_to_line(line, "\"%s/%s\"", code_path, code_files[i]);
}
if (flags & LIBS){
if (arch == Arch_X64){
fm_add_to_line(line, GCC_LIBS_X64);
}
else if (arch == Arch_X86)
{
fm_add_to_line(line, GCC_LIBS_X86);
}
}
fm_finish_build_line(&line);
Temp_Dir temp = fm_pushdir(out_path);
systemf("g++ %s -o %s", line.build_options, out_file);
fm_popdir(temp);
}
#elif COMPILER_CLANG
#if OS_MAC
# define CLANG_OPTS \
"-Wno-write-strings -Wno-deprecated-declarations " \
"-Wno-comment -Wno-switch -Wno-null-dereference " \
"-Wno-tautological-compare -Wno-unused-result " \
"-Wno-missing-declarations -Wno-nullability-completeness " \
"-std=c++11 "
#define CLANG_LIBS_COMMON \
"-framework Cocoa -framework QuartzCore " \
"-framework CoreServices " \
"-framework OpenGL -framework IOKit -framework Metal -framework MetalKit "
#define CLANG_LIBS_X64 CLANG_LIBS_COMMON \
FOREIGN "/x64/libfreetype-mac.a"
#define CLANG_LIBS_X86 CLANG_LIBS_COMMON \
FOREIGN "/x86/libfreetype-mac.a"
#else
# error clang options not set for this platform
#endif
internal void
build(Arena *arena, u32 flags, u32 arch, char *code_path, char **code_files, char *out_path, char *out_file, char **defines, char **exports, char **inc_folders){
Build_Line line;
fm_init_build_line(&line);
switch (arch){
case Arch_X64:
fm_add_to_line(line, "-m64");
fm_add_to_line(line, "-DFTECH_64_BIT"); break;
case Arch_X86:
fm_add_to_line(line, "-m32");
fm_add_to_line(line, "-DFTECH_32_BIT"); break;
default: InvalidPath;
}
if (flags & OPTS){
fm_add_to_line(line, CLANG_OPTS);
}
fm_add_to_line(line, "-I%s", code_path);
if (inc_folders != 0){
for (u32 i = 0; inc_folders[i] != 0; ++i){
char *str = fm_str(arena, code_path, "/", inc_folders[i]);
fm_add_to_line(line, "-I%s", str);
}
}
if (flags & DEBUG_INFO){
fm_add_to_line(line, "-g -O0");
}
if (flags & OPTIMIZATION){
fm_add_to_line(line, "-O3");
}
if (flags & SHARED_CODE){
fm_add_to_line(line, "-shared");
}
if (defines != 0){
for (u32 i = 0; defines[i]; ++i){
char *define_flag = fm_str(arena, "-D", defines[i]);
fm_add_to_line(line, "%s", define_flag);
}
}
fm_add_to_line(line, "-I\"%s\"", code_path);
for (u32 i = 0; code_files[i] != 0; ++i){
fm_add_to_line(line, "\"%s/%s\"", code_path, code_files[i]);
}
if (flags & LIBS){
if (arch == Arch_X64){
fm_add_to_line(line, CLANG_LIBS_X64);
}
else if (arch == Arch_X86)
{
fm_add_to_line(line, CLANG_LIBS_X86);
}
}
fm_finish_build_line(&line);
Temp_Dir temp = fm_pushdir(out_path);
// systemf("clang++ %s -E -o %s", line.build_options, "4ed.i");
systemf("clang++ %s -o %s", line.build_options, out_file);
fm_popdir(temp);
}
#else
# error build function not defined for this compiler
#endif
internal void
build(Arena *arena, u32 flags, u32 arch, char *code_path, char *code_file, char *out_path, char *out_file, char **defines, char **exports, char **inc_folders){
char **code_files = fm_list_one_item(arena, code_file);
build(arena, flags, arch, code_path, code_files, out_path, out_file, defines, exports, inc_folders);
}
internal void
build_and_run(Arena *arena, char *cdir, char *filename, char *name, u32 flags){
char *dir = fm_str(arena, BUILD_DIR);
{
char *file = fm_str(arena, filename);
build(arena, flags, Arch_X64, cdir, file, dir, name, get_defines_from_flags(arena, flags), 0, includes);
}
if (prev_error == 0){
char *cmd = fm_str(arena, dir, "/", name);
fm_execute_in_dir(cdir, cmd, 0);
}
}
internal void
buildsuper(Arena *arena, char *cdir, char *file, u32 arch){
printf("BUILDSUPER:\n cdir = %s;\n file = %s;\n arch = %s;\n", cdir, file, arch_names[arch]);
fflush(stdout);
Temp_Dir temp = fm_pushdir(fm_str(arena, BUILD_DIR));
char *build_script_postfix = "";
switch (This_OS){
case Platform_Windows:
{
build_script_postfix = "-win";
}break;
case Platform_Linux:
{
build_script_postfix = "-linux";
}break;
case Platform_Mac:
{
build_script_postfix = "-mac";
}break;
}
char *build_script = fm_str(arena, "custom/bin/buildsuper_", arch_names[arch], build_script_postfix, BAT);
char *build_command = fm_str(arena, "\"", cdir, "/", build_script, "\" \"", file, "\"");
if (This_OS == Platform_Windows){
build_command = fm_str(arena, "call ", build_command);
}
systemf("%s", build_command);
fm_popdir(temp);
fflush(stdout);
}
internal void
build_main(Arena *arena, char *cdir, b32 update_local_theme, u32 flags, u32 arch){
char *dir = fm_str(arena, BUILD_DIR);
{
char *file = fm_str(arena, "4ed_app_target.cpp");
char **exports = fm_list_one_item(arena, "app_get_functions");
char **build_includes = includes;
build(arena, OPTS | SHARED_CODE | flags, arch, cdir, file, dir, "4ed_app" DLL, get_defines_from_flags(arena, flags), exports, build_includes);
}
{
char **inc = (char**)fm_list(arena, includes, platform_includes[This_OS][This_Compiler]);
build(arena, OPTS | LIBS | ICON | flags, arch, cdir, platform_layers[This_OS], dir, "4ed", get_defines_from_flags(arena, flags), 0, inc);
}
if (update_local_theme){
char *themes_folder = fm_str(arena, "../build/themes");
char *source_themes_folder = fm_str(arena, "ship_files/themes");
fm_clear_folder(themes_folder);
fm_make_folder_if_missing(arena, themes_folder);
fm_copy_all(source_themes_folder, themes_folder);
}
fflush(stdout);
}
internal void
standard_build(Arena *arena, char *cdir, u32 flags, u32 arch){
buildsuper(arena, cdir, fm_str(arena, default_custom_target), arch);
build_main(arena, cdir, true, flags, arch);
}
internal char*
get_4coder_dist_name(Arena *arena, u32 platform, char *tier, u32 arch){
char *name = fm_str(arena, "4coder-" MAJOR_STR "-" MINOR_STR "-" PATCH_STR "-", tier);
if (platform != Platform_None){
name = fm_str(arena, name, "-", platform_names[platform]);
}
if (arch != Arch_None){
name = fm_str(arena, name, "-", arch_names[arch]);
}
return(name);
}
function void
package_for_arch(Arena *arena, u32 arch, char *cdir, char *build_dir, char *pack_dir, i32 tier, char *tier_name, char *current_dist_tier, u32 flags, char** dist_files, i32 dist_file_count){
char *arch_name = arch_names[arch];
char *parent_dir = fm_str(arena, current_dist_tier, "_", arch_name);
char *dir = fm_str(arena, parent_dir, SLASH "4coder");
char *zip_dir = fm_str(arena, pack_dir, SLASH, tier_name, "_", arch_name);
printf("\nBUILD: %s_%s\n", tier_name, arch_name);
printf(" parent_dir = %s;\n", parent_dir);
printf(" dir = %s;\n", dir);
printf(" zip_dir = %s;\n", zip_dir);
fflush(stdout);
buildsuper(arena, cdir, fm_str(arena, default_custom_target), arch);
build_main(arena, cdir, false, flags, arch);
fm_clear_folder(parent_dir);
fm_make_folder_if_missing(arena, parent_dir);
fm_make_folder_if_missing(arena, dir);
fm_copy_file(fm_str(arena, build_dir, "/4ed" EXE), fm_str(arena, dir, "/4ed" EXE));
fm_copy_file(fm_str(arena, build_dir, "/4ed_app" DLL), fm_str(arena, dir, "/4ed_app" DLL));
fm_copy_file(fm_str(arena, build_dir, "/custom_4coder" DLL), fm_str(arena, dir, "/custom_4coder" DLL));
if (tier == Tier_Demo){
dist_file_count -= 1;
}
for (i32 j = 0; j < dist_file_count; j += 1){
fm_copy_all(dist_files[j], dir);
}
if (tier == Tier_Super){
char *custom_src_dir = fm_str(arena, cdir, SLASH, "custom");
char *custom_dst_dir = fm_str(arena, dir, SLASH, "custom");
fm_make_folder_if_missing(arena, custom_dst_dir);
fm_copy_all(custom_src_dir, custom_dst_dir);
}
char *dist_name = get_4coder_dist_name(arena, This_OS, tier_name, arch);
char *zip_name = fm_str(arena, zip_dir, SLASH, dist_name, ".zip");
fm_make_folder_if_missing(arena, zip_dir);
fm_zip(parent_dir, "4coder", zip_name);
}
internal u32
tier_flags(Tier_Code code){
u32 result = 0;
switch (code){
case Tier_Super:
{
result = SUPER;
}break;
}
return(result);
}
internal void
package(Arena *arena, char *cdir, Tier_Code tier, Arch_Code arch){
// NOTE(allen): meta
char *build_dir = fm_str(arena, BUILD_DIR);
char *pack_dir = fm_str(arena, PACK_DIR);
char *dist_files[3];
dist_files[0] = fm_str(arena, "../4coder-non-source/dist_files");
dist_files[1] = fm_str(arena, "ship_files");
dist_files[2] = fm_str(arena, "ship_files_super");
printf("build dir: %s\n", build_dir);
printf("pack dir: %s\n", pack_dir);
printf("dist files: %s, %s, %s\n", dist_files[0], dist_files[1], dist_files[2]);
fflush(stdout);
u32 base_flags = SHIP | DEBUG_INFO | OPTIMIZATION;
fm_make_folder_if_missing(arena, pack_dir);
char *tier_name = tier_names[tier];
u32 flags = base_flags | tier_flags(tier);
Temp_Memory temp = begin_temp(arena);
char *current_dist_tier = fm_str(arena, ".." SLASH "current_dist_", tier_name);
package_for_arch(arena, arch, cdir, build_dir, pack_dir, tier, tier_name, current_dist_tier, flags, dist_files, ArrayCount(dist_files));
end_temp(temp);
}
int main(int argc, char **argv){
Arena arena = fm_init_system(DetailLevel_FileOperations);
char cdir[256];
i32 n = fm_get_current_directory(cdir, sizeof(cdir));
Assert(n < sizeof(cdir));
u32 flags = SUPER;
u32 arch = Arch_X64;
#if defined(DEV_BUILD) || defined(DEV_BUILD_X86)
flags |= DEBUG_INFO | INTERNAL;
#endif
#if defined(OPT_BUILD) || defined(OPT_BUILD_X86)
flags |= OPTIMIZATION;
#endif
#if defined(DEV_BUILD_X86) || defined(OPT_BUILD_X86)
arch = Arch_X86;
#endif
#if defined(DEV_BUILD) || defined(OPT_BUILD) || defined(DEV_BUILD_X86) || defined(OPT_BUILD_X86)
standard_build(&arena, cdir, flags, arch);
#elif defined(PACKAGE_DEMO_X64)
package(&arena, cdir, Tier_Demo, Arch_X64);
#elif defined(PACKAGE_DEMO_X86)
package(&arena, cdir, Tier_Demo, Arch_X86);
#elif defined(PACKAGE_SUPER_X64)
package(&arena, cdir, Tier_Super, Arch_X64);
#elif defined(PACKAGE_SUPER_X86)
package(&arena, cdir, Tier_Super, Arch_X86);
#else
# error No build type specified.
#endif
return(error_state);
}
// BOTTOM

35
code/bin/build-linux.sh Executable file
View File

@ -0,0 +1,35 @@
#!/bin/bash
# If any command errors, stop the script
set -e
# Set up directories
ME="$(readlink -f "$0")"
LOCATION="$(dirname "$ME")"
SRC_ROOT="$(dirname "$LOCATION")"
PROJECT_ROOT="$(dirname "$SRC_ROOT")"
if [ ! -d "$PROJECT_ROOT/build" ]; then
mkdir "$PROJECT_ROOT/build"
fi
BUILD_ROOT="$PROJECT_ROOT/build"
BIN_ROOT="$SRC_ROOT/bin"
CUSTOM_ROOT="$SRC_ROOT/custom"
CUSTOM_BIN="$CUSTOM_ROOT/bin"
# Get the build mode
BUILD_MODE="$1"
if [ -z "$BUILD_MODE" ]; then
BUILD_MODE="-DDEV_BUILD"
fi
WARNINGS="-Wno-write-strings -Wno-comment"
FLAGS="-D_GNU_SOURCE -fPIC -fpermissive $BUILD_MODE"
INCLUDES="-I$SRC_ROOT -I$CUSTOM_ROOT"
# Execute
g++ $WARNINGS $FLAGS $INCLUDES "$BIN_ROOT/4ed_build.cpp" -g -o "$BUILD_ROOT/build"
pushd "$SRC_ROOT"
"$BUILD_ROOT/build"
popd

35
code/bin/build-mac.sh Executable file
View File

@ -0,0 +1,35 @@
#!/bin/bash
# If any command errors, stop the script
set -e
# Set up directories
# NOTE(yuval): Replaced readlink with realpath which works for both macOS and Linux
ME="$(realpath "$0")"
LOCATION="$(dirname "$ME")"
SRC_ROOT="$(dirname "$LOCATION")"
PROJECT_ROOT="$(dirname "$SRC_ROOT")"
if [ ! -d "$PROJECT_ROOT/build" ]; then
mkdir "$PROJECT_ROOT/build"
fi
BUILD_ROOT="$PROJECT_ROOT/build"
BIN_ROOT="$SRC_ROOT/bin"
CUSTOM_ROOT="$SRC_ROOT/custom"
CUSTOM_BIN="$CUSTOM_ROOT/bin"
# Get the build mode
BUILD_MODE="$1"
if [ -z "$BUILD_MODE" ]; then
BUILD_MODE="-DDEV_BUILD"
fi
WARNINGS="-Wno-write-strings -Wno-comment -Wno-null-dereference -Wno-logical-op-parentheses -Wno-switch"
FLAGS="-D_GNU_SOURCE -fPIC -fpermissive $BUILD_MODE"
INCLUDES="-I$SRC_ROOT -I$CUSTOM_ROOT"
# Execute
clang++ $WARNINGS $FLAGS $INCLUDES "$BIN_ROOT/4ed_build.cpp" -g -o "$BUILD_ROOT/build"
pushd "$SRC_ROOT"
"$BUILD_ROOT/build"
popd

7
code/bin/build-x86-linux.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
ME="$(readlink -f "$0")"
LOCATION="$(dirname "$ME")"
$LOCATION/build-linux.sh -DDEV_BUILD_X86

2
code/bin/build-x86.bat Normal file
View File

@ -0,0 +1,2 @@
@echo off
bin\build.bat /DDEV_BUILD_X86

32
code/bin/build.bat Normal file
View File

@ -0,0 +1,32 @@
@echo off
set location=%cd%
set me="%~dp0"
cd %me%
cd ..
set src_root=%cd%
cd ..\build
set build_root=%cd%
set bin_root=%src_root%\bin
set custom_root=%src_root%\custom
set custom_bin=%custom_root\bin
cd %location%
set mode=%1
if "%mode%" == "" (set mode="/DDEV_BUILD")
set opts=/W4 /wd4310 /wd4100 /wd4201 /wd4505 /wd4996 /wd4127 /wd4510 /wd4512 /wd4610 /wd4390 /wd4189 /WX
set opts=%opts% /GR- /EHa- /nologo /FC /Zi
set opts=%opts% /I%src_root% /I%custom_root%
set opts=%opts% %mode%
set FirstError=0
pushd %build_root%
call cl %opts% kernel32.lib %bin_root%\4ed_build.cpp /Febuild
if %ERRORLEVEL% neq 0 (set FirstError=1)
if %ERRORLEVEL% neq 0 (goto END)
popd
%build_root%\build
:END
if %ERRORLEVEL% neq 0 (set FirstError=1)

View File

@ -0,0 +1,3 @@
@echo off
call bin\build.bat /DOPT_BUILD

9
code/bin/detect_os.sh Executable file
View File

@ -0,0 +1,9 @@
#!/bin/bash
os="unknown"
if [[ "$OSTYPE" == "darwin"* ]]; then
echo "mac"
elif [[ "$OSTYPE" == "linux-gnu" ]]; then
echo "linux"
fi

33
code/bin/itchio_push_all.sh Executable file
View File

@ -0,0 +1,33 @@
#!/bin/sh
if [ "$#" -lt "3" ]
then
echo need 3 parameters
exit
else
fake=$1
maj=$2
min=$3
vr=$fake.$maj.$min
fv=$fake-$maj-$min
flags="--fix-permissions --userversion=$vr"
dir=../current_dist_all_os
butler push $flags $dir/4coder-alpha-$fv-win-x64.zip 4coder/4coder:win-x64-alpha
butler push $flags $dir/4coder-alpha-$fv-linux-x64.zip 4coder/4coder:linux-x64-alpha
butler push $flags $dir/4coder-alpha-$fv-mac-x64.zip 4coder/4coder:osx-x64-alpha
butler push $flags $dir/4coder-alpha-$fv-win-x86.zip 4coder/4coder:win-x86-alpha
butler push $flags $dir/4coder-alpha-$fv-linux-x86.zip 4coder/4coder:linux-x86-alpha
butler push $flags $dir/4coder-alpha-$fv-mac-x86.zip 4coder/4coder:osx-x86-alpha
butler push $flags $dir/4coder-alpha-$fv-super-win-x64.zip 4coder/4coder:win-x64-super
butler push $flags $dir/4coder-alpha-$fv-super-linux-x64.zip 4coder/4coder:linux-x64-super
butler push $flags $dir/4coder-alpha-$fv-super-mac-x64.zip 4coder/4coder:osx-x64-super
butler push $flags $dir/4coder-alpha-$fv-super-win-x86.zip 4coder/4coder:win-x86-super
butler push $flags $dir/4coder-alpha-$fv-super-linux-x86.zip 4coder/4coder:linux-x86-super
butler push $flags $dir/4coder-alpha-$fv-super-mac-x86.zip 4coder/4coder:osx-x86-super
fi

View File

@ -0,0 +1,22 @@
#!/bin/sh
if [ "$#" -lt "3" ]
then
echo need 3 parameters
exit
else
fake=$1
maj=$2
min=$3
vr=$fake.$maj.$min
fv=$fake-$maj-$min
flags="--fix-permissions --userversion=$vr"
dir=../distributions
butler push $flags $dir/demo_x86/4coder-$fv-demo-linux-x86.zip 4coder/4coder:linux-x86-demo
butler push $flags $dir/super_x86/4coder-$fv-super-linux-x86.zip 4coder/4coder:linux-x86
fi

22
code/bin/itchio_push_linux.sh Executable file
View File

@ -0,0 +1,22 @@
#!/bin/sh
if [ "$#" -lt "3" ]
then
echo need 3 parameters
exit
else
fake=$1
maj=$2
min=$3
vr=$fake.$maj.$min
fv=$fake-$maj-$min
flags="--fix-permissions --userversion=$vr"
dir=../distributions
butler push $flags $dir/demo_x64/4coder-$fv-demo-linux-x64.zip 4coder/4coder:linux-x64-demo
butler push $flags $dir/super_x64/4coder-$fv-super-linux-x64.zip 4coder/4coder:linux-x64
fi

22
code/bin/itchio_push_mac.sh Executable file
View File

@ -0,0 +1,22 @@
#!/bin/sh
if [ "$#" -lt "3" ]
then
echo need 3 parameters
exit
else
fake=$1
maj=$2
min=$3
vr=$fake.$maj.$min
fv=$fake-$maj-$min
flags="--fix-permissions --userversion=$vr"
dir=../distributions
butler push $flags $dir/demo_x64/4coder-$fv-demo-mac-x64.zip 4coder/4coder:mac-x64-demo
butler push $flags $dir/super_x64/4coder-$fv-super-mac-x64.zip 4coder/4coder:mac-x64
fi

View File

@ -0,0 +1,26 @@
@echo off
IF "%3" == "" (echo need 3 parameters & GOTO END)
SET fake=%1
SET maj=%2
SET min=%3
SET vr=%fake%.%maj%.%min%
SET fv=%fake%-%maj%-%min%
SET flags=--fix-permissions --userversion=%vr%
pushd ..
SET dir=%CD%\distributions
butler push %flags% %dir%\demo_x64\4coder-%fv%-demo-win-x64.zip 4coder/4coder:win-x64-demo
butler push %flags% %dir%\super_x64\4coder-%fv%-super-win-x64.zip 4coder/4coder:win-x64
REM butler push %flags% %dir%\demo_x86\4coder-%fv%-demo-win-x86.zip 4coder/4coder:win-x86-demo
REM butler push %flags% %dir%\super_x86\4coder-%fv%-super-win-x86.zip 4coder/4coder:win-x86
popd
:END

5
code/bin/package-linux.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
chmod +x bin/build-linux.sh
bin/build-linux.sh "-DPACKAGE_DEMO_X64"
bin/build-linux.sh "-DPACKAGE_SUPER_X64"

5
code/bin/package-mac.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
chmod +x bin/build-mac.sh
bin/build-mac.sh "-DPACKAGE_DEMO_X64"
bin/build-mac.sh "-DPACKAGE_SUPER_X64"

5
code/bin/package-x86-linux.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
chmod +x bin/build-linux.sh
bin/build-linux.sh "-DPACKAGE_DEMO_X86"
bin/build-linux.sh "-DPACKAGE_SUPER_X86"

6
code/bin/package.bat Normal file
View File

@ -0,0 +1,6 @@
@echo off
call bin\build.bat /DPACKAGE_DEMO_X64
call bin\build.bat /DPACKAGE_DEMO_X86
call bin\build.bat /DPACKAGE_SUPER_X64
call bin\build.bat /DPACKAGE_SUPER_X86

9
code/bin/zip.bat Normal file
View File

@ -0,0 +1,9 @@
@echo off
REM Usage: zip <archivename>
REM compresses the current directory into a zip named <archivename>.zip
SET ZIP_PATH=C:\Program Files (x86)\7-Zip
IF NOT EXIST "%ZIP_PATH%" (SET ZIP_PATH=C:\Program Files\7-Zip)
IF EXIST "%ZIP_PATH%" ("%ZIP_PATH%\7z.exe" a -tzip -y %*)

View File

@ -0,0 +1,32 @@
/*
* 4coder app links base allocator
*/
// TOP
Scratch_Block::Scratch_Block(Application_Links *app){
Thread_Context *t = this->tctx = get_thread_context(app);
this->arena = tctx_reserve(t);
this->temp = begin_temp(this->arena);
}
Scratch_Block::Scratch_Block(Application_Links *app, Arena *a1){
Thread_Context *t = this->tctx = get_thread_context(app);
this->arena = tctx_reserve(t, a1);
this->temp = begin_temp(this->arena);
}
Scratch_Block::Scratch_Block(Application_Links *app, Arena *a1, Arena *a2){
Thread_Context *t = this->tctx = get_thread_context(app);
this->arena = tctx_reserve(t, a1, a2);
this->temp = begin_temp(this->arena);
}
Scratch_Block::Scratch_Block(Application_Links *app, Arena *a1, Arena *a2, Arena *a3){
Thread_Context *t = this->tctx = get_thread_context(app);
this->arena = tctx_reserve(t, a1, a2, a3);
this->temp = begin_temp(this->arena);
}
// BOTTOM

View File

@ -0,0 +1,238 @@
/*
* 4coder_async_tasks.cpp - Implementation of the custom layer asynchronous task system.
*/
// TOP
// TODO(allen): put atomic wrappers in base type layer
#define atomic_write_b32(p,v) (*(p)=(v))
#define atomic_read_b32(p) (*(p))
global Async_System global_async_system = {};
function Async_Node*
async_pop_node(Async_System *async_system){
for (;async_system->task_count == 0;){
system_condition_variable_wait(async_system->cv, async_system->mutex);
}
Node *node = async_system->task_sent.next;
Assert(node != &async_system->task_sent);
dll_remove(node);
async_system->task_count -= 1;
Async_Node *a_node = CastFromMember(Async_Node, node, node);
a_node->next = 0;
return(a_node);
}
function Async_Node*
async_push_node__inner(Async_System *async_system, Async_Task_Function_Type *func, String_Const_u8 data){
Async_Task result = async_system->task_id_counter;
async_system->task_id_counter += 1;
Async_Node *node = async_system->free_nodes;
if (node == 0){
node = push_array(&async_system->node_arena, Async_Node, 1);
}
else{
sll_stack_pop(async_system->free_nodes);
}
node->task = result;
node->thread = 0;
node->func = func;
node->data.str = (u8*)heap_allocate(&async_system->node_heap, data.size);
block_copy(node->data.str, data.str, data.size);
node->data.size = data.size;
dll_insert_back(&async_system->task_sent, &node->node);
async_system->task_count += 1;
system_condition_variable_signal(async_system->cv);
return(node);
}
function Async_Task
async_push_node(Async_System *async_system, Async_Task_Function_Type *func, String_Const_u8 data){
Async_Node *node = async_push_node__inner(async_system, func, data);
return(node->task);
}
function void
async_free_node(Async_System *async_system, Async_Node *node){
heap_free(&async_system->node_heap, node->data.str);
sll_stack_push(async_system->free_nodes, node);
}
function void
async_task_thread(void *thread_ptr){
Base_Allocator *allocator = get_base_allocator_system();
Thread_Context_Extra_Info tctx_info = {};
tctx_info.async_thread = thread_ptr;
Thread_Context tctx_ = {};
Thread_Context *tctx = &tctx_;
thread_ctx_init(tctx, ThreadKind_AsyncTasks, allocator, allocator);
Async_Thread *thread = (Async_Thread*)thread_ptr;
Async_System *async_system = thread->async_system;
Application_Links app = {};
app.tctx = tctx;
app.cmd_context = async_system->cmd_context;
Profile_Global_List *list = get_core_profile_list(&app);
ProfileThreadName(tctx, list, string_u8_litexpr("async"));
Async_Context ctx = {&app, thread};
for (;;){
system_mutex_acquire(async_system->mutex);
Async_Node *node = async_pop_node(async_system);
node->thread = thread;
thread->node = node;
thread->task = node->task;
thread->cancel_signal = false;
system_mutex_release(async_system->mutex);
node->func(&ctx, node->data);
system_mutex_acquire(async_system->mutex);
node->thread = 0;
thread->node = 0;
thread->task = 0;
thread->cancel_signal = false;
async_free_node(async_system, node);
system_condition_variable_signal(async_system->join_cv);
system_mutex_release(async_system->mutex);
}
}
function Async_Node*
async_get_pending_node(Async_System *async_system, Async_Task task){
Async_Node *result = 0;
if (task != 0){
for (Node *node = async_system->task_sent.next;
node != &async_system->task_sent;
node = node->next){
Async_Node *a_node = CastFromMember(Async_Node, node, node);
if (a_node->task == task){
result = a_node;
break;
}
}
}
return(result);
}
function Async_Node*
async_get_running_node(Async_System *async_system, Async_Task task){
Async_Node *result = 0;
if (task != 0 && async_system->thread.task == task){
result = async_system->thread.node;
}
return(result);
}
////////////////////////////////
function void
async_task_handler_init(Application_Links *app, Async_System *async_system){
block_zero_struct(async_system);
async_system->cmd_context = app->cmd_context;
async_system->node_arena = make_arena_system(KB(4));
heap_init(&async_system->node_heap, &async_system->node_arena);
async_system->mutex = system_mutex_make();
async_system->cv = system_condition_variable_make();
async_system->join_cv = system_condition_variable_make();
dll_init_sentinel(&async_system->task_sent);
async_system->thread.async_system = async_system;
async_system->thread.thread = system_thread_launch(async_task_thread, &async_system->thread);
}
function Async_Task
async_task_no_dep(Async_System *async_system, Async_Task_Function_Type *func, String_Const_u8 data){
system_mutex_acquire(async_system->mutex);
Async_Task result = async_push_node(async_system, func, data);
system_mutex_release(async_system->mutex);
return(result);
}
function b32
async_task_is_pending(Async_System *async_system, Async_Task task){
system_mutex_acquire(async_system->mutex);
Async_Node *node = async_get_pending_node(async_system, task);
system_mutex_release(async_system->mutex);
return(node != 0);
}
function b32
async_task_is_running(Async_System *async_system, Async_Task task){
system_mutex_acquire(async_system->mutex);
Async_Node *node = async_get_running_node(async_system, task);
system_mutex_release(async_system->mutex);
return(node != 0);
}
function b32
async_task_is_running_or_pending__inner(Async_System *async_system, Async_Task task){
Async_Node *node = async_get_pending_node(async_system, task);
if (node == 0){
node = async_get_running_node(async_system, task);
}
return(node != 0);
}
function b32
async_task_is_running_or_pending(Async_System *async_system, Async_Task task){
system_mutex_acquire(async_system->mutex);
b32 result = async_task_is_running_or_pending__inner(async_system, task);
system_mutex_release(async_system->mutex);
return(result);
}
function void
async_task_wait__inner(Application_Links *app, Async_System *async_system, Async_Task task){
release_global_frame_mutex(app);
for (;async_task_is_running_or_pending__inner(async_system, task);){
system_condition_variable_wait(async_system->join_cv, async_system->mutex);
}
acquire_global_frame_mutex(app);
}
function void
async_task_wait(Application_Links *app, Async_System *async_system, Async_Task task){
system_mutex_acquire(async_system->mutex);
if (async_task_is_running_or_pending__inner(async_system, task)){
async_task_wait__inner(app, async_system, task);
}
system_mutex_release(async_system->mutex);
}
function void
async_task_cancel(Application_Links *app, Async_System *async_system, Async_Task task){
system_mutex_acquire(async_system->mutex);
Async_Node *node = async_get_pending_node(async_system, task);
if (node != 0){
dll_remove(&node->node);
async_system->task_count -= 1;
async_free_node(async_system, node);
}
else{
node = async_get_running_node(async_system, task);
if (node != 0){
b32 *cancel_signal = &node->thread->cancel_signal;
atomic_write_b32(cancel_signal, true);
async_task_wait__inner(app, async_system, task);
}
}
system_mutex_release(async_system->mutex);
}
function b32
async_check_canceled(Async_Context *actx){
b32 *cancel_signal = &actx->thread->cancel_signal;
b32 result = atomic_read_b32(cancel_signal);
return(result);
}
// BOTTOM

View File

@ -0,0 +1,56 @@
/*
* 4coder_async_tasks.cpp - Types for the custom layer asynchronous task system.
*/
// TOP
#if !defined(FCODER_ASYNC_TASKS_H)
#define FCODER_ASYNC_TASKS_H
typedef void Async_Task_Function_Type(struct Async_Context *actx, String_Const_u8 data);
typedef u64 Async_Task;
struct Async_Thread{
struct Async_System *async_system;
System_Thread thread;
struct Async_Node *node;
Async_Task task;
b32 cancel_signal;
};
struct Async_Node{
union{
Async_Node *next;
Node node;
};
Async_Task task;
Async_Thread *thread;
Async_Task_Function_Type *func;
String_Const_u8 data;
};
struct Async_System{
void *cmd_context;
Heap node_heap;
Arena node_arena;
System_Mutex mutex;
System_Condition_Variable cv;
System_Condition_Variable join_cv;
Async_Task task_id_counter;
Async_Node *free_nodes;
Node task_sent;
i32 task_count;
Async_Thread thread;
};
struct Async_Context{
Application_Links *app;
Async_Thread *thread;
};
#endif
// BOTTOM

View File

@ -0,0 +1,276 @@
////////////////////////////////
// NOTE(allen): Default Mixer Helpers
// TODO(allen): intrinsics wrappers
#if OS_LINUX
#include <immintrin.h>
#define _InterlockedExchangeAdd __sync_fetch_and_add
#elif OS_MAC
#include <immintrin.h>
#define _InterlockedExchangeAdd __sync_fetch_and_add
#else
#include <intrin.h>
#endif
function u32
AtomicAddU32AndReturnOriginal(u32 volatile *Value, u32 Addend)
{
// NOTE(casey): Returns the original value _prior_ to adding
u32 Result = _InterlockedExchangeAdd((long volatile*)Value, (long)Addend);
return(Result);
}
function void
def_audio_begin_ticket_mutex(Audio_System *Crunky)
{
u32 Ticket = AtomicAddU32AndReturnOriginal(&Crunky->ticket, 1);
while(Ticket != Crunky->serving) {_mm_pause();}
}
function void
def_audio_end_ticket_mutex(Audio_System *Crunky)
{
AtomicAddU32AndReturnOriginal(&Crunky->serving, 1);
}
////////////////////////////////
// NOTE(allen): Default Mixer
global Audio_System def_audio_system = {};
function void
def_audio_init(void){
block_zero_struct(&def_audio_system);
system_set_source_mixer(&def_audio_system, def_audio_mix_sources);
system_set_destination_mixer(def_audio_mix_destination);
}
function void
def_audio_play_clip(Audio_Clip clip, Audio_Control *control){
clip.control = control;
Audio_System *Crunky = &def_audio_system;
def_audio_begin_ticket_mutex(Crunky);
if (Crunky->pending_clip_count < ArrayCount(Crunky->pending_clips))
{
Crunky->pending_clips[Crunky->pending_clip_count++] = clip;
}
def_audio_end_ticket_mutex(Crunky);
}
internal b32
def_audio_is_playing(Audio_Control *control){
Audio_System *Crunky = &def_audio_system;
b32 result = (Crunky->generation - control->generation < 2);
return(result);
}
internal void
def_audio_stop(Audio_Control *control){
Audio_System *Crunky = &def_audio_system;
def_audio_begin_ticket_mutex(Crunky);
Audio_Clip *clip = Crunky->playing_clips;
for(u32 i = 0;
i < ArrayCount(Crunky->playing_clips);
i += 1, clip += 1){
if (clip->control == control){
clip->at_sample_index = clip->sample_count;
clip->control = 0;
}
}
control->loop = false;
def_audio_end_ticket_mutex(Crunky);
}
function void
def_audio_mix_sources(void *ctx, f32 *mix_buffer, u32 sample_count){
Audio_System *Crunky = (Audio_System*)ctx;
def_audio_begin_ticket_mutex(Crunky);
// NOTE(casey): Move pending sounds into the playing list
{
Crunky->generation += 1;
u32 PendIndex = 0;
Audio_Clip *clip = Crunky->playing_clips;
for(u32 DestIndex = 0;
(DestIndex < ArrayCount(Crunky->playing_clips)) && (PendIndex < Crunky->pending_clip_count);
DestIndex += 1, clip += 1)
{
if (clip->at_sample_index == clip->sample_count)
{
Audio_Control *control = clip->control;
if (control == 0 || !control->loop){
*clip = Crunky->pending_clips[PendIndex++];
}
}
}
Crunky->pending_clip_count = 0;
}
def_audio_end_ticket_mutex(Crunky);
// NOTE(casey): Mix all sounds into the output buffer
{
Audio_Clip *clip = Crunky->playing_clips;
for(u32 SoundIndex = 0;
SoundIndex < ArrayCount(Crunky->playing_clips);
SoundIndex += 1, clip += 1)
{
// NOTE(allen): Determine starting point
Audio_Control *control = clip->control;
if (control != 0 && control->loop && clip->at_sample_index == clip->sample_count){
clip->at_sample_index = 0;
}
u32 base_sample_index = clip->at_sample_index;
// NOTE(casey): Determine how many samples are left to play in this
// sound (possible none)
u32 SamplesToMix = clamp_top((clip->sample_count - clip->at_sample_index), sample_count);
clip->at_sample_index += SamplesToMix;
// NOTE(casey): Load the volume out of the control if there is one,
// and if there is, update the generation and sample index so
// external controllers can take action
f32 LeftVol = clip->channel_volume[0];
f32 RightVol = clip->channel_volume[1];
if(SamplesToMix && control != 0)
{
LeftVol *= control->channel_volume[0];
RightVol *= control->channel_volume[1];
control->generation = Crunky->generation;
control->last_played_sample_index = clip->at_sample_index;
}
// NOTE(casey): Mix samples
for(u32 SampleIndex = 0;
SampleIndex < SamplesToMix;
++SampleIndex)
{
u32 src_index = 2*(base_sample_index + SampleIndex);
f32 Left = LeftVol *(f32)clip->samples[src_index + 0];
f32 Right = RightVol*(f32)clip->samples[src_index + 1];
u32 dst_index = 2*SampleIndex;
mix_buffer[dst_index + 0] += Left;
mix_buffer[dst_index + 1] += Right;
}
}
}
}
function void
def_audio_mix_destination(i16 *dst, f32 *src, u32 sample_count){
u32 opl = sample_count*2;
for(u32 i = 0; i < opl; i += 1){
f32 sample = src[i];
f32 sat_sample = clamp(-32768.f, sample, 32767.f);
dst[i] = (i16)sat_sample;
}
}
////////////////////////////////
// NOTE(allen): Loading Clip
#if !defined(FCODER_SKIP_WAV)
#define FCODER_SKIP_WAV
#pragma pack(push, 1)
struct wave_fmt_data
{
u16 wFormatTag;
u16 wChannels;
u32 dwSamplesPerSec;
u32 dwAvgBytesPerSec;
u16 wBlockAlign;
u16 wBitsPerSample;
};
struct riff_header
{
u32 ID;
u32 DataSize;
};
#pragma pack(pop)
#endif
function Audio_Clip
audio_clip_from_wav_data(String_Const_u8 data){
Audio_Clip Result = {};
if (data.size >= 4 && *(u32 *)data.str == *(u32 *)"RIFF"){
// NOTE(casey): This ROM is in WAV format
riff_header *RootHeader = (riff_header *)data.str;
wave_fmt_data *Format = 0;
u32 SampleDataSize = 0;
i16 *Samples = 0;
u32 At = sizeof(riff_header);
u32 LastAt = At + ((RootHeader->DataSize + 1) & ~1);
if ((*(u32 *)(data.str + At) == *(u32 *)"WAVE") &&
(LastAt <= data.size)){
At += sizeof(u32);
while (At < LastAt){
riff_header *Header = (riff_header *)(data.str + At);
u32 DataAt = At + sizeof(riff_header);
u32 EndAt = DataAt + ((Header->DataSize + 1) & ~1);
if(EndAt <= data.size)
{
void *Data = (data.str + DataAt);
if(Header->ID == *(u32 *)"fmt ")
{
Format = (wave_fmt_data *)Data;
}
else if(Header->ID == *(u32 *)"data")
{
SampleDataSize = Header->DataSize;
Samples = (i16 *)Data;
}
}
At = EndAt;
}
}
if (Format &&
Samples &&
(Format->wFormatTag == 1) &&
(Format->wChannels == 2) &&
(Format->wBitsPerSample == 16) &&
(Format->dwSamplesPerSec == 48000)){
for (u32 i = 0; i < 2; i += 1){
Result.channel_volume[i] = 1.f;
}
Result.sample_count = SampleDataSize / (Format->wChannels*Format->wBitsPerSample/8);
Result.samples = (i16 *)Samples;
}
else{
// TODO(casey): This is where you would output an error - to 4coder somehow?
}
}
else{
// TODO(casey): This is where you would output an error - to 4coder somehow?
}
return(Result);
}
function Audio_Clip
audio_clip_from_wav_FILE(Arena *arena, FILE *file){
String_Const_u8 data = data_from_file(arena, file);
Audio_Clip result = audio_clip_from_wav_data(data);
return(result);
}
function Audio_Clip
audio_clip_from_wav_file_name(Arena *arena, char *file_name){
Audio_Clip result = {};
String_Const_u8 data = {};
FILE *file = fopen(file_name, "rb");
if (file != 0){
result = audio_clip_from_wav_FILE(arena, file);
fclose(file);
}
return(result);
}

View File

@ -0,0 +1,56 @@
/* date = November 23rd 2020 1:18 pm */
#ifndef FCODER_AUDIO_H
#define FCODER_AUDIO_H
////////////////////////////////
// NOTE(allen): Default Mixer Types
struct Audio_Control{
volatile f32 channel_volume[2];
volatile u32 generation;
volatile u32 last_played_sample_index;
volatile b32 loop;
};
struct Audio_Clip{
i16 *samples;
Audio_Control *control;
f32 channel_volume[2];
u32 sample_count;
u32 at_sample_index;
};
struct Audio_System{
volatile u32 quit;
volatile u32 ticket;
volatile u32 serving;
volatile u32 generation;
Audio_Clip playing_clips[64];
// NOTE(casey): Requests to play sounds are written to a pending array to avoid long locking
volatile u32 pending_clip_count;
Audio_Clip pending_clips[64];
};
////////////////////////////////
// NOTE(allen): Default Mixer
function void def_audio_init(void);
function void def_audio_play_clip(Audio_Clip clip, Audio_Control *control);
function b32 def_audio_is_playing(Audio_Control *control);
function void def_audio_stop(Audio_Control *control);
function void def_audio_mix_sources(void *ctx, f32 *mix_buffer, u32 sample_count);
function void def_audio_mix_destination(i16 *dst, f32 *src, u32 sample_count);
////////////////////////////////
// NOTE(allen): Loading Clip
function Audio_Clip audio_clip_from_wav_data(String_Const_u8 data);
function Audio_Clip audio_clip_from_wav_FILE(Arena *arena, FILE *file);
function Audio_Clip audio_clip_from_wav_file_name(Arena *arena, char *file_name);
#endif //4CODER_AUDIO_H

View File

@ -0,0 +1,489 @@
/*
4coder_auto_indent.cpp - Commands for automatic indentation.
*/
// TOP
internal Batch_Edit*
make_batch_from_indentations(Application_Links *app, Arena *arena, Buffer_ID buffer, Range_i64 lines, i64 *indentations, Indent_Flag flags, i32 tab_width){
i64 *shifted_indentations = indentations - lines.first;
Batch_Edit *batch_first = 0;
Batch_Edit *batch_last = 0;
for (i64 line_number = lines.first;
line_number <= lines.max;
++line_number){
i64 line_start_pos = get_line_start_pos(app, buffer, line_number);
Indent_Info indent_info = get_indent_info_line_number_and_start(app, buffer, line_number, line_start_pos, tab_width);
i64 correct_indentation = shifted_indentations[line_number];
if (indent_info.is_blank && HasFlag(flags, Indent_ClearLine)){
correct_indentation = 0;
}
if (correct_indentation <= -1){
correct_indentation = indent_info.indent_pos;
}
if (correct_indentation != indent_info.indent_pos){
u64 str_size = 0;
u8 *str = 0;
if (HasFlag(flags, Indent_UseTab)){
i64 tab_count = correct_indentation/tab_width;
i64 indent = tab_count*tab_width;
i64 space_count = correct_indentation - indent;
str_size = tab_count + space_count;
str = push_array(arena, u8, str_size);
block_fill_u8(str, tab_count, '\t');
block_fill_u8(str + tab_count, space_count, ' ');
}
else{
str_size = correct_indentation;
str = push_array(arena, u8, str_size);
block_fill_u8(str, str_size, ' ');
}
Batch_Edit *batch = push_array(arena, Batch_Edit, 1);
sll_queue_push(batch_first, batch_last, batch);
batch->edit.text = SCu8(str, str_size);
batch->edit.range = Ii64(line_start_pos, indent_info.first_char_pos);
}
}
return(batch_first);
}
internal void
set_line_indents(Application_Links *app, Arena *arena, Buffer_ID buffer, Range_i64 lines, i64 *indentations, Indent_Flag flags, i32 tab_width){
Batch_Edit *batch = make_batch_from_indentations(app, arena, buffer, lines, indentations, flags, tab_width);
if (batch != 0){
buffer_batch_edit(app, buffer, batch);
}
}
internal Token*
find_anchor_token(Application_Links *app, Buffer_ID buffer, Token_Array *tokens, i64 invalid_line){
ProfileScope(app, "find anchor token");
Token *result = 0;
if (tokens != 0 && tokens->tokens != 0){
result = tokens->tokens;
i64 invalid_pos = get_line_start_pos(app, buffer, invalid_line);
i32 scope_counter = 0;
i32 paren_counter = 0;
Token *token = tokens->tokens;
for (;;token += 1){
if (token->pos + token->size > invalid_pos){
break;
}
if (!HasFlag(token->flags, TokenBaseFlag_PreprocessorBody)){
if (scope_counter == 0 && paren_counter == 0){
result = token;
}
switch (token->kind){
case TokenBaseKind_ScopeOpen:
{
scope_counter += 1;
}break;
case TokenBaseKind_ScopeClose:
{
paren_counter = 0;
if (scope_counter > 0){
scope_counter -= 1;
}
}break;
case TokenBaseKind_ParentheticalOpen:
{
paren_counter += 1;
}break;
case TokenBaseKind_ParentheticalClose:
{
if (paren_counter > 0){
paren_counter -= 1;
}
}break;
}
}
}
}
return(result);
}
internal Nest*
indent__new_nest(Arena *arena, Nest_Alloc *alloc){
Nest *new_nest = alloc->free_nest;
if (new_nest == 0){
new_nest = push_array(arena, Nest, 1);
}
else{
sll_stack_pop(alloc->free_nest);
}
return(new_nest);
}
internal void
indent__free_nest(Nest_Alloc *alloc, Nest *nest){
sll_stack_push(alloc->free_nest, nest);
}
internal b32
indent__unfinished_statement(Token *token, Nest *current_nest){
b32 result = false;
if (current_nest != 0 && current_nest->kind == TokenBaseKind_ScopeOpen){
result = true;
switch (token->kind){
case TokenBaseKind_ScopeOpen:
case TokenBaseKind_ScopeClose:
case TokenBaseKind_StatementClose:
{
result = false;
}break;
}
if (HasFlag(token->flags, TokenBaseFlag_PreprocessorBody)){
result = false;
}
}
return(result);
}
function void
line_indent_cache_update(Application_Links *app, Buffer_ID buffer, i32 tab_width, Indent_Line_Cache *line_cache){
if (line_cache->line_number_for_cached_indent != line_cache->where_token_starts){
ProfileScope(app, "get indent info");
line_cache->line_number_for_cached_indent = line_cache->where_token_starts;
line_cache->start_pos = get_line_start_pos(app, buffer, line_cache->where_token_starts);
Range_i64 range = Ii64(line_cache->start_pos, line_cache->one_past_last_pos);
line_cache->indent_info = get_indent_info_range(app, buffer, range, tab_width);
}
}
internal i64*
get_indentation_array(Application_Links *app, Arena *arena, Buffer_ID buffer, Range_i64 lines, Indent_Flag flags, i32 tab_width, i32 indent_width){
ProfileScope(app, "get indentation array");
i64 count = lines.max - lines.min + 1;
i64 *indentations = push_array(arena, i64, count);
i64 *shifted_indentations = indentations - lines.first;
block_fill_u64(indentations, sizeof(*indentations)*count, (u64)(-1));
#if 0
Managed_Scope scope = buffer_get_managed_scope(app, buffer);
Token_Array *tokens = scope_attachment(app, scope, attachment_tokens, Token_Array);
#endif
Token_Array token_array = get_token_array_from_buffer(app, buffer);
Token_Array *tokens = &token_array;
i64 anchor_line = clamp_bot(1, lines.first - 1);
Token *anchor_token = find_anchor_token(app, buffer, tokens, anchor_line);
if (anchor_token != 0 &&
anchor_token >= tokens->tokens &&
anchor_token < tokens->tokens + tokens->count){
i64 line = get_line_number_from_pos(app, buffer, anchor_token->pos);
line = clamp_top(line, lines.first);
Token_Iterator_Array token_it = token_iterator(0, tokens, anchor_token);
Scratch_Block scratch(app, arena);
Nest *nest = 0;
Nest_Alloc nest_alloc = {};
i64 line_last_indented = line - 1;
i64 last_indent = 0;
i64 actual_indent = 0;
b32 in_unfinished_statement = false;
Indent_Line_Cache line_cache = {};
for (;;){
Token *token = token_it_read(&token_it);
if (line_cache.where_token_starts == 0 ||
token->pos >= line_cache.one_past_last_pos){
ProfileScope(app, "get line number");
line_cache.where_token_starts = get_line_number_from_pos(app, buffer, token->pos);
line_cache.one_past_last_pos = get_line_end_pos(app, buffer, line_cache.where_token_starts);
}
i64 current_indent = 0;
if (nest != 0){
current_indent = nest->indent;
}
i64 this_indent = current_indent;
i64 following_indent = current_indent;
b32 shift_by_actual_indent = false;
b32 ignore_unfinished_statement = false;
if (HasFlag(token->flags, TokenBaseFlag_PreprocessorBody)){
this_indent = 0;
}
else{
switch (token->kind){
case TokenBaseKind_ScopeOpen:
{
Nest *new_nest = indent__new_nest(arena, &nest_alloc);
sll_stack_push(nest, new_nest);
nest->kind = TokenBaseKind_ScopeOpen;
nest->indent = current_indent + indent_width;
following_indent = nest->indent;
ignore_unfinished_statement = true;
}break;
case TokenBaseKind_ScopeClose:
{
for (;nest != 0 && nest->kind != TokenBaseKind_ScopeOpen;){
Nest *n = nest;
sll_stack_pop(nest);
indent__free_nest(&nest_alloc, n);
}
if (nest != 0 && nest->kind == TokenBaseKind_ScopeOpen){
Nest *n = nest;
sll_stack_pop(nest);
indent__free_nest(&nest_alloc, n);
}
this_indent = 0;
if (nest != 0){
this_indent = nest->indent;
}
following_indent = this_indent;
ignore_unfinished_statement = true;
}break;
case TokenBaseKind_ParentheticalOpen:
{
Nest *new_nest = indent__new_nest(arena, &nest_alloc);
sll_stack_push(nest, new_nest);
nest->kind = TokenBaseKind_ParentheticalOpen;
line_indent_cache_update(app, buffer, tab_width, &line_cache);
nest->indent = (token->pos - line_cache.indent_info.first_char_pos) + 1;
following_indent = nest->indent;
shift_by_actual_indent = true;
ignore_unfinished_statement = true;
}break;
case TokenBaseKind_ParentheticalClose:
{
if (nest != 0 && nest->kind == TokenBaseKind_ParentheticalOpen){
Nest *n = nest;
sll_stack_pop(nest);
indent__free_nest(&nest_alloc, n);
}
following_indent = 0;
if (nest != 0){
following_indent = nest->indent;
}
//ignore_unfinished_statement = true;
}break;
}
if (token->sub_kind == TokenCppKind_BlockComment ||
token->sub_kind == TokenCppKind_LiteralStringRaw){
ignore_unfinished_statement = true;
}
if (in_unfinished_statement && !ignore_unfinished_statement){
this_indent += indent_width;
}
}
#define EMIT(N) \
Stmnt(if (lines.first <= line_it){shifted_indentations[line_it]=N;} \
if (line_it == lines.end){goto finished;} \
actual_indent = N; )
i64 line_it = line_last_indented;
if (lines.first <= line_cache.where_token_starts){
for (;line_it < line_cache.where_token_starts;){
line_it += 1;
if (line_it == line_cache.where_token_starts){
EMIT(this_indent);
}
else{
EMIT(last_indent);
}
}
}
else{
actual_indent = this_indent;
line_it = line_cache.where_token_starts;
}
i64 line_where_token_ends = get_line_number_from_pos(app, buffer, token->pos + token->size);
if (lines.first <= line_where_token_ends){
line_indent_cache_update(app, buffer, tab_width, &line_cache);
i64 line_where_token_starts_shift = this_indent - line_cache.indent_info.indent_pos;
for (;line_it < line_where_token_ends;){
line_it += 1;
i64 line_it_start_pos = get_line_start_pos(app, buffer, line_it);
Indent_Info line_it_indent_info = get_indent_info_line_number_and_start(app, buffer, line_it, line_it_start_pos, tab_width);
i64 new_indent = line_it_indent_info.indent_pos + line_where_token_starts_shift;
new_indent = clamp_bot(0, new_indent);
EMIT(new_indent);
}
}
else{
line_it = line_where_token_ends;
}
#undef EMIT
if (shift_by_actual_indent){
nest->indent += actual_indent;
following_indent += actual_indent;
}
if (token->kind != TokenBaseKind_Comment){
in_unfinished_statement = indent__unfinished_statement(token, nest);
if (in_unfinished_statement){
following_indent += indent_width;
}
}
last_indent = following_indent;
line_last_indented = line_it;
if (!token_it_inc_non_whitespace(&token_it)){
break;
}
}
}
finished:;
return(indentations);
}
internal b32
auto_indent_buffer(Application_Links *app, Buffer_ID buffer, Range_i64 pos, Indent_Flag flags, i32 tab_width, i32 indent_width){
ProfileScope(app, "auto indent buffer");
Token_Array token_array = get_token_array_from_buffer(app, buffer);
Token_Array *tokens = &token_array;
b32 result = false;
if (tokens->tokens != 0){
result = true;
Scratch_Block scratch(app);
Range_i64 line_numbers = {};
if (HasFlag(flags, Indent_FullTokens)){
i32 safety_counter = 0;
for (;;){
Range_i64 expanded = enclose_tokens(app, buffer, pos);
expanded = enclose_whole_lines(app, buffer, expanded);
if (expanded == pos){
break;
}
pos = expanded;
safety_counter += 1;
if (safety_counter == 20){
pos = buffer_range(app, buffer);
break;
}
}
}
line_numbers = get_line_range_from_pos_range(app, buffer, pos);
i64 *indentations = get_indentation_array(app, scratch, buffer, line_numbers, flags, tab_width, indent_width);
set_line_indents(app, scratch, buffer, line_numbers, indentations, flags, tab_width);
}
return(result);
}
function void
auto_indent_buffer(Application_Links *app, Buffer_ID buffer, Range_i64 pos, Indent_Flag flags){
i32 indent_width = (i32)def_get_config_u64(app, vars_save_string_lit("indent_width"));
i32 tab_width = (i32)def_get_config_u64(app, vars_save_string_lit("default_tab_width"));
tab_width = clamp_bot(1, tab_width);
AddFlag(flags, Indent_FullTokens);
b32 indent_with_tabs = def_get_config_b32(vars_save_string_lit("indent_with_tabs"));
if (indent_with_tabs){
AddFlag(flags, Indent_UseTab);
}
auto_indent_buffer(app, buffer, pos, flags, indent_width, tab_width);
}
function void
auto_indent_buffer(Application_Links *app, Buffer_ID buffer, Range_i64 pos){
auto_indent_buffer(app, buffer, pos, 0);
}
////////////////////////////////
CUSTOM_COMMAND_SIG(auto_indent_whole_file)
CUSTOM_DOC("Audo-indents the entire current buffer.")
{
View_ID view = get_active_view(app, Access_ReadWriteVisible);
Buffer_ID buffer = view_get_buffer(app, view, Access_ReadWriteVisible);
i64 buffer_size = buffer_get_size(app, buffer);
auto_indent_buffer(app, buffer, Ii64(0, buffer_size));
}
CUSTOM_COMMAND_SIG(auto_indent_line_at_cursor)
CUSTOM_DOC("Auto-indents the line on which the cursor sits.")
{
View_ID view = get_active_view(app, Access_ReadWriteVisible);
Buffer_ID buffer = view_get_buffer(app, view, Access_ReadWriteVisible);
i64 pos = view_get_cursor_pos(app, view);
auto_indent_buffer(app, buffer, Ii64(pos));
move_past_lead_whitespace(app, view, buffer);
}
CUSTOM_COMMAND_SIG(auto_indent_range)
CUSTOM_DOC("Auto-indents the range between the cursor and the mark.")
{
View_ID view = get_active_view(app, Access_ReadWriteVisible);
Buffer_ID buffer = view_get_buffer(app, view, Access_ReadWriteVisible);
Range_i64 range = get_view_range(app, view);
auto_indent_buffer(app, buffer, range);
move_past_lead_whitespace(app, view, buffer);
}
CUSTOM_COMMAND_SIG(write_text_and_auto_indent)
CUSTOM_DOC("Inserts text and auto-indents the line on which the cursor sits if any of the text contains 'layout punctuation' such as ;:{}()[]# and new lines.")
{
ProfileScope(app, "write and auto indent");
User_Input in = get_current_input(app);
String_Const_u8 insert = to_writable(&in);
if (insert.str != 0 && insert.size > 0){
b32 do_auto_indent = false;
for (u64 i = 0; !do_auto_indent && i < insert.size; i += 1){
switch (insert.str[i]){
case ';': case ':':
case '{': case '}':
case '(': case ')':
case '[': case ']':
case '#':
case '\n': case '\t':
{
do_auto_indent = true;
}break;
}
}
if (do_auto_indent){
View_ID view = get_active_view(app, Access_ReadWriteVisible);
Buffer_ID buffer = view_get_buffer(app, view, Access_ReadWriteVisible);
Range_i64 pos = {};
if (view_has_highlighted_range(app, view)){
pos = get_view_range(app, view);
}
else{
pos.min = pos.max = view_get_cursor_pos(app, view);
}
write_text_input(app);
i64 end_pos = view_get_cursor_pos(app, view);
pos.min = Min(pos.min, end_pos);
pos.max = Max(pos.max, end_pos);
auto_indent_buffer(app, buffer, pos, 0);
move_past_lead_whitespace(app, view, buffer);
}
else{
write_text_input(app);
}
}
}
// BOTTOM

View File

@ -0,0 +1,38 @@
/*
4coder_auto_indent.h - Auto-indentation types.
*/
// TOP
#if !defined(FCODER_AUTO_INDENT_H)
#define FCODER_AUTO_INDENT_H
typedef u32 Indent_Flag;
enum{
Indent_ClearLine = 0x1,
Indent_UseTab = 0x2,
Indent_FullTokens = 0x4,
};
struct Nest{
Nest *next;
Token_Base_Kind kind;
i64 indent;
};
struct Nest_Alloc{
Nest *free_nest;
};
struct Indent_Line_Cache{
i64 where_token_starts;
i64 line_number_for_cached_indent;
i64 start_pos;
i64 one_past_last_pos;
Indent_Info indent_info;
};
#endif
// BOTTOM

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
/*
* Buffer seek descriptor constructors.
*/
// TOP
static Buffer_Seek
seek_pos(i64 pos){
Buffer_Seek result;
result.type = buffer_seek_pos;
result.pos = pos;
return(result);
}
static Buffer_Seek
seek_line_col(i64 line, i64 col){
Buffer_Seek result;
result.type = buffer_seek_line_col;
result.line = line;
result.col = col;
return(result);
}
// BOTTOM

View File

@ -0,0 +1,191 @@
/*
4coder_build_commands.cpp - Commands for building.
*/
// TOP
static String_Const_u8
push_build_directory_at_file(Application_Links *app, Arena *arena, Buffer_ID buffer){
String_Const_u8 result = {};
String_Const_u8 file_name = push_buffer_file_name(app, arena, buffer);
Temp_Memory restore_point = begin_temp(arena);
String_Const_u8 base_name = push_buffer_base_name(app, arena, buffer);
b32 is_match = string_match(file_name, base_name);
end_temp(restore_point);
if (!is_match){
result = push_string_copy(arena, string_remove_last_folder(file_name));
}
return(result);
}
#if OS_WINDOWS
global String_Const_u8 standard_build_file_name_array[] = {
str8_lit("build.bat"),
};
global String_Const_u8 standard_build_cmd_string_array[] = {
str8_lit("build"),
};
#elif OS_LINUX || OS_MAC
global String_Const_u8 standard_build_file_name_array[] = {
str8_lit("build.sh"),
str8_lit("Makefile"),
};
global String_Const_u8 standard_build_cmd_string_array[] = {
str8_lit("build.sh"),
str8_lit("make"),
};
#else
#error OS needs standard search and build rules
#endif
static String_Const_u8
push_fallback_command(Arena *arena, String_Const_u8 file_name){
return(push_u8_stringf(arena, "echo could not find %.*s", string_expand(file_name)));
}
static String_Const_u8
push_fallback_command(Arena *arena){
return(push_fallback_command(arena, standard_build_file_name_array[0]));
}
global_const Buffer_Identifier standard_build_build_buffer_identifier = buffer_identifier(string_u8_litexpr("*compilation*"));
global_const u32 standard_build_exec_flags = CLI_OverlapWithConflict|CLI_SendEndSignal;
static void
standard_build_exec_command(Application_Links *app, View_ID view, String_Const_u8 dir, String_Const_u8 cmd){
exec_system_command(app, view, standard_build_build_buffer_identifier,
dir, cmd,
standard_build_exec_flags);
}
function b32
standard_search_and_build_from_dir(Application_Links *app, View_ID view, String_Const_u8 start_dir){
Scratch_Block scratch(app);
// NOTE(allen): Search
String_Const_u8 full_file_path = {};
String_Const_u8 cmd_string = {};
for (i32 i = 0; i < ArrayCount(standard_build_file_name_array); i += 1){
full_file_path = push_file_search_up_path(app, scratch, start_dir, standard_build_file_name_array[i]);
if (full_file_path.size > 0){
cmd_string = standard_build_cmd_string_array[i];
break;
}
}
b32 result = (full_file_path.size > 0);
if (result){
// NOTE(allen): Build
String_Const_u8 path = string_remove_last_folder(full_file_path);
String_Const_u8 command = push_u8_stringf(scratch, "\"%.*s/%.*s\"",
string_expand(path),
string_expand(cmd_string));
b32 auto_save = def_get_config_b32(vars_save_string_lit("automatically_save_changes_on_build"));
if (auto_save){
save_all_dirty_buffers(app);
}
standard_build_exec_command(app, view, path, command);
print_message(app, push_u8_stringf(scratch, "Building with: %.*s\n",
string_expand(full_file_path)));
}
return(result);
}
// NOTE(allen): This searches first using the active file's directory,
// then if no build script is found, it searches from 4coders hot directory.
static void
standard_search_and_build(Application_Links *app, View_ID view, Buffer_ID active_buffer){
Scratch_Block scratch(app);
b32 did_build = false;
String_Const_u8 build_dir = push_build_directory_at_file(app, scratch, active_buffer);
if (build_dir.size > 0){
did_build = standard_search_and_build_from_dir(app, view, build_dir);
}
if (!did_build){
build_dir = push_hot_directory(app, scratch);
if (build_dir.size > 0){
did_build = standard_search_and_build_from_dir(app, view, build_dir);
}
}
if (!did_build){
standard_build_exec_command(app, view,
push_hot_directory(app, scratch),
push_fallback_command(scratch));
}
}
CUSTOM_COMMAND_SIG(build_search)
CUSTOM_DOC("Looks for a build.bat, build.sh, or makefile in the current and parent directories. Runs the first that it finds and prints the output to *compilation*.")
{
View_ID view = get_active_view(app, Access_Always);
Buffer_ID buffer = view_get_buffer(app, view, Access_Always);
standard_search_and_build(app, view, buffer);
block_zero_struct(&prev_location);
lock_jump_buffer(app, string_u8_litexpr("*compilation*"));
}
static Buffer_ID
get_comp_buffer(Application_Links *app){
return(get_buffer_by_name(app, string_u8_litexpr("*compilation*"), Access_Always));
}
static View_ID
get_or_open_build_panel(Application_Links *app){
View_ID view = 0;
Buffer_ID buffer = get_comp_buffer(app);
if (buffer != 0){
view = get_first_view_with_buffer(app, buffer);
}
if (view == 0){
view = open_build_footer_panel(app);
}
return(view);
}
function void
set_fancy_compilation_buffer_font(Application_Links *app){
Scratch_Block scratch(app);
Buffer_ID buffer = get_comp_buffer(app);
Font_Load_Location font = {};
font.file_name = def_search_normal_full_path(scratch, str8_lit("fonts/Inconsolata-Regular.ttf"));
set_buffer_face_by_font_load_location(app, buffer, &font);
}
CUSTOM_COMMAND_SIG(build_in_build_panel)
CUSTOM_DOC("Looks for a build.bat, build.sh, or makefile in the current and parent directories. Runs the first that it finds and prints the output to *compilation*. Puts the *compilation* buffer in a panel at the footer of the current view.")
{
View_ID view = get_active_view(app, Access_Always);
Buffer_ID buffer = view_get_buffer(app, view, Access_Always);
View_ID build_view = get_or_open_build_panel(app);
standard_search_and_build(app, build_view, buffer);
set_fancy_compilation_buffer_font(app);
block_zero_struct(&prev_location);
lock_jump_buffer(app, string_u8_litexpr("*compilation*"));
}
CUSTOM_COMMAND_SIG(close_build_panel)
CUSTOM_DOC("If the special build panel is open, closes it.")
{
close_build_footer_panel(app);
}
CUSTOM_COMMAND_SIG(change_to_build_panel)
CUSTOM_DOC("If the special build panel is open, makes the build panel the active panel.")
{
View_ID view = get_or_open_build_panel(app);
if (view != 0){
view_set_active(app, view);
}
}
// BOTTOM

View File

@ -0,0 +1,12 @@
/*
4coder_build_commands.h - Commands for building types.
*/
// TOP
#if !defined(FCODER_BUILD_COMMANDS_H)
#define FCODER_BUILD_COMMANDS_H
#endif
// BOTTOM

View File

@ -0,0 +1,54 @@
/*
4coder_system_command.cpp - Commands for executing arbitrary system command line instructions.
*/
// TOP
CUSTOM_COMMAND_SIG(execute_previous_cli)
CUSTOM_DOC("If the command execute_any_cli has already been used, this will execute a CLI reusing the most recent buffer name and command.")
{
String_Const_u8 out_buffer = SCu8(out_buffer_space);
String_Const_u8 cmd = SCu8(command_space);
String_Const_u8 hot_directory = SCu8(hot_directory_space);
if (out_buffer.size > 0 && cmd.size > 0 && hot_directory.size > 0){
View_ID view = get_active_view(app, Access_Always);
Buffer_Identifier id = buffer_identifier(out_buffer);
exec_system_command(app, view, id, hot_directory, cmd, CLI_OverlapWithConflict|CLI_CursorAtEnd|CLI_SendEndSignal);
lock_jump_buffer(app, out_buffer);
}
}
CUSTOM_COMMAND_SIG(execute_any_cli)
CUSTOM_DOC("Queries for an output buffer name and system command, runs the system command as a CLI and prints the output to the specified buffer."){
Scratch_Block scratch(app);
Query_Bar_Group group(app);
Query_Bar bar_out = {};
bar_out.prompt = string_u8_litexpr("Output Buffer: ");
bar_out.string = SCu8(out_buffer_space, (u64)0);
bar_out.string_capacity = sizeof(out_buffer_space);
if (!query_user_string(app, &bar_out)) return;
bar_out.string.size = clamp_top(bar_out.string.size, sizeof(out_buffer_space) - 1);
out_buffer_space[bar_out.string.size] = 0;
Query_Bar bar_cmd = {};
bar_cmd.prompt = string_u8_litexpr("Command: ");
bar_cmd.string = SCu8(command_space, (u64)0);
bar_cmd.string_capacity = sizeof(command_space);
if (!query_user_string(app, &bar_cmd)) return;
bar_cmd.string.size = clamp_top(bar_cmd.string.size, sizeof(command_space) - 1);
command_space[bar_cmd.string.size] = 0;
String_Const_u8 hot = push_hot_directory(app, scratch);
{
u64 size = clamp_top(hot.size, sizeof(hot_directory_space));
block_copy(hot_directory_space, hot.str, size);
hot_directory_space[hot.size] = 0;
}
execute_previous_cli(app);
}
// BOTTOM

View File

@ -0,0 +1,423 @@
/*
4coder_clipboard.cpp - Copy paste commands and clipboard related setup.
*/
// TOP
CUSTOM_COMMAND_SIG(clipboard_record_clip)
CUSTOM_DOC("In response to a new clipboard contents events, saves the new clip onto the clipboard history")
{
User_Input in = get_current_input(app);
if (in.event.kind == InputEventKind_Core &&
in.event.core.code == CoreCode_NewClipboardContents){
clipboard_post_internal_only(0, in.event.core.string);
}
}
////////////////////////////////
function b32
clipboard_post_buffer_range(Application_Links *app, i32 clipboard_index, Buffer_ID buffer, Range_i64 range){
b32 success = false;
Scratch_Block scratch(app);
String_Const_u8 string = push_buffer_range(app, scratch, buffer, range);
if (string.size > 0){
clipboard_post(clipboard_index, string);
success = true;
}
return(success);
}
function b32
clipboard_update_history_from_system(Application_Links *app, i32 clipboard_id){
Scratch_Block scratch(app);
b32 result = false;
String_Const_u8 string = system_get_clipboard(scratch, clipboard_id);
if (string.str != 0){
clipboard_post_internal_only(clipboard_id, string);
result = true;
}
return(result);
}
global List_String_Const_u8 clipboard_collection_list = {};
function void
clipboard_collection_render(Application_Links *app, Frame_Info frame_info, View_ID view){
Scratch_Block scratch(app);
Rect_f32 region = draw_background_and_margin(app, view);
Vec2_f32 mid_p = (region.p1 + region.p0)*0.5f;
Fancy_Block message = {};
Fancy_Line *line = push_fancy_line(scratch, &message);
push_fancy_string(scratch, line, fcolor_id(defcolor_pop2),
string_u8_litexpr("Collecting all clipboard events "));
push_fancy_string(scratch, line, fcolor_id(defcolor_pop1),
string_u8_litexpr("press [escape] to stop"));
for (Node_String_Const_u8 *node = clipboard_collection_list.first;
node != 0;
node = node->next){
line = push_fancy_line(scratch, &message);
push_fancy_string(scratch, line, fcolor_id(defcolor_text_default), node->string);
}
Face_ID face_id = get_face_id(app, 0);
Vec2_f32 dim = get_fancy_block_dim(app, face_id, &message);
Vec2_f32 half_dim = dim*0.5f;
draw_fancy_block(app, face_id, fcolor_zero(), &message, mid_p - half_dim);
}
CUSTOM_UI_COMMAND_SIG(begin_clipboard_collection_mode)
CUSTOM_DOC("Allows the user to copy multiple strings from other applications before switching to 4coder and pasting them all.")
{
local_persist b32 in_clipboard_collection_mode = false;
if (!in_clipboard_collection_mode){
in_clipboard_collection_mode = true;
system_set_clipboard_catch_all(true);
Scratch_Block scratch(app);
block_zero_struct(&clipboard_collection_list);
View_ID view = get_this_ctx_view(app, Access_Always);
View_Context ctx = view_current_context(app, view);
ctx.render_caller = clipboard_collection_render;
ctx.hides_buffer = true;
View_Context_Block ctx_block(app, view, &ctx);
for (;;){
User_Input in = get_next_input(app, EventPropertyGroup_Any, EventProperty_Escape);
if (in.abort){
break;
}
if (in.event.kind == InputEventKind_KeyStroke && in.event.key.code == KeyCode_Escape){
break;
}
if (in.event.kind == InputEventKind_Core &&
in.event.core.code == CoreCode_NewClipboardContents){
String_Const_u8 stable_clip = clipboard_post_internal_only(0, in.event.core.string);
string_list_push(scratch, &clipboard_collection_list, stable_clip);
}
}
block_zero_struct(&clipboard_collection_list);
system_set_clipboard_catch_all(false);
in_clipboard_collection_mode = false;
}
}
CUSTOM_COMMAND_SIG(copy)
CUSTOM_DOC("Copy the text in the range from the cursor to the mark onto the clipboard.")
{
View_ID view = get_active_view(app, Access_ReadVisible);
Buffer_ID buffer = view_get_buffer(app, view, Access_ReadVisible);
Range_i64 range = get_view_range(app, view);
clipboard_post_buffer_range(app, 0, buffer, range);
}
CUSTOM_COMMAND_SIG(cut)
CUSTOM_DOC("Cut the text in the range from the cursor to the mark onto the clipboard.")
{
View_ID view = get_active_view(app, Access_ReadWriteVisible);
Buffer_ID buffer = view_get_buffer(app, view, Access_ReadWriteVisible);
Range_i64 range = get_view_range(app, view);
if (clipboard_post_buffer_range(app, 0, buffer, range)){
buffer_replace_range(app, buffer, range, string_u8_empty);
}
}
CUSTOM_COMMAND_SIG(paste)
CUSTOM_DOC("At the cursor, insert the text at the top of the clipboard.")
{
clipboard_update_history_from_system(app, 0);
i32 count = clipboard_count(0);
if (count > 0){
View_ID view = get_active_view(app, Access_ReadWriteVisible);
if_view_has_highlighted_range_delete_range(app, view);
set_next_rewrite(app, view, Rewrite_Paste);
Managed_Scope scope = view_get_managed_scope(app, view);
i32 *paste_index = scope_attachment(app, scope, view_paste_index_loc, i32);
if (paste_index != 0){
*paste_index = 0;
Scratch_Block scratch(app);
String_Const_u8 string = push_clipboard_index(scratch, 0, *paste_index);
if (string.size > 0){
Buffer_ID buffer = view_get_buffer(app, view, Access_ReadWriteVisible);
i64 pos = view_get_cursor_pos(app, view);
buffer_replace_range(app, buffer, Ii64(pos), string);
view_set_mark(app, view, seek_pos(pos));
view_set_cursor_and_preferred_x(app, view, seek_pos(pos + (i32)string.size));
ARGB_Color argb = fcolor_resolve(fcolor_id(defcolor_paste));
buffer_post_fade(app, buffer, 0.667f, Ii64_size(pos, string.size), argb);
}
}
}
}
CUSTOM_COMMAND_SIG(paste_next)
CUSTOM_DOC("If the previous command was paste or paste_next, replaces the paste range with the next text down on the clipboard, otherwise operates as the paste command.")
{
Scratch_Block scratch(app);
b32 new_clip = clipboard_update_history_from_system(app, 0);
i32 count = clipboard_count(0);
if (count > 0){
View_ID view = get_active_view(app, Access_ReadWriteVisible);
Managed_Scope scope = view_get_managed_scope(app, view);
Rewrite_Type *rewrite = scope_attachment(app, scope, view_rewrite_loc, Rewrite_Type);
if (rewrite != 0){
if (*rewrite == Rewrite_Paste && !new_clip){
no_mark_snap_to_cursor(app, scope);
set_next_rewrite(app, view, Rewrite_Paste);
i32 *paste_index_ptr = scope_attachment(app, scope, view_paste_index_loc, i32);
i32 paste_index = (*paste_index_ptr) + 1;
*paste_index_ptr = paste_index;
String_Const_u8 string = push_clipboard_index(scratch, 0, paste_index);
Buffer_ID buffer = view_get_buffer(app, view, Access_ReadWriteVisible);
Range_i64 range = get_view_range(app, view);
i64 pos = range.min;
buffer_replace_range(app, buffer, range, string);
view_set_cursor_and_preferred_x(app, view, seek_pos(pos + string.size));
ARGB_Color argb = fcolor_resolve(fcolor_id(defcolor_paste));
buffer_post_fade(app, buffer, 0.667f, Ii64_size(pos, string.size), argb);
}
else{
paste(app);
}
}
}
}
CUSTOM_COMMAND_SIG(paste_and_indent)
CUSTOM_DOC("Paste from the top of clipboard and run auto-indent on the newly pasted text.")
{
paste(app);
auto_indent_range(app);
}
CUSTOM_COMMAND_SIG(paste_next_and_indent)
CUSTOM_DOC("Paste the next item on the clipboard and run auto-indent on the newly pasted text.")
{
paste_next(app);
auto_indent_range(app);
}
CUSTOM_COMMAND_SIG(clear_clipboard)
CUSTOM_DOC("Clears the history of the clipboard")
{
clipboard_clear(0);
}
////////////////////////////////
CUSTOM_COMMAND_SIG(multi_paste)
CUSTOM_DOC("Paste multiple entries from the clipboard at once")
{
Scratch_Block scratch(app);
i32 count = clipboard_count(0);
if (count > 0){
View_ID view = get_active_view(app, Access_ReadWriteVisible);
Managed_Scope scope = view_get_managed_scope(app, view);
Rewrite_Type *rewrite = scope_attachment(app, scope, view_rewrite_loc, Rewrite_Type);
if (rewrite != 0){
if (*rewrite == Rewrite_Paste){
Rewrite_Type *next_rewrite = scope_attachment(app, scope, view_next_rewrite_loc, Rewrite_Type);
*next_rewrite = Rewrite_Paste;
i32 *paste_index_ptr = scope_attachment(app, scope, view_paste_index_loc, i32);
i32 paste_index = (*paste_index_ptr) + 1;
*paste_index_ptr = paste_index;
String_Const_u8 string = push_clipboard_index(scratch, 0, paste_index);
String_Const_u8 insert_string = push_u8_stringf(scratch, "\n%.*s", string_expand(string));
Buffer_ID buffer = view_get_buffer(app, view, Access_ReadWriteVisible);
Range_i64 range = get_view_range(app, view);
buffer_replace_range(app, buffer, Ii64(range.max), insert_string);
view_set_mark(app, view, seek_pos(range.max + 1));
view_set_cursor_and_preferred_x(app, view, seek_pos(range.max + insert_string.size));
ARGB_Color argb = fcolor_resolve(fcolor_id(defcolor_paste));
buffer_post_fade(app, buffer, 0.667f, Ii64(range.max + 1, range.max + insert_string.size), argb);
}
else{
paste(app);
}
}
}
}
function Range_i64
multi_paste_range(Application_Links *app, View_ID view, Range_i64 range, i32 paste_count, b32 old_to_new){
Scratch_Block scratch(app);
Range_i64 finish_range = range;
if (paste_count >= 1){
Buffer_ID buffer = view_get_buffer(app, view, Access_ReadWriteVisible);
if (buffer != 0){
i64 total_size = 0;
for (i32 paste_index = 0; paste_index < paste_count; ++paste_index){
Temp_Memory temp = begin_temp(scratch);
String_Const_u8 string = push_clipboard_index(scratch, 0, paste_index);
total_size += string.size + 1;
end_temp(temp);
}
total_size -= 1;
i32 first = paste_count - 1;
i32 one_past_last = -1;
i32 step = -1;
if (!old_to_new){
first = 0;
one_past_last = paste_count;
step = 1;
}
List_String_Const_u8 list = {};
for (i32 paste_index = first; paste_index != one_past_last; paste_index += step){
if (paste_index != first){
string_list_push(scratch, &list, SCu8("\n", 1));
}
String_Const_u8 string = push_clipboard_index(scratch, 0, paste_index);
if (string.size > 0){
string_list_push(scratch, &list, string);
}
}
String_Const_u8 flattened = string_list_flatten(scratch, list);
buffer_replace_range(app, buffer, range, flattened);
i64 pos = range.min;
finish_range.min = pos;
finish_range.max = pos + total_size;
view_set_mark(app, view, seek_pos(finish_range.min));
view_set_cursor_and_preferred_x(app, view, seek_pos(finish_range.max));
ARGB_Color argb = fcolor_resolve(fcolor_id(defcolor_paste));
buffer_post_fade(app, buffer, 0.667f, finish_range, argb);
}
}
return(finish_range);
}
function void
multi_paste_interactive_up_down(Application_Links *app, i32 paste_count, i32 clip_count){
View_ID view = get_active_view(app, Access_ReadWriteVisible);
i64 pos = view_get_cursor_pos(app, view);
b32 old_to_new = true;
Range_i64 range = multi_paste_range(app, view, Ii64(pos), paste_count, old_to_new);
Query_Bar_Group group(app);
Query_Bar bar = {};
bar.prompt = string_u8_litexpr("Up and Down to condense and expand paste stages; R to reverse order; Return to finish; Escape to abort.");
if (start_query_bar(app, &bar, 0) == 0) return;
User_Input in = {};
for (;;){
in = get_next_input(app, EventProperty_AnyKey, EventProperty_Escape);
if (in.abort) break;
b32 did_modify = false;
if (match_key_code(&in, KeyCode_Up)){
if (paste_count > 1){
--paste_count;
did_modify = true;
}
}
else if (match_key_code(&in, KeyCode_Down)){
if (paste_count < clip_count){
++paste_count;
did_modify = true;
}
}
else if (match_key_code(&in, KeyCode_R)){
old_to_new = !old_to_new;
did_modify = true;
}
else if (match_key_code(&in, KeyCode_Return)){
break;
}
if (did_modify){
range = multi_paste_range(app, view, range, paste_count, old_to_new);
}
}
if (in.abort){
Buffer_ID buffer = view_get_buffer(app, view, Access_ReadWriteVisible);
buffer_replace_range(app, buffer, range, SCu8(""));
}
}
CUSTOM_COMMAND_SIG(multi_paste_interactive)
CUSTOM_DOC("Paste multiple lines from the clipboard history, controlled with arrow keys")
{
i32 clip_count = clipboard_count(0);
if (clip_count > 0){
multi_paste_interactive_up_down(app, 1, clip_count);
}
}
CUSTOM_COMMAND_SIG(multi_paste_interactive_quick)
CUSTOM_DOC("Paste multiple lines from the clipboard history, controlled by inputing the number of lines to paste")
{
i32 clip_count = clipboard_count(0);
if (clip_count > 0){
u8 string_space[256];
Query_Bar_Group group(app);
Query_Bar bar = {};
bar.prompt = string_u8_litexpr("How Many Slots To Paste: ");
bar.string = SCu8(string_space, (u64)0);
bar.string_capacity = sizeof(string_space);
query_user_number(app, &bar);
i32 initial_paste_count = (i32)string_to_integer(bar.string, 10);
initial_paste_count = clamp(1, initial_paste_count, clip_count);
end_query_bar(app, &bar, 0);
multi_paste_interactive_up_down(app, initial_paste_count, clip_count);
}
}
////////////////////////////////
#if FCODER_TRANSITION_TO < 4001004
function void
clipboard_clear(Application_Links *app, i32 clipboard_id){
clipboard_clear(clipboard_id);
}
function b32
clipboard_post(Application_Links *app, i32 clipboard_id, String_Const_u8 string){
return(clipboard_post(clipboard_id, string));
}
function i32
clipboard_count(Application_Links *app, i32 clipboard_id){
return(clipboard_count(clipboard_id));
}
function String_Const_u8
push_clipboard_index(Application_Links *app, Arena *arena, i32 clipboard_id, i32 item_index){
return(push_clipboard_index(arena, clipboard_id, item_index));
}
#endif
// BOTTOM

View File

@ -0,0 +1,21 @@
/*
4coder_clipboard.cpp - Copy paste commands and clipboard related setup.
*/
// TOP
#ifndef FCODER_CLIPBOARD_H
#define FCODER_CLIPBOARD_H
struct Clipboard{
Arena arena;
Heap heap;
String_Const_u8 *clips;
u32 clip_index;
u32 clip_capacity;
};
#endif //4CODER_CLIPBOARD_H
// BOTTOM

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,127 @@
/*
4coder_code_index.h - Generic code indexing system for layout, definition jumps, etc.
*/
// TOP
#if !defined(FCODER_CODE_INDEX_H)
#define FCODER_CODE_INDEX_H
struct Code_Index_Nest_List{
struct Code_Index_Nest *first;
struct Code_Index_Nest *last;
i32 count;
};
struct Code_Index_Nest_Ptr_Array{
struct Code_Index_Nest **ptrs;
i32 count;
};
typedef i32 Code_Index_Nest_Kind;
enum{
CodeIndexNest_Scope,
CodeIndexNest_Paren,
CodeIndexNest_Preprocessor,
CodeIndexNest_Statement,
};
struct Code_Index_Nest{
Code_Index_Nest *next;
Code_Index_Nest_Kind kind;
b32 is_closed;
Range_i64 open;
Range_i64 close;
struct Code_Index_File *file;
Code_Index_Nest *parent;
Code_Index_Nest_List nest_list;
Code_Index_Nest_Ptr_Array nest_array;
};
typedef i64 Code_Index_Note_Kind;
enum{
CodeIndexNote_Type,
CodeIndexNote_Function,
CodeIndexNote_Macro,
CodeIndexNote_4coderCommand,
};
struct Code_Index_Note{
Code_Index_Note *next;
Code_Index_Note_Kind note_kind;
Range_i64 pos;
String_Const_u8 text;
struct Code_Index_File *file;
Code_Index_Nest *parent;
Code_Index_Note *prev_in_hash;
Code_Index_Note *next_in_hash;
};
struct Code_Index_Note_List{
Code_Index_Note *first;
Code_Index_Note *last;
i32 count;
};
struct Code_Index_Note_Ptr_Array{
Code_Index_Note **ptrs;
i32 count;
};
struct Code_Index_File{
Code_Index_Nest_List nest_list;
Code_Index_Nest_Ptr_Array nest_array;
Code_Index_Note_List note_list;
Code_Index_Note_Ptr_Array note_array;
Buffer_ID buffer;
};
struct Code_Index_File_Storage{
Code_Index_File_Storage *next;
Code_Index_File_Storage *prev;
Arena arena;
Code_Index_File *file;
};
struct Code_Index{
System_Mutex mutex;
Arena node_arena;
Table_u64_u64 buffer_to_index_file;
Code_Index_File_Storage *free_storage;
Code_Index_File_Storage *storage_first;
Code_Index_File_Storage *storage_last;
i32 storage_count;
Code_Index_Note_List name_hash[4099];
};
////////////////////////////////
typedef void Generic_Parse_Comment_Function(Application_Links *app, Arena *arena, Code_Index_File *index,
Token *token, String_Const_u8 contents);
struct Generic_Parse_State{
Application_Links *app;
Arena *arena;
String_Const_u8 contents;
Token_Iterator_Array it;
Generic_Parse_Comment_Function *handle_comment;
u8 *prev_line_start;
b32 finished;
i32 scope_counter;
i32 paren_counter;
b32 in_preprocessor;
b32 in_statement;
b32 do_cpp_parse;
};
#endif
// BOTTOM

View File

@ -0,0 +1,99 @@
/*
4coder_code_index_listers.cpp - Listers for exploring the contents of the code index.
*/
// TOP
struct Tiny_Jump{
Buffer_ID buffer;
i64 pos;
};
CUSTOM_UI_COMMAND_SIG(jump_to_definition)
CUSTOM_DOC("List all definitions in the code index and jump to one chosen by the user.")
{
char *query = "Definition:";
Scratch_Block scratch(app);
Lister_Block lister(app, scratch);
lister_set_query(lister, query);
lister_set_default_handlers(lister);
code_index_lock();
for (Buffer_ID buffer = get_buffer_next(app, 0, Access_Always);
buffer != 0;
buffer = get_buffer_next(app, buffer, Access_Always)){
Code_Index_File *file = code_index_get_file(buffer);
if (file != 0){
for (i32 i = 0; i < file->note_array.count; i += 1){
Code_Index_Note *note = file->note_array.ptrs[i];
Tiny_Jump *jump = push_array(scratch, Tiny_Jump, 1);
jump->buffer = buffer;
jump->pos = note->pos.first;
String_Const_u8 sort = {};
switch (note->note_kind){
case CodeIndexNote_Type:
{
sort = string_u8_litexpr("type");
}break;
case CodeIndexNote_Function:
{
sort = string_u8_litexpr("function");
}break;
case CodeIndexNote_Macro:
{
sort = string_u8_litexpr("macro");
}break;
}
lister_add_item(lister, note->text, sort, jump, 0);
}
}
}
code_index_unlock();
Lister_Result l_result = run_lister(app, lister);
Tiny_Jump result = {};
if (!l_result.canceled && l_result.user_data != 0){
block_copy_struct(&result, (Tiny_Jump*)l_result.user_data);
}
if (result.buffer != 0){
View_ID view = get_this_ctx_view(app, Access_Always);
point_stack_push_view_cursor(app, view);
jump_to_location(app, view, result.buffer, result.pos);
}
}
CUSTOM_UI_COMMAND_SIG(jump_to_definition_at_cursor)
CUSTOM_DOC("Jump to the first definition in the code index matching an identifier at the cursor")
{
View_ID view = get_active_view(app, Access_Visible);
if (view != 0){
Scratch_Block scratch(app);
String_Const_u8 query = push_token_or_word_under_active_cursor(app, scratch);
code_index_lock();
for (Buffer_ID buffer = get_buffer_next(app, 0, Access_Always);
buffer != 0;
buffer = get_buffer_next(app, buffer, Access_Always)){
Code_Index_File *file = code_index_get_file(buffer);
if (file != 0){
for (i32 i = 0; i < file->note_array.count; i += 1){
Code_Index_Note *note = file->note_array.ptrs[i];
if (string_match(note->text, query)){
point_stack_push_view_cursor(app, view);
jump_to_location(app, view, buffer, note->pos.first);
goto done;
}
}
}
}
done:;
code_index_unlock();
}
}
// BOTTOM

View File

@ -0,0 +1,72 @@
/*
4coder_codepoint_map.cpp - Codepoint map to index
*/
// TOP
function b32
codepoint_index_map_read(Codepoint_Index_Map *map, u32 codepoint, u16 *index_out){
b32 success = true;
if (codepoint == 0 && map->has_zero_index){
*index_out = map->zero_index;
}
else if (table_read(&map->table, codepoint, index_out)){
// NOTE(allen): do nothing
}
else{
success = false;
}
return(success);
}
function u16
codepoint_index_map_count(Codepoint_Index_Map *map){
return(map->max_index + 1);
}
function f32
font_get_glyph_advance(Face_Advance_Map *map, Face_Metrics *metrics, u32 codepoint, f32 tab_multiplier){
f32 result = 0.f;
if (codepoint == '\t'){
result = metrics->space_advance*tab_multiplier;
}
else{
if (character_is_whitespace(codepoint)){
codepoint = ' ';
}
u16 index = 0;
if (codepoint_index_map_read(&map->codepoint_to_index, codepoint, &index)){
if (index < map->index_count){
result = map->advance[index];
}
}
}
return(result);
}
function f32
font_get_max_glyph_advance_range(Face_Advance_Map *map, Face_Metrics *metrics,
u32 codepoint_first, u32 codepoint_last,
f32 tab_multiplier){
f32 result = font_get_glyph_advance(map, metrics, codepoint_first, tab_multiplier);
for (u32 i = codepoint_first + 1; i <= codepoint_last; i += 1){
f32 a = font_get_glyph_advance(map, metrics, i, tab_multiplier);
result = Max(a, result);
}
return(result);
}
function f32
font_get_average_glyph_advance_range(Face_Advance_Map *map, Face_Metrics *metrics,
u32 codepoint_first, u32 codepoint_last,
f32 tab_multiplier){
f32 result = 0.f;
for (u32 i = codepoint_first; i <= codepoint_last; i += 1){
result += font_get_glyph_advance(map, metrics, i, tab_multiplier);
}
result /= (f32)(codepoint_last - codepoint_first + 1);
return(result);
}
// BOTTOM

View File

@ -0,0 +1,253 @@
/*
4coder_combined_write_commands.cpp - Commands for writing text specialized for particular contexts.
*/
// TOP
function void
write_string(Application_Links *app, View_ID view, Buffer_ID buffer, String_Const_u8 string){
i64 pos = view_get_cursor_pos(app, view);
buffer_replace_range(app, buffer, Ii64(pos), string);
view_set_cursor_and_preferred_x(app, view, seek_pos(pos + string.size));
}
function void
write_string(Application_Links *app, String_Const_u8 string){
View_ID view = get_active_view(app, Access_ReadWriteVisible);
Buffer_ID buffer = view_get_buffer(app, view, Access_ReadWriteVisible);
write_string(app, view, buffer, string);
}
function void
write_named_comment_string(Application_Links *app, char *type_string){
Scratch_Block scratch(app);
String_Const_u8 name = def_get_config_string(scratch, vars_save_string_lit("user_name"));
String_Const_u8 str = {};
if (name.size > 0){
str = push_u8_stringf(scratch, "// %s(%.*s): ", type_string, string_expand(name));
}
else{
str = push_u8_stringf(scratch, "// %s: ", type_string);
}
write_string(app, str);
}
function void
long_braces(Application_Links *app, char *text, i32 size){
View_ID view = get_active_view(app, Access_ReadWriteVisible);
Buffer_ID buffer = view_get_buffer(app, view, Access_ReadWriteVisible);
i64 pos = view_get_cursor_pos(app, view);
buffer_replace_range(app, buffer, Ii64(pos), SCu8(text, size));
view_set_cursor_and_preferred_x(app, view, seek_pos(pos + 2));
auto_indent_buffer(app, buffer, Ii64_size(pos, size));
move_past_lead_whitespace(app, view, buffer);
}
CUSTOM_COMMAND_SIG(open_long_braces)
CUSTOM_DOC("At the cursor, insert a '{' and '}' separated by a blank line.")
{
char text[] = "{\n\n}";
i32 size = sizeof(text) - 1;
long_braces(app, text, size);
}
CUSTOM_COMMAND_SIG(open_long_braces_semicolon)
CUSTOM_DOC("At the cursor, insert a '{' and '};' separated by a blank line.")
{
char text[] = "{\n\n};";
i32 size = sizeof(text) - 1;
long_braces(app, text, size);
}
CUSTOM_COMMAND_SIG(open_long_braces_break)
CUSTOM_DOC("At the cursor, insert a '{' and '}break;' separated by a blank line.")
{
char text[] = "{\n\n}break;";
i32 size = sizeof(text) - 1;
long_braces(app, text, size);
}
CUSTOM_COMMAND_SIG(if0_off)
CUSTOM_DOC("Surround the range between the cursor and mark with an '#if 0' and an '#endif'")
{
place_begin_and_end_on_own_lines(app, "#if 0", "#endif");
}
CUSTOM_COMMAND_SIG(write_todo)
CUSTOM_DOC("At the cursor, insert a '// TODO' comment, includes user name if it was specified in config.4coder.")
{
write_named_comment_string(app, "TODO");
}
CUSTOM_COMMAND_SIG(write_hack)
CUSTOM_DOC("At the cursor, insert a '// HACK' comment, includes user name if it was specified in config.4coder.")
{
write_named_comment_string(app, "HACK");
}
CUSTOM_COMMAND_SIG(write_note)
CUSTOM_DOC("At the cursor, insert a '// NOTE' comment, includes user name if it was specified in config.4coder.")
{
write_named_comment_string(app, "NOTE");
}
CUSTOM_COMMAND_SIG(write_block)
CUSTOM_DOC("At the cursor, insert a block comment.")
{
place_begin_and_end_on_own_lines(app, "/* ", " */");
}
CUSTOM_COMMAND_SIG(write_zero_struct)
CUSTOM_DOC("At the cursor, insert a ' = {};'.")
{
write_string(app, string_u8_litexpr(" = {};"));
}
function i64
get_start_of_line_at_cursor(Application_Links *app, View_ID view, Buffer_ID buffer){
i64 pos = view_get_cursor_pos(app, view);
i64 line = get_line_number_from_pos(app, buffer, pos);
return(get_pos_past_lead_whitespace_from_line_number(app, buffer, line));
}
function b32
c_line_comment_starts_at_position(Application_Links *app, Buffer_ID buffer, i64 pos){
b32 alread_has_comment = false;
u8 check_buffer[2];
if (buffer_read_range(app, buffer, Ii64(pos, pos + 2), check_buffer)){
if (check_buffer[0] == '/' && check_buffer[1] == '/'){
alread_has_comment = true;
}
}
return(alread_has_comment);
}
CUSTOM_COMMAND_SIG(comment_line)
CUSTOM_DOC("Insert '//' at the beginning of the line after leading whitespace.")
{
View_ID view = get_active_view(app, Access_ReadWriteVisible);
Buffer_ID buffer = view_get_buffer(app, view, Access_ReadWriteVisible);
i64 pos = get_start_of_line_at_cursor(app, view, buffer);
b32 alread_has_comment = c_line_comment_starts_at_position(app, buffer, pos);
if (!alread_has_comment){
buffer_replace_range(app, buffer, Ii64(pos), string_u8_litexpr("//"));
}
}
CUSTOM_COMMAND_SIG(uncomment_line)
CUSTOM_DOC("If present, delete '//' at the beginning of the line after leading whitespace.")
{
View_ID view = get_active_view(app, Access_ReadWriteVisible);
Buffer_ID buffer = view_get_buffer(app, view, Access_ReadWriteVisible);
i64 pos = get_start_of_line_at_cursor(app, view, buffer);
b32 alread_has_comment = c_line_comment_starts_at_position(app, buffer, pos);
if (alread_has_comment){
buffer_replace_range(app, buffer, Ii64(pos, pos + 2), string_u8_empty);
}
}
CUSTOM_COMMAND_SIG(comment_line_toggle)
CUSTOM_DOC("Turns uncommented lines into commented lines and vice versa for comments starting with '//'.")
{
View_ID view = get_active_view(app, Access_ReadWriteVisible);
Buffer_ID buffer = view_get_buffer(app, view, Access_ReadWriteVisible);
i64 pos = get_start_of_line_at_cursor(app, view, buffer);
b32 alread_has_comment = c_line_comment_starts_at_position(app, buffer, pos);
if (alread_has_comment){
buffer_replace_range(app, buffer, Ii64(pos, pos + 2), string_u8_empty);
}
else{
buffer_replace_range(app, buffer, Ii64(pos), string_u8_litexpr("//"));
}
}
////////////////////////////////
static Snippet default_snippets[] = {
// general (for Allen's style)
{"if", "if (){\n\n}\n", 4, 7},
{"ifelse", "if (){\n\n}\nelse{\n\n}", 4, 7},
{"forn", "for (node = ;\nnode != 0;\nnode = node->next){\n\n}\n", 5, 38},
{"fori", "for (i = 0; i < ; i += 1){\n\n}\n", 5, 16},
{"forj", "for (j = 0; j < ; j += 1){\n\n}\n", 5, 16},
{"fork", "for (k = 0; k < ; k += 1){\n\n}\n", 5, 16},
{"for", "for (;;){\n\n}\n", 5, 10},
{"///", "////////////////////////////////", 32, 32},
{"#guard", "#if !defined(Z)\n#define Z\n#endif\n", 0, 26},
{"op+", "Z\noperator+(Z a, Z b){\n,\n}\n", 0, 23},
{"op-", "Z\noperator-(Z a, Z b){\n,\n}\n", 0, 23},
{"op*", "Z\noperator*(Z a, Z b){\n,\n}\n", 0, 23},
{"op/", "Z\noperator/(Z a, Z b){\n,\n}\n", 0, 23},
{"op+=", "Z&\noperator+=(Z &a, Z b){\n,\n}\n", 0, 26},
{"op-=", "Z&\noperator-=(Z &a, Z b){\n,\n}\n", 0, 26},
{"op*=", "Z&\noperator*=(Z &a, Z b){\n,\n}\n", 0, 26},
{"op/=", "Z&\noperator/=(Z &a, Z b){\n,\n}\n", 0, 26},
// for 4coder development
{"4command", "CUSTOM_COMMAND_SIG()\nCUSTOM_DOC()\n{\n\n}\n", 19, 32},
{"4app", "Application_Links *app", 22, 22},
#if defined(SNIPPET_EXPANSION)
#include SNIPPET_EXPANSION
#endif
};
function void
write_snippet(Application_Links *app, View_ID view, Buffer_ID buffer,
i64 pos, Snippet *snippet){
if (snippet != 0){
String_Const_u8 snippet_text = SCu8(snippet->text);
buffer_replace_range(app, buffer, Ii64(pos), snippet_text);
i64 new_cursor = pos + snippet->cursor_offset;
view_set_cursor_and_preferred_x(app, view, seek_pos(new_cursor));
i64 new_mark = pos + snippet->mark_offset;
view_set_mark(app, view, seek_pos(new_mark));
auto_indent_buffer(app, buffer, Ii64_size(pos, snippet_text.size));
}
}
function Snippet*
get_snippet_from_user(Application_Links *app, Snippet *snippets, i32 snippet_count,
String_Const_u8 query){
Scratch_Block scratch(app);
Lister_Block lister(app, scratch);
lister_set_query(lister, query);
lister_set_default_handlers(lister);
Snippet *snippet = snippets;
for (i32 i = 0; i < snippet_count; i += 1, snippet += 1){
lister_add_item(lister, SCu8(snippet->name), SCu8(snippet->text), snippet, 0);
}
Lister_Result l_result = run_lister(app, lister);
Snippet *result = 0;
if (!l_result.canceled){
result = (Snippet*)l_result.user_data;
}
return(result);
}
function Snippet*
get_snippet_from_user(Application_Links *app, Snippet *snippets, i32 snippet_count,
char *query){
return(get_snippet_from_user(app, snippets, snippet_count, SCu8(query)));
}
CUSTOM_UI_COMMAND_SIG(snippet_lister)
CUSTOM_DOC("Opens a snippet lister for inserting whole pre-written snippets of text.")
{
View_ID view = get_this_ctx_view(app, Access_ReadWrite);
if (view != 0){
Snippet *snippet = get_snippet_from_user(app, default_snippets,
ArrayCount(default_snippets),
"Snippet:");
Buffer_ID buffer = view_get_buffer(app, view, Access_ReadWriteVisible);
i64 pos = view_get_cursor_pos(app, view);
write_snippet(app, view, buffer, pos, snippet);
}
}
// BOTTOM

View File

@ -0,0 +1,20 @@
/*
4coder_combined_write_commands.cpp - Commands for writing text specialized for particular contexts.
*/
// TOP
struct Snippet{
char *name;
char *text;
i32 cursor_offset;
i32 mark_offset;
};
struct Snippet_Array{
Snippet *snippets;
i32 count;
};
// BOTTOM

View File

@ -0,0 +1,832 @@
/*
4coder_command_map.cpp - Command management functions
*/
// TOP
#if !defined(MAP_METADATA_ONLY)
#define MAP_METADATA_ONLY 0
#endif
#if MAP_METADATA_ONLY
#define BindingGetPtr(b) ((b).name)
#else
#define BindingGetPtr(b) ((b).custom)
#endif
Command_Binding::Command_Binding(){
block_zero_struct(this);
}
Command_Binding::Command_Binding(Custom_Command_Function *c){
this->custom = c;
}
Command_Binding::Command_Binding(char *n){
this->name = n;
}
Command_Binding::operator Custom_Command_Function*(){
return(this->custom);
}
Command_Binding::operator char*(){
return(this->name);
}
function u64
mapping__key(Input_Event_Kind kind, u32 sub_code){
return((((u64)kind) << 32) | sub_code);
}
function Command_Map*
mapping__alloc_map(Mapping *mapping){
Command_Map *result = mapping->free_maps;
if (result != 0){
sll_stack_pop(mapping->free_maps);
}
else{
result = push_array(&mapping->node_arena, Command_Map, 1);
}
zdll_push_back(mapping->first_map, mapping->last_map, result);
return(result);
}
function void
mapping__free_map(Mapping *mapping, Command_Map *map){
zdll_remove(mapping->first_map, mapping->last_map, map);
sll_stack_push(mapping->free_maps, map);
}
function Command_Modified_Binding*
mapping__alloc_modified_binding(Mapping *mapping){
Command_Modified_Binding *result = mapping->free_bindings;
if (result != 0){
sll_stack_pop(mapping->free_bindings);
}
else{
result = push_array(&mapping->node_arena, Command_Modified_Binding, 1);
}
return(result);
}
function void
mapping__free_modified_binding(Mapping *mapping, Command_Modified_Binding *binding){
sll_stack_push(mapping->free_bindings, binding);
}
function Command_Binding_List*
mapping__alloc_binding_list(Mapping *mapping){
Command_Binding_List *result = mapping->free_lists;
if (result != 0){
sll_stack_pop(mapping->free_lists);
}
else{
result = push_array(&mapping->node_arena, Command_Binding_List, 1);
}
return(result);
}
function void
mapping__free_binding_list(Mapping *mapping, Command_Binding_List *binding_list){
sll_stack_push(mapping->free_lists, binding_list);
}
function Command_Binding_List*
map__get_list(Command_Map *map, u64 key){
Command_Binding_List *result = 0;
Table_Lookup lookup = table_lookup(&map->event_code_to_binding_list, key);
if (lookup.found_match){
u64 val = 0;
table_read(&map->event_code_to_binding_list, lookup, &val);
result = (Command_Binding_List*)IntAsPtr(val);
}
return(result);
}
function Command_Binding_List*
map__get_or_make_list(Mapping *mapping, Command_Map *map, u64 key){
Command_Binding_List *result = map__get_list(map, key);
if (result == 0){
result = mapping__alloc_binding_list(mapping);
block_zero_struct(result);
sll_queue_push(map->list_first, map->list_last, result);
table_insert(&map->event_code_to_binding_list, key, (u64)PtrAsInt(result));
}
return(result);
}
////////////////////////////////
function void
mapping_init(Thread_Context *tctx, Mapping *mapping){
block_zero_struct(mapping);
mapping->node_arena = make_arena_system();
heap_init(&mapping->heap, &mapping->node_arena);
mapping->heap_wrapper = base_allocator_on_heap(&mapping->heap);
mapping->id_to_map = make_table_u64_u64(tctx->allocator, 10);
mapping->id_counter = 1;
}
function void
mapping_release(Thread_Context *tctx, Mapping *mapping){
linalloc_clear(&mapping->node_arena);
table_free(&mapping->id_to_map);
}
function void
map__init(Mapping *mapping, Command_Map *map, Command_Map_ID id){
block_zero_struct(map);
map->id = id;
map->node_arena = make_arena(&mapping->heap_wrapper, KB(2));
map->event_code_to_binding_list = make_table_u64_u64(&mapping->heap_wrapper, 100);
map->cmd_to_binding_trigger = make_table_u64_u64(&mapping->heap_wrapper, 100);
}
function Command_Map*
mapping_get_map(Mapping *mapping, Command_Map_ID id){
Command_Map *result = 0;
Table_Lookup lookup = table_lookup(&mapping->id_to_map, id);
if (lookup.found_match){
u64 val = 0;
table_read(&mapping->id_to_map, lookup, &val);
result = (Command_Map*)IntAsPtr(val);
}
return(result);
}
function Command_Map_ID
mapping_validate_id(Mapping *mapping, Command_Map_ID id){
Table_Lookup lookup = table_lookup(&mapping->id_to_map, id);
if (!lookup.found_match){
id = 0;
}
return(id);
}
function Command_Map*
mapping_get_or_make_map(Mapping *mapping, Command_Map_ID id){
Command_Map *result = mapping_get_map(mapping, id);
if (result == 0){
result = mapping__alloc_map(mapping);
map__init(mapping, result, id);
table_insert(&mapping->id_to_map, id, (u64)PtrAsInt(result));
}
return(result);
}
function void
mapping_release_map(Mapping *mapping, Command_Map *map){
table_erase(&mapping->id_to_map, map->id);
if (map->binding_last != 0){
map->binding_last->next = mapping->free_bindings;
mapping->free_bindings = map->binding_first;
}
if (map->list_last != 0){
map->list_last->next = mapping->free_lists;
mapping->free_lists = map->list_first;
}
table_free(&map->event_code_to_binding_list);
linalloc_clear(&map->node_arena);
}
////////////////////////////////
function b32
map_strict_match(Input_Modifier_Set *binding_mod_set, Input_Modifier_Set *event_mod_set, Key_Code skip_self_mod){
b32 result = true;
i32 binding_mod_count = binding_mod_set->count;
Key_Code *binding_mods = binding_mod_set->mods;
for (i32 i = 0; i < binding_mod_count; i += 1){
if (!has_modifier(event_mod_set, binding_mods[i])){
result = false;
break;
}
}
i32 mod_count = event_mod_set->count;
Key_Code *mods = event_mod_set->mods;
for (i32 i = 0; i < mod_count; i += 1){
if (mods[i] != skip_self_mod && !has_modifier(binding_mod_set, mods[i])){
result = false;
break;
}
}
return(result);
}
function b32
map_loose_match(Input_Modifier_Set *binding_mod_set, Input_Modifier_Set *event_mod_set){
b32 result = true;
i32 binding_mod_count = binding_mod_set->count;
Key_Code *binding_mods = binding_mod_set->mods;
for (i32 i = 0; i < binding_mod_count; i += 1){
if (!has_modifier(event_mod_set, binding_mods[i])){
result = false;
break;
}
}
return(result);
}
function Map_Event_Breakdown
map_get_event_breakdown(Input_Event *event){
Map_Event_Breakdown result = {};
switch (event->kind){
case InputEventKind_KeyStroke:
{
result.key = mapping__key(InputEventKind_KeyStroke, event->key.code);
result.mod_set = &event->key.modifiers;
result.skip_self_mod = event->key.code;
}break;
case InputEventKind_MouseButton:
{
result.key = mapping__key(InputEventKind_MouseButton, event->mouse.code);
result.mod_set = &event->mouse.modifiers;
}break;
case InputEventKind_MouseWheel:
{
result.key = mapping__key(InputEventKind_MouseWheel, 0);
result.mod_set = &event->mouse_wheel.modifiers;
}break;
case InputEventKind_MouseMove:
{
result.key = mapping__key(InputEventKind_MouseMove, 0);
result.mod_set = &event->mouse_move.modifiers;
}break;
case InputEventKind_Core:
{
result.key = mapping__key(InputEventKind_Core, event->core.code);
}break;
}
return(result);
}
function Command_Binding
map_get_binding_non_recursive(Command_Map *map, Input_Event *event, Binding_Match_Rule rule){
Command_Binding result = {};
if (event->kind == InputEventKind_CustomFunction){
result.custom = event->custom_func;
}
else if (map != 0){
if (event->kind == InputEventKind_TextInsert){
result = map->text_input_command;
}
else{
Map_Event_Breakdown breakdown = map_get_event_breakdown(event);
Table_Lookup lookup = table_lookup(&map->event_code_to_binding_list, breakdown.key);
if (lookup.found_match){
u64 val = 0;
table_read(&map->event_code_to_binding_list, lookup, &val);
Command_Binding_List *list = (Command_Binding_List*)IntAsPtr(val);
if (breakdown.mod_set != 0){
switch (rule){
case BindingMatchRule_Strict:
{
for (SNode *node = list->first;
node != 0;
node = node->next){
Command_Modified_Binding *mod_binding = CastFromMember(Command_Modified_Binding, order_node, node);
Input_Modifier_Set *binding_mod_set = &mod_binding->mods;
if (map_strict_match(binding_mod_set, breakdown.mod_set, breakdown.skip_self_mod)){
result = mod_binding->binding;
goto done;
}
}
}break;
case BindingMatchRule_Loose:
{
for (SNode *node = list->first;
node != 0;
node = node->next){
Command_Modified_Binding *mod_binding = CastFromMember(Command_Modified_Binding, order_node, node);
Input_Modifier_Set *binding_mod_set = &mod_binding->mods;
if (map_loose_match(binding_mod_set, breakdown.mod_set)){
result = mod_binding->binding;
goto done;
}
}
}break;
}
done:;
}
else{
Command_Modified_Binding *mod_binding = CastFromMember(Command_Modified_Binding, order_node, list->first);
result = mod_binding->binding;
}
}
}
}
return(result);
}
function Command_Binding
map_get_binding_non_recursive(Command_Map *map, Input_Event *event){
Command_Binding result = map_get_binding_non_recursive(map, event, BindingMatchRule_Strict);
if (result.custom == 0){
result = map_get_binding_non_recursive(map, event, BindingMatchRule_Loose);
}
return(result);
}
function Command_Binding
map_get_binding_recursive(Mapping *mapping, Command_Map *map, Input_Event *event, Binding_Match_Rule rule){
Command_Binding result = {};
for (i32 safety_counter = 0;
map != 0 && safety_counter < 40;
safety_counter += 1){
result = map_get_binding_non_recursive(map, event, rule);
if (result.custom != 0){
break;
}
map = mapping_get_map(mapping, map->parent);
}
return(result);
}
function Command_Binding
map_get_binding_recursive(Mapping *mapping, Command_Map *map, Input_Event *event){
Command_Binding result = map_get_binding_recursive(mapping, map, event, BindingMatchRule_Strict);
if (result.custom == 0){
result = map_get_binding_recursive(mapping, map, event, BindingMatchRule_Loose);
}
return(result);
}
function void
map_set_parent(Command_Map *map, Command_Map *parent){
if (map != 0 && parent != 0){
map->parent = parent->id;
}
}
function void
map_null_parent(Command_Map *map){
map->parent = 0;
}
function void
map__command_add_trigger(Command_Map *map, Command_Binding binding, Command_Trigger *trigger){
if (map != 0){
u64 key = (u64)(PtrAsInt(BindingGetPtr(binding)));
Table_Lookup lookup = table_lookup(&map->cmd_to_binding_trigger, key);
Command_Trigger_List *list = 0;
if (!lookup.found_match){
list = push_array_zero(&map->node_arena, Command_Trigger_List, 1);
table_insert(&map->cmd_to_binding_trigger, key, (u64)(PtrAsInt(list)));
}
else{
u64 val = 0;
table_read(&map->cmd_to_binding_trigger, lookup, &val);
list = (Command_Trigger_List*)IntAsPtr(val);
}
Command_Trigger *trigger_ptr = push_array(&map->node_arena, Command_Trigger, 1);
block_copy_struct(trigger_ptr, trigger);
sll_queue_push(list->first, list->last, trigger_ptr);
}
}
function Input_Event
map_trigger_as_event(Command_Trigger *trigger){
Input_Event result = {};
result.kind = trigger->kind;
switch (result.kind){
case InputEventKind_TextInsert:
{}break;
case InputEventKind_KeyStroke:
case InputEventKind_KeyRelease:
{
result.key.code = trigger->sub_code;
result.key.modifiers = trigger->mods;
}break;
case InputEventKind_MouseButton:
case InputEventKind_MouseButtonRelease:
{
result.mouse.code = trigger->sub_code;
result.mouse.modifiers = trigger->mods;
}break;
case InputEventKind_MouseWheel:
{
result.mouse_wheel.modifiers = trigger->mods;
}break;
case InputEventKind_MouseMove:
{
result.mouse_move.modifiers = trigger->mods;
}break;
case InputEventKind_Core:
{
result.core.code = trigger->sub_code;
}break;
}
return(result);
}
function Command_Trigger_List
map_get_triggers_non_recursive(Mapping *mapping, Command_Map *map, Command_Binding binding){
Command_Trigger_List *result_ptr = 0;
if (map != 0){
u64 key = (u64)(PtrAsInt(BindingGetPtr(binding)));
Table_Lookup lookup = table_lookup(&map->cmd_to_binding_trigger, key);
if (lookup.found_match){
u64 val = 0;
table_read(&map->cmd_to_binding_trigger, lookup, &val);
result_ptr = (Command_Trigger_List*)IntAsPtr(val);
Command_Trigger_List list = {};
for (Command_Trigger *node = result_ptr->first, *next = 0;
node != 0;
node = next){
next = node->next;
Input_Event event = map_trigger_as_event(node);
Command_Binding this_binding = {};
if (mapping != 0){
this_binding = map_get_binding_recursive(mapping, map, &event);
}
else{
this_binding = map_get_binding_non_recursive(map, &event);
}
if (BindingGetPtr(this_binding) == BindingGetPtr(binding)){
sll_queue_push(list.first, list.last, node);
}
}
*result_ptr = list;
}
}
Command_Trigger_List result = {};
if (result_ptr != 0){
result = *result_ptr;
}
return(result);
}
function Command_Trigger_List
map_get_triggers_non_recursive(Command_Map *map, Command_Binding binding){
return(map_get_triggers_non_recursive(0, map, binding));
}
function Command_Trigger_List
map_get_triggers_recursive(Arena *arena, Mapping *mapping, Command_Map *map, Command_Binding binding){
Command_Trigger_List result = {};
if (mapping != 0){
for (i32 safety_counter = 0;
map != 0 && safety_counter < 40;
safety_counter += 1){
Command_Trigger_List list = map_get_triggers_non_recursive(mapping, map, binding);
for (Command_Trigger *node = list.first, *next = 0;
node != 0;
node = next){
next = node->next;
Command_Trigger *nnode = push_array_write(arena, Command_Trigger, 1, node);
sll_queue_push(result.first, result.last, nnode);
}
map = mapping_get_map(mapping, map->parent);
}
}
return(result);
}
function Command_Binding_List*
map_get_binding_list_on_key(Command_Map *map, Key_Code code){
Command_Binding_List *result = 0;
if (map != 0){
u64 key = mapping__key(InputEventKind_KeyStroke, code);
result = map__get_list(map, key);
}
return(result);
}
function Command_Binding_List*
map_get_binding_list_on_mouse_button(Command_Map *map, Mouse_Code code){
Command_Binding_List *result = 0;
if (map != 0){
u64 key = mapping__key(InputEventKind_MouseButton, code);
result = map__get_list(map, key);
}
return(result);
}
function Command_Binding_List*
map_get_binding_list_on_core(Command_Map *map, Core_Code code){
Command_Binding_List *result = 0;
if (map != 0){
u64 key = mapping__key(InputEventKind_Core, code);
result = map__get_list(map, key);
}
return(result);
}
////////////////////////////////
function void
map_set_binding(Mapping *mapping, Command_Map *map, Command_Binding binding, u32 code1, u32 code2, Input_Modifier_Set *mods){
if (map != 0){
u64 key = mapping__key(code1, code2);
Command_Binding_List *list = map__get_or_make_list(mapping, map, key);
Command_Modified_Binding *mod_binding = mapping__alloc_modified_binding(mapping);
sll_stack_push(map->binding_first, mod_binding);
if (map->binding_last == 0){
map->binding_last = map->binding_first;
}
sll_stack_push(list->first, &mod_binding->order_node);
if (list->last == 0){
list->last= list->first;
}
list->count += 1;
mod_binding->mods = copy_modifier_set(&map->node_arena, mods);
mod_binding->binding = binding;
Command_Trigger trigger = {};
trigger.kind = code1;
trigger.sub_code = code2;
trigger.mods = mod_binding->mods;
map__command_add_trigger(map, binding, &trigger);
}
}
function void
map_set_binding_key(Mapping *mapping, Command_Map *map, Command_Binding binding, Key_Code code, Input_Modifier_Set *modifiers){
map_set_binding(mapping, map, binding, InputEventKind_KeyStroke, code, modifiers);
}
function void
map_set_binding_mouse(Mapping *mapping, Command_Map *map, Command_Binding binding, Mouse_Code code, Input_Modifier_Set *modifiers){
map_set_binding(mapping, map, binding, InputEventKind_MouseButton, code, modifiers);
}
function void
map_set_binding_core(Mapping *mapping, Command_Map *map, Command_Binding binding, Core_Code code, Input_Modifier_Set *modifiers){
map_set_binding(mapping, map, binding, InputEventKind_Core, code, modifiers);
}
function void
map_set_binding_text_input(Command_Map *map, Command_Binding binding){
if (map != 0){
map->text_input_command = binding;
Command_Trigger trigger = {};
trigger.kind = InputEventKind_TextInsert;
map__command_add_trigger(map, binding, &trigger);
}
}
////////////////////////////////
function Command_Binding_List*
map_get_binding_list_on_key(Mapping *mapping, Command_Map_ID map_id, Key_Code code){
Command_Map *map = mapping_get_map(mapping, map_id);
return(map_get_binding_list_on_key(map, code));
}
function Command_Binding
map_get_binding_non_recursive(Mapping *mapping, Command_Map_ID map_id, Input_Event *event){
Command_Map *map = mapping_get_map(mapping, map_id);
return(map_get_binding_non_recursive(map, event));
}
function Command_Binding
map_get_binding_recursive(Mapping *mapping, Command_Map_ID map_id, Input_Event *event){
Command_Map *map = mapping_get_map(mapping, map_id);
return(map_get_binding_recursive(mapping, map, event));
}
function Command_Trigger_List
map_get_triggers_non_recursive(Mapping *mapping, Command_Map_ID map_id, Command_Binding binding){
Command_Map *map = mapping_get_map(mapping, map_id);
return(map_get_triggers_non_recursive(map, binding));
}
function Command_Trigger_List
map_get_triggers_recursive(Arena *arena, Mapping *mapping, Command_Map_ID map_id, Command_Binding binding){
Command_Map *map = mapping_get_map(mapping, map_id);
return(map_get_triggers_recursive(arena, mapping, map, binding));
}
function void
map_set_parent(Mapping *mapping, Command_Map_ID map_id, Command_Map_ID parent_id){
Command_Map *map = mapping_get_map(mapping, map_id);
Command_Map *parent = mapping_get_map(mapping, parent_id);
map_set_parent(map, parent);
}
function void
map_set_parent(Mapping *mapping, Command_Map *map, Command_Map_ID parent_id){
Command_Map *parent = mapping_get_map(mapping, parent_id);
map_set_parent(map, parent);
}
function void
map_null_parent(Mapping *mapping, Command_Map_ID map_id){
Command_Map *map = mapping_get_map(mapping, map_id);
map_null_parent(map);
}
function void
map_set_binding(Mapping *mapping, Command_Map_ID map_id, Command_Binding binding,
u32 code1, u32 code2, Input_Modifier_Set *modifiers){
Command_Map *map = mapping_get_map(mapping, map_id);
map_set_binding(mapping, map, binding, code1, code2, modifiers);
}
function void
map_set_binding_key(Mapping *mapping, Command_Map_ID map_id, Command_Binding binding,
Key_Code code, Input_Modifier_Set *modifiers){
Command_Map *map = mapping_get_map(mapping, map_id);
map_set_binding_key(mapping, map, binding, code, modifiers);
}
function void
map_set_binding_mouse(Mapping *mapping, Command_Map_ID map_id, Command_Binding binding,
Mouse_Code code, Input_Modifier_Set *modifiers){
Command_Map *map = mapping_get_map(mapping, map_id);
map_set_binding_mouse(mapping, map, binding, code, modifiers);
}
function void
map_set_binding_core(Mapping *mapping, Command_Map_ID map_id, Command_Binding binding,
Core_Code code, Input_Modifier_Set *modifiers){
Command_Map *map = mapping_get_map(mapping, map_id);
map_set_binding_core(mapping, map, binding, code, modifiers);
}
function void
map_set_binding_text_input(Mapping *mapping, Command_Map_ID map_id, Command_Binding binding){
Command_Map *map = mapping_get_map(mapping, map_id);
map_set_binding_text_input(map, binding);
}
////////////////////////////////
function void
command_trigger_stringize_mods(Arena *arena, List_String_Const_u8 *list, Input_Modifier_Set *modifiers){
if (modifiers->count > 0){
string_list_push(arena, list, string_u8_litexpr(" holding:"));
i32 count = modifiers->count;
Key_Code *mods = modifiers->mods;
for (i32 i = 0; i < count; i += 1){
string_list_pushf(arena, list, " %s", ArraySafe(key_code_name, mods[i]));
}
}
}
function void
command_trigger_stringize(Arena *arena, List_String_Const_u8 *list, Command_Trigger *trigger){
string_list_push(arena, list, string_u8_litexpr("<"));
switch (trigger->kind){
case InputEventKind_TextInsert:
{
string_list_push(arena, list, string_u8_litexpr("TextInsert"));
}break;
case InputEventKind_KeyStroke:
{
String_Const_u8 key_name = SCu8(ArraySafe(key_code_name, trigger->sub_code));
string_list_push(arena, list, key_name);
command_trigger_stringize_mods(arena, list, &trigger->mods);
}break;
case InputEventKind_KeyRelease:
{
string_list_pushf(arena, list, "Release %s", ArraySafe(key_code_name, trigger->sub_code));
command_trigger_stringize_mods(arena, list, &trigger->mods);
}break;
case InputEventKind_MouseButton:
{
string_list_pushf(arena, list, "Mouse %s", ArraySafe(mouse_code_name, trigger->sub_code));
command_trigger_stringize_mods(arena, list, &trigger->mods);
}break;
case InputEventKind_MouseButtonRelease:
{
string_list_pushf(arena, list, "Release Mouse %s", ArraySafe(mouse_code_name, trigger->sub_code));
command_trigger_stringize_mods(arena, list, &trigger->mods);
}break;
case InputEventKind_MouseWheel:
{
string_list_push(arena, list, string_u8_litexpr("MouseWheel"));
command_trigger_stringize_mods(arena, list, &trigger->mods);
}break;
case InputEventKind_MouseMove:
{
string_list_push(arena, list, string_u8_litexpr("MouseMove"));
command_trigger_stringize_mods(arena, list, &trigger->mods);
}break;
case InputEventKind_Core:
{
string_list_pushf(arena, list, "Core %s", ArraySafe(core_code_name, trigger->sub_code));
}break;
default:
{
string_list_push(arena, list, string_u8_litexpr("ERROR unexpected trigger kind"));
}break;
}
string_list_push(arena, list, string_u8_litexpr(">"));
}
////////////////////////////////
function void
map_set_binding_lv(Mapping *mapping, Command_Map *map,
Command_Binding binding, u32 code1, u32 code2, va_list args){
Input_Modifier_Set mods = {};
Key_Code mods_array[Input_MaxModifierCount];
mods.mods = mods_array;
for (;mods.count < ArrayCount(mods_array);){
i32 v = va_arg(args, i32);
if (v <= 0){
break;
}
mods.mods[mods.count] = v;
mods.count += 1;
}
return(map_set_binding(mapping, map, binding, code1, code2, &mods));
}
#if MAP_METADATA_ONLY
function void
map_set_binding_l(Mapping *mapping, Command_Map *map, char *name, u32 code1, u32 code2, ...){
va_list args;
va_start(args, code2);
Command_Binding binding = {};
binding.name = name;
map_set_binding_lv(mapping, map, binding, code1, code2, args);
va_end(args);
}
#else
function void
map_set_binding_l(Mapping *mapping, Command_Map *map, Custom_Command_Function *custom, u32 code1, u32 code2, ...){
va_list args;
va_start(args, code2);
Command_Binding binding = {};
binding.custom = custom;
map_set_binding_lv(mapping, map, binding, code1, code2, args);
va_end(args);
}
#endif
#if MAP_METADATA_ONLY
# define BindFWrap_(F) stringify(F)
#else
# define BindFWrap_(F) F
#endif
#define MappingScope() Mapping *m = 0; Command_Map *map = 0
#define SelectMapping(N) m = (N)
#define SelectMap(ID) map = mapping_get_or_make_map(m, (ID))
#define ParentMap(ID) map_set_parent(m, map, (ID))
#define BindTextInput(F) map_set_binding_text_input(map, BindFWrap_(F))
#if COMPILER_CL
#define Bind(F, K, ...) \
map_set_binding_l(m, map, BindFWrap_(F), InputEventKind_KeyStroke, (K), __VA_ARGS__, 0)
#define BindRelease(F, K, ...) \
map_set_binding_l(m, map, BindFWrap_(F), InputEventKind_KeyRelease, (K), __VA_ARGS__, 0)
#define BindMouse(F, K, ...) \
map_set_binding_l(m, map, BindFWrap_(F), InputEventKind_MouseButton, (K), __VA_ARGS__, 0)
#define BindMouseRelease(F, K, ...) \
map_set_binding_l(m, map, BindFWrap_(F), InputEventKind_MouseButtonRelease, (K), __VA_ARGS__, 0)
#define BindMouseWheel(F, ...) \
map_set_binding_l(m, map, BindFWrap_(F), InputEventKind_MouseWheel, 0, __VA_ARGS__, 0)
#define BindMouseMove(F, ...) \
map_set_binding_l(m, map, BindFWrap_(F), InputEventKind_MouseMove, 0, __VA_ARGS__, 0)
#define BindCore(F, K, ...) \
map_set_binding_l(m, map, BindFWrap_(F), InputEventKind_Core, (K), __VA_ARGS__, 0)
#elif COMPILER_GCC | COMPILER_CLANG
#define Bind(F, K, ...) \
map_set_binding_l(m, map, BindFWrap_(F), InputEventKind_KeyStroke, (K), ##__VA_ARGS__, 0)
#define BindRelease(F, K, ...) \
map_set_binding_l(m, map, BindFWrap_(F), InputEventKind_KeyRelease, (K), ##__VA_ARGS__, 0)
#define BindMouse(F, K, ...) \
map_set_binding_l(m, map, BindFWrap_(F), InputEventKind_MouseButton, (K), ##__VA_ARGS__, 0)
#define BindMouseRelease(F, K, ...) \
map_set_binding_l(m, map, BindFWrap_(F), InputEventKind_MouseButtonRelease, (K), ##__VA_ARGS__, 0)
#define BindMouseWheel(F, ...) \
map_set_binding_l(m, map, BindFWrap_(F), InputEventKind_MouseWheel, 0, ##__VA_ARGS__, 0)
#define BindMouseMove(F, ...) \
map_set_binding_l(m, map, BindFWrap_(F), InputEventKind_MouseMove, 0, ##__VA_ARGS__, 0)
#define BindCore(F, K, ...) \
map_set_binding_l(m, map, BindFWrap_(F), InputEventKind_Core, (K), ##__VA_ARGS__, 0)
#else
#error "Unsupported compiler"
#endif
// BOTTOM

View File

@ -0,0 +1,96 @@
/*
4coder_command_map.h - Command management types
*/
// TOP
#if !defined(FCODER_CODEPOINT_MAP_H)
#define FCODER_CODEPOINT_MAP_H
typedef i64 Command_Map_ID;
struct Command_Trigger{
Command_Trigger *next;
Input_Event_Kind kind;
u32 sub_code;
Input_Modifier_Set mods;
};
struct Command_Trigger_List{
Command_Trigger *first;
Command_Trigger *last;
};
struct Command_Binding{
union{
Custom_Command_Function *custom;
char *name;
};
Command_Binding();
Command_Binding(Custom_Command_Function *c);
Command_Binding(char *n);
operator Custom_Command_Function*();
operator char*();
};
struct Command_Modified_Binding{
Command_Modified_Binding *next;
SNode order_node;
Input_Modifier_Set mods;
Command_Binding binding;
};
struct Command_Binding_List{
Command_Binding_List *next;
SNode *first;
SNode *last;
i32 count;
};
struct Command_Map{
Command_Map *next;
Command_Map *prev;
Command_Map_ID id;
Command_Map_ID parent;
Command_Binding text_input_command;
Arena node_arena;
Table_u64_u64 event_code_to_binding_list;
Table_u64_u64 cmd_to_binding_trigger;
Command_Modified_Binding *binding_first;
Command_Modified_Binding *binding_last;
Command_Binding_List *list_first;
Command_Binding_List *list_last;
struct Binding_Unit *real_beginning;
};
struct Mapping{
Arena node_arena;
Heap heap;
Base_Allocator heap_wrapper;
Table_u64_u64 id_to_map;
Command_Map_ID id_counter;
Command_Map *first_map;
Command_Map *last_map;
Command_Map *free_maps;
Command_Modified_Binding *free_bindings;
Command_Binding_List *free_lists;
};
typedef i32 Binding_Match_Rule;
enum{
BindingMatchRule_Strict,
BindingMatchRule_Loose,
};
struct Map_Event_Breakdown{
Input_Modifier_Set *mod_set;
u64 key;
Key_Code skip_self_mod;
};
#endif
// BOTTOM

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More